source: sasview/src/sas/perspectives/calculator/model_editor.py @ 439420f9

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 439420f9 was 439420f9, checked in by butler, 5 years ago

use Regular text module to check for valid python name when creating a
custom model from composite model editor (sum|multi(p1,p2)). also
refactor checking code and made notes for future extraction of non GUI
code.

  • Property mode set to 100644
File size: 54.5 KB
Line 
1'''
2This module provides three model editor classes: the composite model editor,
3the easy editor which provides a simple interface with tooltip help to enter
4the parameters of the model and their default value and a panel to input a
5function of y (usually the intensity).  It also provides a drop down of
6standard available math functions.  Finally a full python editor panel for
7complete customizatin is provided.
8
9:TODO the writiong of the file and name checking (and maybe some other
10funtions?) should be moved to a computational module which could be called
11fropm a python script.  Basically one just needs to pass the name,
12description text and function text (or in the case of the composite editor
13the names of the first and second model and the operator to be used).
14'''
15
16################################################################################
17#This software was developed by the University of Tennessee as part of the
18#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
19#project funded by the US National Science Foundation.
20#
21#See the license text in license.txt
22#
23#copyright 2009, University of Tennessee
24################################################################################
25import wx
26import sys
27import os
28import math
29import re
30from wx.py.editwindow import EditWindow
31
32if sys.platform.count("win32") > 0:
33    FONT_VARIANT = 0
34    PNL_WIDTH = 450
35    PNL_HITE = 320
36else:
37    FONT_VARIANT = 1
38    PNL_WIDTH = 590
39    PNL_HITE = 350
40M_NAME = 'Model'
41EDITOR_WIDTH = 800
42EDITOR_HEIGTH = 735
43PANEL_WIDTH = 500
44_BOX_WIDTH = 55
45
46
47def _compileFile(path):
48    """
49    Compile the file in the path
50    """
51    try:
52        import py_compile
53        py_compile.compile(file=path, doraise=True)
54        return ''
55    except:
56        _, value, _ = sys.exc_info()
57        return value
58
59def _deleteFile(path):
60    """
61    Delete file in the path
62    """
63    try:
64        os.remove(path)
65    except:
66        raise
67
68
69class TextDialog(wx.Dialog):
70    """
71    Dialog for easy custom composite models.  Provides a wx.Dialog panel
72    to choose two existing models (including pre-existing custom models which
73    may themselves be sum models) as well as an operation on those models (add
74    or multiply) the resulting model will add a scale parameter for summed
75    models and a background parameter for a multiplied model.
76   
77    The user also gives a brief help for the model in a description box and
78    must provide a unique name which is verified as unique before the new
79    model is saved.
80   
81    This Dialog pops up for the user when they press 'Sum|Multi(p1,p2)' under
82    'Edit Custom Model' under 'Fitting' menu.  This is currently called as
83    a Modal Dialog.
84   
85    :TODO the build in compiler currently balks at when it tries to import
86    a model whose name contains spaces or symbols (such as + ... underscore
87    should be fine) need to either alter how the model file is written on add
88    checking method to look for non-approved characters.  - PDB:April 5, 2015
89    """
90    def __init__(self, parent=None, base=None, id=None, title='',
91                 model_list=[], plugin_dir=None):
92        """
93        This class is run when instatiated.  The __init__ intializes and
94        calls the internal methods necessary.  On exiting the wx.Dialog
95        window should be destroyed.
96
97        """
98        wx.Dialog.__init__(self, parent=parent, id=id,
99                           title=title, size=(PNL_WIDTH, PNL_HITE))
100        self.parent = base
101        #Font
102        self.SetWindowVariant(variant=FONT_VARIANT)
103        # default
104        self.overwrite_name = False
105        self.plugin_dir = plugin_dir
106        self.model_list = model_list
107        self.model1_string = "SphereModel"
108        self.model2_string = "CylinderModel"
109        self.name = 'Sum' + M_NAME
110        self.factor = 'scale_factor'
111        self._notes = ''
112        self._operator = '+'
113        self._operator_choice = None
114        self.explanation = ''
115        self.explanationctr = None
116        self.type = None
117        self.name_sizer = None
118        self.name_tcl = None
119        self.desc_sizer = None
120        self.desc_tcl = None
121        self._selection_box= None
122        self.model1 = None
123        self.model2 = None
124        self.static_line_1 = None
125        self.okButton = None
126        self.closeButton = None
127        self._msg_box = None
128        self.msg_sizer = None
129        self.fname = None
130        self.cm_list = None
131        self.is_p1_custom = False
132        self.is_p2_custom = False
133        self._build_sizer()
134        self.model1_name = str(self.model1.GetValue())
135        self.model2_name = str(self.model2.GetValue())
136        self.good_name = True
137        self.fill_oprator_combox()
138
139    def _layout_name(self):
140        """
141        Do the layout for file/function name related widgets
142        """
143        #container for new model name input
144        self.name_sizer = wx.BoxSizer(wx.HORIZONTAL)
145
146        #set up label and input box with tool tip and event handling
147        self.name_txt = wx.StaticText(self, -1, 'Function Name : ')
148        self.name_tcl = wx.TextCtrl(self, -1, value = 'MySumFunction')
149        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
150        hint_name = "Unique Sum/Multiply Model Function Name."
151        self.name_tcl.SetToolTipString(hint_name)
152
153        self.name_sizer.AddMany([(self.name_txt, 0, wx.LEFT | wx.TOP, 10),
154                            (self.name_tcl, -1,
155                             wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM, 10)])
156
157
158    def _layout_description(self):
159        """
160        Do the layout for description related widgets
161        """
162        #container for new model description input
163        self.desc_sizer = wx.BoxSizer(wx.HORIZONTAL)
164
165        #set up description label and input box with tool tip and event handling
166        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')
167        self.desc_tcl = wx.TextCtrl(self, -1)
168        hint_desc = "Write a short description of this model function."
169        self.desc_tcl.SetToolTipString(hint_desc)
170
171        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT | wx.TOP, 10),
172                                (self.desc_tcl, -1,
173                                wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM, 10)])
174
175
176    def _layout_model_selection(self):
177        """
178        Do the layout for model selection related widgets
179        """
180        box_width = 195 # combobox width
181
182        #First set up main sizer for the selection
183        selection_box_title = wx.StaticBox(self, -1, 'Select',
184                                       size=(PNL_WIDTH - 30, 70))
185        self._selection_box = wx.StaticBoxSizer(selection_box_title, wx.VERTICAL)
186
187        #Next create the help labels for the model selection
188        select_help_box = wx.BoxSizer(wx.HORIZONTAL)
189        model_string = " Model%s (p%s):"
190        select_help_box.Add(wx.StaticText(self, -1, model_string % (1, 1)), 0, 0)
191        select_help_box.Add((box_width - 25, 10),0,0)
192        select_help_box.Add(wx.StaticText(self, -1, model_string % (2, 2)), 0, 0)
193        self._selection_box.Add(select_help_box, 0, 0)
194
195        #Next create the actual selection box with 3 combo boxes
196        selection_box_choose = wx.BoxSizer(wx.HORIZONTAL)
197
198        self.model1 = wx.ComboBox(self, -1, style=wx.CB_READONLY)
199        wx.EVT_COMBOBOX(self.model1, -1, self.on_model1)
200        self.model1.SetMinSize((box_width * 5 / 6, -1))
201        self.model1.SetToolTipString("model1")
202
203        self._operator_choice = wx.ComboBox(self, -1, size=(50, -1), style=wx.CB_READONLY)
204        wx.EVT_COMBOBOX(self._operator_choice, -1, self.on_select_operator)
205        operation_tip = "Add: +, Multiply: * "
206        self._operator_choice.SetToolTipString(operation_tip)
207
208        self.model2 = wx.ComboBox(self, -1, style=wx.CB_READONLY)
209        wx.EVT_COMBOBOX(self.model2, -1, self.on_model2)
210        self.model2.SetMinSize((box_width * 5 / 6, -1))
211        self.model2.SetToolTipString("model2")
212        self._set_model_list()
213
214        selection_box_choose.Add(self.model1, 0, 0)
215        selection_box_choose.Add((15, 10))
216        selection_box_choose.Add(self._operator_choice, 0, 0)
217        selection_box_choose.Add((15, 10))
218        selection_box_choose.Add(self.model2, 0, 0)
219        self._selection_box.Add((20,5), 0, 0) # add some space between labels and selection
220        self._selection_box.Add(selection_box_choose, 0, 0)
221
222    def _build_sizer(self):
223        """
224        Build GUI with calls to _layout_name, _layout Description
225        and _layout_model_selection which each build a their portion of the
226        GUI.
227        """
228        mainsizer = wx.BoxSizer(wx.VERTICAL) # create main sizer for dialog
229
230        # build fromm top by calling _layout_name and _layout_description
231        # and adding to main sizer
232        self._layout_name()
233        mainsizer.Add(self.name_sizer,0, wx.EXPAND)
234        self._layout_description()
235        mainsizer.Add(self.desc_sizer,0, wx.EXPAND)
236
237        # Add an explanation of dialog (short help)
238        self.explanationctr = wx.StaticText(self, -1, self.explanation)
239        self.fill_explanation_helpstring(self._operator)
240        mainsizer.Add(self.explanationctr,0, wx.LEFT | wx.EXPAND, 15)
241
242        # Add the selection box stuff with border and labels built
243        # by _layout_model_selection
244        self._layout_model_selection()
245        mainsizer.Add(self._selection_box, 0, wx.LEFT, 15)
246
247        # Add a space and horizontal line before the notification
248        #messages and the buttons at the bottom
249        mainsizer.Add((10, 10))
250        self.static_line_1 = wx.StaticLine(self, -1)
251        mainsizer.Add(self.static_line_1, 0, wx.EXPAND, 10)
252
253        # Add action status notification line (null at startup)
254        self._msg_box = wx.StaticText(self, -1, self._notes)
255        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
256        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 0)
257        mainsizer.Add(self.msg_sizer, 0,
258                 wx.LEFT | wx.RIGHT | wx.ADJUST_MINSIZE | wx.BOTTOM, 10)
259
260        # Finally add the buttons (apply and close) on the bottom
261        # TODO: need help added here
262        self.okButton = wx.Button(self, wx.ID_OK, 'Apply')
263        self.okButton.Bind(wx.EVT_BUTTON, self.check_name)
264        self.closeButton = wx.Button(self, wx.ID_CANCEL, 'Close')
265        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
266        sizer_button.AddMany([((20, 20), 1, 0),
267                              (self.okButton, 0, 0),
268                              (self.closeButton, 0, wx.LEFT | wx.RIGHT, 10)])       
269        mainsizer.Add(sizer_button, 0, wx.EXPAND | wx.BOTTOM | wx.TOP, 10)
270
271        self.SetSizer(mainsizer)
272        self.Centre()
273
274    def on_change_name(self, event=None):
275        """
276        Change name
277        """
278        if event is not None:
279            event.Skip()
280        self.name_tcl.SetBackgroundColour('white')
281        self.Refresh()
282
283    def check_name(self, event=None):
284        """
285        Check that proposed new model name is a valid Python module name
286        and that it does not already exist. If not show error message and
287        pink background in text box else call on_apply
288       
289        :TODO this should be separated out from the GUI code.  For that we
290        need to pass it the name (or if we want to keep the default name
291        option also need to pass the self._operator attribute) We just need
292        the function to return an error code that the name is good or if
293        not why (not a valid name, name exists already).  The rest of the
294        error handling should be done in this module. so on_apply would then
295        start by checking the name and then either raise errors or do the
296        deed.
297        """
298        #Get the function/file name
299        mname = M_NAME
300        self.on_change_name(None)
301        title = self.name_tcl.GetValue().lstrip().rstrip()
302        if title == '':
303            text = self._operator
304            if text.count('+') > 0:
305                mname = 'Sum'
306            else:
307                mname = 'Multi'
308            mname += M_NAME
309            title = mname
310        self.name = title
311        t_fname = title + '.py'
312
313        #First check if the name is a valid Python name
314        if re.match('^[A-Za-z0-9_]*$',title):
315            self.good_name = True
316        else: 
317            self.good_name = False
318            msg = ("%s is not a valid Python name. Only alphanumeric \n" \
319                "and underscore allowed" % self.name)
320 
321        #Now check if the name already exists
322        if not self.overwrite_name and self.good_name:
323            #Create list of existing model names for comparison
324            list_fnames = os.listdir(self.plugin_dir)
325            # fake existing regular model name list
326            m_list = [model + ".py" for model in self.model_list]
327            list_fnames.append(m_list)
328            if t_fname in list_fnames and title != mname:
329                self.good_name = False
330                msg = "Name exists already."
331
332        if self.good_name == False:
333            self.name_tcl.SetBackgroundColour('pink')
334            info = 'Error'
335            wx.MessageBox(msg, info)
336            self._notes = msg
337            color = 'red'
338            self._msg_box.SetLabel(msg)
339            self._msg_box.SetForegroundColour(color)
340            return self.good_name
341        self.fname = os.path.join(self.plugin_dir, t_fname)
342        s_title = title
343        if len(title) > 20:
344            s_title = title[0:19] + '...'
345        self._notes = "Model function (%s) has been set! \n" % str(s_title)
346        self.good_name = True
347        self.on_apply(self.fname)
348        return self.good_name
349
350    def on_apply(self, path):
351        """
352        On Apply
353        """
354        self.name_tcl.SetBackgroundColour('white')
355        try:
356            label = self.getText()
357            fname = path
358            name1 = label[0]
359            name2 = label[1]
360            self.write_string(fname, name1, name2)
361            self.compile_file(fname)
362            self.parent.update_custom_combo()
363            msg = self._notes
364            info = 'Info'
365            color = 'blue'
366        except:
367            msg = "Easy Custom Sum/Multipy: Error occurred..."
368            info = 'Error'
369            color = 'red'
370        self._msg_box.SetLabel(msg)
371        self._msg_box.SetForegroundColour(color)
372        if self.parent.parent != None:
373            from sas.guiframe.events import StatusEvent
374            wx.PostEvent(self.parent.parent, StatusEvent(status=msg,
375                                                      info=info))
376        else:
377            raise
378
379    def _set_model_list(self):
380        """
381        Set the list of models
382        """
383        # list of model names
384        cm_list = []
385        # models
386        list = self.model_list
387        # custom models
388        al_list = os.listdir(self.plugin_dir)
389        for c_name in al_list:
390            if c_name.split('.')[-1] == 'py' and \
391                    c_name.split('.')[0] != '__init__':
392                name = str(c_name.split('.')[0])
393                cm_list.append(name)
394                if name not in list:
395                    list.append(name)
396        self.cm_list = cm_list
397        if len(list) > 1:
398            list.sort()
399        for idx in range(len(list)):
400            self.model1.Append(str(list[idx]), idx)
401            self.model2.Append(str(list[idx]), idx)
402        self.model1.SetStringSelection(self.model1_string)
403        self.model2.SetStringSelection(self.model2_string)
404
405    def update_cm_list(self):
406        """
407        Update custom model list
408        """
409        cm_list = []
410        al_list = os.listdir(self.plugin_dir)
411        for c_name in al_list:
412            if c_name.split('.')[-1] == 'py' and \
413                    c_name.split('.')[0] != '__init__':
414                name = str(c_name.split('.')[0])
415                cm_list.append(name)
416        self.cm_list = cm_list
417
418    def on_model1(self, event):
419        """
420        Set model1
421        """
422        event.Skip()
423        self.update_cm_list()
424        self.model1_name = str(self.model1.GetValue())
425        self.model1_string = self.model1_name
426        if self.model1_name in self.cm_list:
427            self.is_p1_custom = True
428        else:
429            self.is_p1_custom = False
430
431    def on_model2(self, event):
432        """
433        Set model2
434        """
435        event.Skip()
436        self.update_cm_list()
437        self.model2_name = str(self.model2.GetValue())
438        self.model2_string = self.model2_name
439        if self.model2_name in self.cm_list:
440            self.is_p2_custom = True
441        else:
442            self.is_p2_custom = False
443
444    def on_select_operator(self, event=None):
445        """
446        On Select an Operator
447        """
448        # For Mac
449        if event != None:
450            event.Skip()
451        item = event.GetEventObject()
452        text = item.GetValue()
453        self.fill_explanation_helpstring(text)
454
455    def fill_explanation_helpstring(self, type):
456        """
457        Choose the equation to use depending on whether we now have
458        a sum or multiply model then create the appropriate string
459        """
460
461        name = ''
462
463        if type == '*':
464            name = 'Multi'
465            factor = 'BackGround'
466            f_oper = '+'
467        else:
468            name = 'Sum'
469            factor = 'scale_factor'
470            f_oper = '*'
471
472        self.factor = factor
473        self._operator = type
474        self.explanation = "  Custom Model = %s %s (model1 %s model2)\n" % \
475                    (self.factor, f_oper, self._operator)
476        self.explanationctr.SetLabel(self.explanation)
477        self.name = name + M_NAME
478
479
480    def fill_oprator_combox(self):
481        """
482        fill the current combobox with the operator
483        """
484        operator_list = [' +', ' *']
485        for oper in operator_list:
486            pos = self._operator_choice.Append(str(oper))
487            self._operator_choice.SetClientData(pos, str(oper.strip()))
488        self._operator_choice.SetSelection(0)
489
490    def getText(self):
491        """
492        Returns model name string as list
493        """
494        return [self.model1_name, self.model2_name]
495
496    def write_string(self, fname, name1, name2):
497        """
498        Write and Save file
499        """
500        self.fname = fname
501        description = self.desc_tcl.GetValue().lstrip().rstrip()
502        if description == '':
503            description = name1 + self._operator + name2
504        name = self.name_tcl.GetValue().lstrip().rstrip()
505        text = self._operator_choice.GetValue()
506        if text.count('+') > 0:
507            factor = 'scale_factor'
508            f_oper = '*'
509            default_val = '1.0'
510        else:
511            factor = 'BackGround'
512            f_oper = '+'
513            default_val = '0.0'
514        path = self.fname
515        try:
516            out_f = open(path, 'w')
517        except :
518            raise
519        lines = SUM_TEMPLATE.split('\n')
520        for line in lines:
521            try:
522                if line.count("scale_factor"):
523                    line = line.replace('scale_factor', factor)
524                    #print "scale_factor", line
525                if line.count("= %s"):
526                    out_f.write(line % (default_val) + "\n")
527                elif line.count("import Model as P1"):
528                    if self.is_p1_custom:
529                        line = line.replace('#', '')
530                        out_f.write(line % name1 + "\n")
531                    else:
532                        out_f.write(line + "\n")
533                elif line.count("import %s as P1"):
534                    if not self.is_p1_custom:
535                        line = line.replace('#', '')
536                        out_f.write(line % (name1, name1) + "\n")
537                    else:
538                        out_f.write(line + "\n")
539                elif line.count("import Model as P2"):
540                    if self.is_p2_custom:
541                        line = line.replace('#', '')
542                        out_f.write(line % name2 + "\n")
543                    else:
544                        out_f.write(line + "\n")
545                elif line.count("import %s as P2"):
546                    if not self.is_p2_custom:
547                        line = line.replace('#', '')
548                        out_f.write(line % (name2, name2) + "\n")
549                    else:
550                        out_f.write(line + "\n")
551                elif line.count("self.description = '%s'"):
552                    out_f.write(line % description + "\n")
553                #elif line.count("run") and line.count("%s"):
554                #    out_f.write(line % self._operator + "\n")
555                #elif line.count("evalDistribution") and line.count("%s"):
556                #    out_f.write(line % self._operator + "\n")
557                elif line.count("return") and line.count("%s") == 2:
558                    #print "line return", line
559                    out_f.write(line % (f_oper, self._operator) + "\n")
560                elif line.count("out2")and line.count("%s"):
561                    out_f.write(line % self._operator + "\n")
562                else:
563                    out_f.write(line + "\n")
564            except:
565                raise
566        out_f.close()
567        #else:
568        #    msg = "Name exists already."
569
570    def compile_file(self, path):
571        """
572        Compile the file in the path
573        """
574        path = self.fname
575        _compileFile(path)
576
577    def delete_file(self, path):
578        """
579        Delete file in the path
580        """
581        _deleteFile(path)
582
583
584class EditorPanel(wx.ScrolledWindow):
585    """
586    Custom model function editor
587    """
588    def __init__(self, parent, base, path, title, *args, **kwds):
589        kwds['name'] = title
590        kwds["size"] = (EDITOR_WIDTH, EDITOR_HEIGTH)
591        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
592        wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
593        #self.SetupScrolling()
594        self.parent = parent
595        self.base = base
596        self.path = path
597        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
598        self.font.SetPointSize(10)
599        self.reader = None
600        self.name = 'untitled'
601        self.overwrite_name = False
602        self.is_2d = False
603        self.fname = None
604        self.main_sizer = None
605        self.name_sizer = None
606        self.name_hsizer = None
607        self.desc_sizer = None
608        self.param_sizer = None
609        self.function_sizer = None
610        self.func_horizon_sizer = None
611        self.button_sizer = None
612        self.param_strings = ''
613        self.function_strings = ''
614        self._notes = ""
615        self._msg_box = None
616        self.msg_sizer = None
617        self.warning = ""
618        self._description = "New Custom Model"
619        self.function_tcl = None
620        #self._default_save_location = os.getcwd()
621        self._do_layout()
622        #self.bt_apply.Disable()
623
624
625    def _define_structure(self):
626        """
627        define initial sizer
628        """
629        #w, h = self.parent.GetSize()
630        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
631        self.name_sizer = wx.BoxSizer(wx.VERTICAL)
632        self.name_hsizer = wx.BoxSizer(wx.HORIZONTAL)
633        self.desc_sizer = wx.BoxSizer(wx.VERTICAL)
634        self.param_sizer = wx.BoxSizer(wx.VERTICAL)
635        self.function_sizer = wx.BoxSizer(wx.VERTICAL)
636        self.func_horizon_sizer = wx.BoxSizer(wx.HORIZONTAL)
637        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
638        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
639
640    def _layout_name(self):
641        """
642        Do the layout for file/function name related widgets
643        """
644        #title name [string]
645        name_txt = wx.StaticText(self, -1, 'Function Name : ')
646        overwrite_cb = wx.CheckBox(self, -1, "Overwrite?", (10, 10))
647        overwrite_cb.SetValue(False)
648        overwrite_cb.SetToolTipString("Overwrite it if already exists?")
649        wx.EVT_CHECKBOX(self, overwrite_cb.GetId(), self.on_over_cb)
650        #overwrite_cb.Show(False)
651        self.name_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1))
652        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
653        self.name_tcl.SetValue('MyFunction')
654        self.name_tcl.SetFont(self.font)
655        hint_name = "Unique Model Function Name."
656        self.name_tcl.SetToolTipString(hint_name)
657        self.name_hsizer.AddMany([(self.name_tcl, 0, wx.LEFT | wx.TOP, 0),
658                                       (overwrite_cb, 0, wx.LEFT, 20)])
659        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT | wx.TOP, 10),
660                                       (self.name_hsizer, 0,
661                                        wx.LEFT | wx.TOP | wx.BOTTOM, 10)])
662
663
664    def _layout_description(self):
665        """
666        Do the layout for description related widgets
667        """
668        #title name [string]
669        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')
670        self.desc_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH * 3 / 5, -1))
671        self.desc_tcl.SetValue('')
672        #self.name_tcl.SetFont(self.font)
673        hint_desc = "Write a short description of the model function."
674        self.desc_tcl.SetToolTipString(hint_desc)
675        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT | wx.TOP, 10),
676                                       (self.desc_tcl, 0,
677                                        wx.LEFT | wx.TOP | wx.BOTTOM, 10)])
678    def _layout_param(self):
679        """
680        Do the layout for parameter related widgets
681        """
682        param_txt = wx.StaticText(self, -1, 'Fit Parameters (if any): ')
683
684        param_tip = "#Set the parameters and initial values.\n"
685        param_tip += "#Example:\n"
686        param_tip += "A = 1\nB = 1"
687        #param_txt.SetToolTipString(param_tip)
688        id = wx.NewId()
689        self.param_tcl = EditWindow(self, id, wx.DefaultPosition,
690                            wx.DefaultSize, wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
691        self.param_tcl.setDisplayLineNumbers(True)
692        self.param_tcl.SetToolTipString(param_tip)
693
694        self.param_sizer.AddMany([(param_txt, 0, wx.LEFT, 10),
695                        (self.param_tcl, 1, wx.EXPAND | wx.ALL, 10)])
696
697    def _layout_function(self):
698        """
699        Do the layout for function related widgets
700        """
701        function_txt = wx.StaticText(self, -1, 'Function(x) : ')
702        hint_function = "#Example:\n"
703        hint_function += "if x <= 0:\n"
704        hint_function += "    y = A + B\n"
705        hint_function += "else:\n"
706        hint_function += "    y = A + B * cos(2 * pi * x)\n"
707        hint_function += "return y\n"
708        math_txt = wx.StaticText(self, -1, '*Useful math functions: ')
709        math_combo = self._fill_math_combo()
710
711        id = wx.NewId()
712        self.function_tcl = EditWindow(self, id, wx.DefaultPosition,
713                            wx.DefaultSize, wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
714        self.function_tcl.setDisplayLineNumbers(True)
715        self.function_tcl.SetToolTipString(hint_function)
716
717        self.func_horizon_sizer.Add(function_txt)
718        self.func_horizon_sizer.Add(math_txt, 0, wx.LEFT, 250)
719        self.func_horizon_sizer.Add(math_combo, 0, wx.LEFT, 10)
720
721        self.function_sizer.Add(self.func_horizon_sizer, 0, wx.LEFT, 10)
722        self.function_sizer.Add(self.function_tcl, 1, wx.EXPAND | wx.ALL, 10)
723
724    def _layout_msg(self):
725        """
726        Layout msg
727        """
728        self._msg_box = wx.StaticText(self, -1, self._notes,
729                                      size=(PANEL_WIDTH, -1))
730        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 10)
731
732    def _layout_button(self):
733        """
734        Do the layout for the button widgets
735        """
736        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH, -1))
737        self.bt_apply.SetToolTipString("Save changes into the imported data.")
738        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
739
740        self.bt_close = wx.Button(self, -1, 'Close', size=(_BOX_WIDTH, -1))
741        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
742        self.bt_close.SetToolTipString("Close this panel.")
743
744        self.button_sizer.AddMany([(self.bt_apply, 0,
745                                    wx.LEFT, EDITOR_WIDTH * 0.8),
746                                   (self.bt_close, 0,
747                                    wx.LEFT | wx.BOTTOM, 15)])
748
749    def _do_layout(self):
750        """
751        Draw the current panel
752        """
753        self._define_structure()
754        self._layout_name()
755        self._layout_description()
756        self._layout_param()
757        self._layout_function()
758        self._layout_msg()
759        self._layout_button()
760        self.main_sizer.AddMany([(self.name_sizer, 0,
761                                        wx.EXPAND | wx.ALL, 5),
762                                 (wx.StaticLine(self), 0,
763                                       wx.ALL | wx.EXPAND, 5),
764                                 (self.desc_sizer, 0,
765                                        wx.EXPAND | wx.ALL, 5),
766                                 (wx.StaticLine(self), 0,
767                                       wx.ALL | wx.EXPAND, 5),
768                                (self.param_sizer, 1,
769                                         wx.EXPAND | wx.ALL, 5),
770                                 (wx.StaticLine(self), 0,
771                                       wx.ALL | wx.EXPAND, 5),
772                                (self.function_sizer, 2,
773                                         wx.EXPAND | wx.ALL, 5),
774                                 (wx.StaticLine(self), 0,
775                                       wx.ALL | wx.EXPAND, 5),
776                                 (self.msg_sizer, 0,
777                                        wx.EXPAND | wx.ALL, 5),
778                                (self.button_sizer, 0,
779                                         wx.EXPAND | wx.ALL, 5)])
780        self.SetSizer(self.main_sizer)
781        self.SetAutoLayout(True)
782
783    def _fill_math_combo(self):
784        """
785        Fill up the math combo box
786        """
787        self.math_combo = wx.ComboBox(self, -1, size=(100, -1),
788                                      style=wx.CB_READONLY)
789        for item in dir(math):
790            if item.count("_") < 1:
791                try:
792                    exec "float(math.%s)" % item
793                    self.math_combo.Append(str(item))
794                except:
795                    self.math_combo.Append(str(item) + "()")
796        self.math_combo.Bind(wx.EVT_COMBOBOX, self._on_math_select)
797        self.math_combo.SetSelection(0)
798        return self.math_combo
799
800    def _on_math_select(self, event):
801        """
802        On math selection on ComboBox
803        """
804        event.Skip()
805        label = self.math_combo.GetLabel()
806        self.function_tcl.SetFocus()
807        # Put the text at the cursor position
808        pos = self.function_tcl.GetCurrentPos()
809        self.function_tcl.InsertText(pos, label)
810        # Put the cursor at appropriate postion
811        length = len(label)
812        if label[-1] == ')':
813            length -= 1
814        f_pos = pos + length
815        self.function_tcl.GotoPos(f_pos)
816
817    def get_notes(self):
818        """
819        return notes
820        """
821        return self._notes
822
823    def on_change_name(self, event=None):
824        """
825        Change name
826        """
827        if event is not None:
828            event.Skip()
829        self.name_tcl.SetBackgroundColour('white')
830        self.Refresh()
831
832    def check_name(self):
833        """
834        Check name if exist already
835        """
836        self._notes = ''
837        self.on_change_name(None)
838        plugin_dir = self.path
839        list_fnames = os.listdir(plugin_dir)
840        # function/file name
841        title = self.name_tcl.GetValue().lstrip().rstrip()
842        self.name = title
843        t_fname = title + '.py'
844        if not self.overwrite_name:
845            if t_fname in list_fnames:
846                self.name_tcl.SetBackgroundColour('pink')
847                return False
848        self.fname = os.path.join(plugin_dir, t_fname)
849        s_title = title
850        if len(title) > 20:
851            s_title = title[0:19] + '...'
852        self._notes += "Model function name is set "
853        self._notes += "to %s. \n" % str(s_title)
854        return True
855
856    def on_over_cb(self, event):
857        """
858        Set overwrite name flag on cb event
859        """
860        if event is not None:
861            event.Skip()
862        cb = event.GetEventObject()
863        self.overwrite_name = cb.GetValue()
864
865    def on_click_apply(self, event):
866        """
867        Changes are saved in data object imported to edit
868        """
869        #must post event here
870        event.Skip()
871        info = 'Info'
872        msg = ''
873        # Sort out the errors if occur
874        if self.check_name():
875            name = self.name_tcl.GetValue().lstrip().rstrip()
876            description = self.desc_tcl.GetValue()
877            param_str = self.param_tcl.GetText()
878            func_str = self.function_tcl.GetText()
879            # No input for the model function
880            if func_str.lstrip().rstrip():
881                if func_str.count('return') > 0:
882                    self.write_file(self.fname, description, param_str,
883                                                                    func_str)
884                    tr_msg = _compileFile(self.fname)
885                    msg = str(tr_msg.__str__())
886                    # Compile error
887                    if msg:
888                        msg.replace("  ", "\n")
889                        msg += "\nCompiling Failed"
890                else:
891                    msg = "Error: The func(x) must 'return' a value at least.\n"
892                    msg += "For example: \n\nreturn 2*x"
893            else:
894                msg = 'Error: Function is not defined.'
895        else:
896            msg = "Name exists already."
897        # Prepare for the messagebox
898        if self.base != None and not msg:
899            self.base.update_custom_combo()
900            Model = None
901            exec "from %s import Model" % name
902            try:
903                Model().run(0.01)
904            except:
905                msg = "Error "
906                _, value, _ = sys.exc_info()
907                msg += "in %s:\n%s\n" % (name, value)
908        if msg:
909            info = 'Error'
910            color = 'red'
911            try:
912                # try to remove pyc file if exists
913                _deleteFile(self.fname)
914                _deleteFile(self.fname + "c")
915            except:
916                pass
917        else:
918            msg = "Successful! "
919            msg += "  " + self._notes
920            msg += " Please look for it in the Customized Models."
921            info = 'Info'
922            color = 'blue'
923        # Not to display long error msg
924        if info == 'Error':
925            mss = info
926        else:
927            mss = msg
928        self._msg_box.SetLabel(mss)
929        self._msg_box.SetForegroundColour(color)
930        # Send msg to the top window 
931        if self.base != None:
932                from sas.guiframe.events import StatusEvent
933                wx.PostEvent(self.base.parent, StatusEvent(status=msg,
934                                                      info=info))
935        self.warning = msg
936
937
938    def write_file(self, fname, desc_str, param_str, func_str):
939        """
940        Write content in file
941
942        :param fname: full file path
943        :param desc_str: content of the description strings
944        :param param_str: content of params; Strings
945        :param func_str: content of func; Strings
946        """
947        try:
948            out_f = open(fname, 'w')
949        except :
950            raise
951        # Prepare the content of the function
952        lines = CUSTOM_TEMPLATE.split('\n')
953
954        has_scipy = func_str.count("scipy.")
955        self.is_2d = func_str.count("#self.ndim = 2")
956        line_2d = ''
957        if self.is_2d:
958            line_2d = CUSTOM_2D_TEMP.split('\n')
959        line_test = TEST_TEMPLATE.split('\n')
960        local_params = ''
961        spaces = '        '#8spaces
962        # write function here
963        for line in lines:
964            # The location where to put the strings is
965            # hard-coded in the template as shown below.
966            if line.count("#self.params here"):
967                for param_line in param_str.split('\n'):
968                    p_line = param_line.lstrip().rstrip()
969                    if p_line:
970                        p0_line = self.set_param_helper(p_line)
971                        local_params += self.set_function_helper(p_line)
972                        out_f.write(p0_line)
973            elif line.count("#local params here"):
974                if local_params:
975                    out_f.write(local_params)
976            elif line.count("self.description = "):
977                des0 = self.name + "\\n"
978                desc = str(desc_str.lstrip().rstrip().replace('\"', ''))
979                out_f.write(line % (des0 + desc) + "\n")
980            elif line.count("def function(self, x=0.0%s):"):
981                if self.is_2d:
982                    y_str = ', y=0.0'
983                    out_f.write(line % y_str + "\n")
984                else:
985                    out_f.write(line % '' + "\n")
986            elif line.count("#function here"):
987                for func_line in func_str.split('\n'):
988                    f_line = func_line.rstrip()
989                    if f_line:
990                        out_f.write(spaces + f_line + "\n")
991                if not func_str:
992                    dep_var = 'y'
993                    if self.is_2d:
994                        dep_var = 'z'
995                    out_f.write(spaces + 'return %s' % dep_var + "\n")
996            elif line.count("#import scipy?"):
997                if has_scipy:
998                    out_f.write("import scipy" + "\n")
999            #elif line.count("name = "):
1000            #    out_f.write(line % self.name + "\n")
1001            elif line:
1002                out_f.write(line + "\n")
1003        # run string for 2d
1004        if line_2d:
1005            for line in line_2d:
1006                out_f.write(line + "\n")
1007        # Test strins
1008        for line in line_test:
1009            out_f.write(line + "\n")
1010
1011        out_f.close()
1012
1013    def set_param_helper(self, line):
1014        """
1015        Get string in line to define the params dictionary
1016
1017        :param line: one line of string got from the param_str
1018        """
1019        flag = True
1020        params_str = ''
1021        spaces = '        '#8spaces
1022        items = line.split(";")
1023        for item in items:
1024            name = item.split("=")[0].lstrip().rstrip()
1025            try:
1026                value = item.split("=")[1].lstrip().rstrip()
1027                float(value)
1028            except:
1029                value = 1.0 # default
1030            params_str += spaces + "self.params['%s'] = %s\n" % (name, value)
1031
1032        return params_str
1033
1034    def set_function_helper(self, line):
1035        """
1036        Get string in line to define the local params
1037
1038        :param line: one line of string got from the param_str
1039        """
1040        flag = True
1041        params_str = ''
1042        spaces = '        '#8spaces
1043        items = line.split(";")
1044        for item in items:
1045            name = item.split("=")[0].lstrip().rstrip()
1046            params_str += spaces + "%s = self.params['%s']\n" % (name, name)
1047        return params_str
1048
1049    def get_warning(self):
1050        """
1051        Get the warning msg
1052        """
1053        return self.warning
1054
1055    def on_close(self, event):
1056        """
1057        leave data as it is and close
1058        """
1059        self.parent.Show(False)#Close()
1060        event.Skip()
1061
1062class EditorWindow(wx.Frame):
1063    """
1064    Editor Window
1065    """
1066    def __init__(self, parent, base, path, title,
1067                 size=(EDITOR_WIDTH, EDITOR_HEIGTH), *args, **kwds):
1068        """
1069        Init
1070        """
1071        kwds["title"] = title
1072        kwds["size"] = size
1073        wx.Frame.__init__(self, parent=None, *args, **kwds)
1074        self.parent = parent
1075        self.panel = EditorPanel(parent=self, base=parent,
1076                                 path=path, title=title)
1077        self.Show(True)
1078        wx.EVT_CLOSE(self, self.OnClose)
1079
1080    def OnClose(self, event):
1081        """
1082        On close event
1083        """
1084        self.Show(False)
1085        #if self.parent != None:
1086        #    self.parent.new_model_frame = None
1087        #self.Destroy() 
1088
1089## Templates for custom models
1090CUSTOM_TEMPLATE = """
1091from sas.models.pluginmodel import Model1DPlugin
1092from math import *
1093import os
1094import sys
1095import numpy
1096#import scipy?
1097class Model(Model1DPlugin):
1098    name = ""
1099    def __init__(self):
1100        Model1DPlugin.__init__(self, name=self.name)
1101        #set name same as file name
1102        self.name = self.get_fname()
1103        #self.params here
1104        self.description = "%s"
1105        self.set_details()
1106    def function(self, x=0.0%s):
1107        #local params here
1108        #function here
1109"""
1110CUSTOM_2D_TEMP = """
1111    def run(self, x=0.0, y=0.0):
1112        if x.__class__.__name__ == 'list':
1113            x_val = x[0]
1114            y_val = y[0]*0.0
1115            return self.function(x_val, y_val)
1116        elif x.__class__.__name__ == 'tuple':
1117            msg = "Tuples are not allowed as input to BaseComponent models"
1118            raise ValueError, msg
1119        else:
1120            return self.function(x, 0.0)
1121    def runXY(self, x=0.0, y=0.0):
1122        if x.__class__.__name__ == 'list':
1123            return self.function(x, y)
1124        elif x.__class__.__name__ == 'tuple':
1125            msg = "Tuples are not allowed as input to BaseComponent models"
1126            raise ValueError, msg
1127        else:
1128            return self.function(x, y)
1129    def evalDistribution(self, qdist):
1130        if qdist.__class__.__name__ == 'list':
1131            msg = "evalDistribution expects a list of 2 ndarrays"
1132            if len(qdist)!=2:
1133                raise RuntimeError, msg
1134            if qdist[0].__class__.__name__ != 'ndarray':
1135                raise RuntimeError, msg
1136            if qdist[1].__class__.__name__ != 'ndarray':
1137                raise RuntimeError, msg
1138            v_model = numpy.vectorize(self.runXY, otypes=[float])
1139            iq_array = v_model(qdist[0], qdist[1])
1140            return iq_array
1141        elif qdist.__class__.__name__ == 'ndarray':
1142            v_model = numpy.vectorize(self.runXY, otypes=[float])
1143            iq_array = v_model(qdist)
1144            return iq_array
1145"""
1146TEST_TEMPLATE = """
1147    def get_fname(self):
1148        path = sys._getframe().f_code.co_filename
1149        basename  = os.path.basename(path)
1150        name, _ = os.path.splitext(basename)
1151        return name
1152######################################################################
1153## THIS IS FOR TEST. DO NOT MODIFY THE FOLLOWING LINES!!!!!!!!!!!!!!!!
1154if __name__ == "__main__":
1155    m= Model()
1156    out1 = m.runXY(0.0)
1157    out2 = m.runXY(0.01)
1158    isfine1 = numpy.isfinite(out1)
1159    isfine2 = numpy.isfinite(out2)
1160    print "Testing the value at Q = 0.0:"
1161    print out1, " : finite? ", isfine1
1162    print "Testing the value at Q = 0.01:"
1163    print out2, " : finite? ", isfine2
1164    if isfine1 and isfine2:
1165        print "===> Simple Test: Passed!"
1166    else:
1167        print "===> Simple Test: Failed!"
1168"""
1169SUM_TEMPLATE = """
1170# A sample of an experimental model function for Sum/Multiply(Pmodel1,Pmodel2)
1171import copy
1172from sas.models.pluginmodel import Model1DPlugin
1173# User can change the name of the model (only with single functional model)
1174#P1_model:
1175#from sas.models.%s import %s as P1
1176#from %s import Model as P1
1177
1178#P2_model:
1179#from sas.models.%s import %s as P2
1180#from %s import Model as P2
1181import os
1182import sys
1183
1184class Model(Model1DPlugin):
1185    name = ""
1186    def __init__(self):
1187        Model1DPlugin.__init__(self, name='')
1188        p_model1 = P1()
1189        p_model2 = P2()
1190        ## Setting  model name model description
1191        self.description = '%s'
1192        self.name = self.get_fname()
1193        if self.name.rstrip().lstrip() == '':
1194            self.name = self._get_name(p_model1.name, p_model2.name)
1195        if self.description.rstrip().lstrip() == '':
1196            self.description = p_model1.name
1197            self.description += p_model2.name
1198            self.fill_description(p_model1, p_model2)
1199
1200        ## Define parameters
1201        self.params = {}
1202
1203        ## Parameter details [units, min, max]
1204        self.details = {}
1205        ## Magnetic Panrameters
1206        self.magnetic_params = []
1207        # non-fittable parameters
1208        self.non_fittable = p_model1.non_fittable
1209        self.non_fittable += p_model2.non_fittable
1210
1211        ##models
1212        self.p_model1= p_model1
1213        self.p_model2= p_model2
1214
1215
1216        ## dispersion
1217        self._set_dispersion()
1218        ## Define parameters
1219        self._set_params()
1220        ## New parameter:scaling_factor
1221        self.params['scale_factor'] = %s
1222
1223        ## Parameter details [units, min, max]
1224        self._set_details()
1225        self.details['scale_factor'] = ['', None, None]
1226
1227
1228        #list of parameter that can be fitted
1229        self._set_fixed_params()
1230        ## parameters with orientation
1231        for item in self.p_model1.orientation_params:
1232            new_item = "p1_" + item
1233            if not new_item in self.orientation_params:
1234                self.orientation_params.append(new_item)
1235
1236        for item in self.p_model2.orientation_params:
1237            new_item = "p2_" + item
1238            if not new_item in self.orientation_params:
1239                self.orientation_params.append(new_item)
1240        ## magnetic params
1241        for item in self.p_model1.magnetic_params:
1242            new_item = "p1_" + item
1243            if not new_item in self.magnetic_params:
1244                self.magnetic_params.append(new_item)
1245
1246        for item in self.p_model2.magnetic_params:
1247            new_item = "p2_" + item
1248            if not new_item in self.magnetic_params:
1249                self.magnetic_params.append(new_item)
1250        # get multiplicity if model provide it, else 1.
1251        try:
1252            multiplicity1 = p_model1.multiplicity
1253            try:
1254                multiplicity2 = p_model2.multiplicity
1255            except:
1256                multiplicity2 = 1
1257        except:
1258            multiplicity1 = 1
1259            multiplicity2 = 1
1260        ## functional multiplicity of the model
1261        self.multiplicity1 = multiplicity1
1262        self.multiplicity2 = multiplicity2
1263        self.multiplicity_info = []
1264
1265    def _clone(self, obj):
1266        obj.params     = copy.deepcopy(self.params)
1267        obj.description     = copy.deepcopy(self.description)
1268        obj.details    = copy.deepcopy(self.details)
1269        obj.dispersion = copy.deepcopy(self.dispersion)
1270        obj.p_model1  = self.p_model1.clone()
1271        obj.p_model2  = self.p_model2.clone()
1272        #obj = copy.deepcopy(self)
1273        return obj
1274
1275    def _get_name(self, name1, name2):
1276        p1_name = self._get_upper_name(name1)
1277        if not p1_name:
1278            p1_name = name1
1279        name = p1_name
1280        name += "_and_"
1281        p2_name = self._get_upper_name(name2)
1282        if not p2_name:
1283            p2_name = name2
1284        name += p2_name
1285        return name
1286
1287    def _get_upper_name(self, name=None):
1288        if name == None:
1289            return ""
1290        upper_name = ""
1291        str_name = str(name)
1292        for index in range(len(str_name)):
1293            if str_name[index].isupper():
1294                upper_name += str_name[index]
1295        return upper_name
1296
1297    def _set_dispersion(self):
1298        ##set dispersion only from p_model
1299        for name , value in self.p_model1.dispersion.iteritems():
1300            #if name.lower() not in self.p_model1.orientation_params:
1301            new_name = "p1_" + name
1302            self.dispersion[new_name]= value
1303        for name , value in self.p_model2.dispersion.iteritems():
1304            #if name.lower() not in self.p_model2.orientation_params:
1305            new_name = "p2_" + name
1306            self.dispersion[new_name]= value
1307
1308    def function(self, x=0.0):
1309        return 0
1310
1311    def getProfile(self):
1312        try:
1313            x,y = self.p_model1.getProfile()
1314        except:
1315            x = None
1316            y = None
1317
1318        return x, y
1319
1320    def _set_params(self):
1321        for name , value in self.p_model1.params.iteritems():
1322            # No 2D-supported
1323            #if name not in self.p_model1.orientation_params:
1324            new_name = "p1_" + name
1325            self.params[new_name]= value
1326
1327        for name , value in self.p_model2.params.iteritems():
1328            # No 2D-supported
1329            #if name not in self.p_model2.orientation_params:
1330            new_name = "p2_" + name
1331            self.params[new_name]= value
1332
1333        # Set "scale" as initializing
1334        self._set_scale_factor()
1335
1336
1337    def _set_details(self):
1338        for name ,detail in self.p_model1.details.iteritems():
1339            new_name = "p1_" + name
1340            #if new_name not in self.orientation_params:
1341            self.details[new_name]= detail
1342
1343        for name ,detail in self.p_model2.details.iteritems():
1344            new_name = "p2_" + name
1345            #if new_name not in self.orientation_params:
1346            self.details[new_name]= detail
1347
1348    def _set_scale_factor(self):
1349        pass
1350
1351
1352    def setParam(self, name, value):
1353        # set param to this (p1, p2) model
1354        self._setParamHelper(name, value)
1355
1356        ## setParam to p model
1357        model_pre = ''
1358        new_name = ''
1359        name_split = name.split('_', 1)
1360        if len(name_split) == 2:
1361            model_pre = name.split('_', 1)[0]
1362            new_name = name.split('_', 1)[1]
1363        if model_pre == "p1":
1364            if new_name in self.p_model1.getParamList():
1365                self.p_model1.setParam(new_name, value)
1366        elif model_pre == "p2":
1367             if new_name in self.p_model2.getParamList():
1368                self.p_model2.setParam(new_name, value)
1369        elif name == 'scale_factor':
1370            self.params['scale_factor'] = value
1371        else:
1372            raise ValueError, "Model does not contain parameter %s" % name
1373
1374    def getParam(self, name):
1375        # Look for dispersion parameters
1376        toks = name.split('.')
1377        if len(toks)==2:
1378            for item in self.dispersion.keys():
1379                # 2D not supported
1380                if item.lower()==toks[0].lower():
1381                    for par in self.dispersion[item]:
1382                        if par.lower() == toks[1].lower():
1383                            return self.dispersion[item][par]
1384        else:
1385            # Look for standard parameter
1386            for item in self.params.keys():
1387                if item.lower()==name.lower():
1388                    return self.params[item]
1389        return
1390        #raise ValueError, "Model does not contain parameter %s" % name
1391
1392    def _setParamHelper(self, name, value):
1393        # Look for dispersion parameters
1394        toks = name.split('.')
1395        if len(toks)== 2:
1396            for item in self.dispersion.keys():
1397                if item.lower()== toks[0].lower():
1398                    for par in self.dispersion[item]:
1399                        if par.lower() == toks[1].lower():
1400                            self.dispersion[item][par] = value
1401                            return
1402        else:
1403            # Look for standard parameter
1404            for item in self.params.keys():
1405                if item.lower()== name.lower():
1406                    self.params[item] = value
1407                    return
1408
1409        raise ValueError, "Model does not contain parameter %s" % name
1410
1411
1412    def _set_fixed_params(self):
1413        for item in self.p_model1.fixed:
1414            new_item = "p1" + item
1415            self.fixed.append(new_item)
1416        for item in self.p_model2.fixed:
1417            new_item = "p2" + item
1418            self.fixed.append(new_item)
1419
1420        self.fixed.sort()
1421
1422
1423    def run(self, x = 0.0):
1424        self._set_scale_factor()
1425        return self.params['scale_factor'] %s \
1426(self.p_model1.run(x) %s self.p_model2.run(x))
1427
1428    def runXY(self, x = 0.0):
1429        self._set_scale_factor()
1430        return self.params['scale_factor'] %s \
1431(self.p_model1.runXY(x) %s self.p_model2.runXY(x))
1432
1433    ## Now (May27,10) directly uses the model eval function
1434    ## instead of the for-loop in Base Component.
1435    def evalDistribution(self, x = []):
1436        self._set_scale_factor()
1437        return self.params['scale_factor'] %s \
1438(self.p_model1.evalDistribution(x) %s \
1439self.p_model2.evalDistribution(x))
1440
1441    def set_dispersion(self, parameter, dispersion):
1442        value= None
1443        new_pre = parameter.split("_", 1)[0]
1444        new_parameter = parameter.split("_", 1)[1]
1445        try:
1446            if new_pre == 'p1' and \
1447new_parameter in self.p_model1.dispersion.keys():
1448                value= self.p_model1.set_dispersion(new_parameter, dispersion)
1449            if new_pre == 'p2' and \
1450new_parameter in self.p_model2.dispersion.keys():
1451                value= self.p_model2.set_dispersion(new_parameter, dispersion)
1452            self._set_dispersion()
1453            return value
1454        except:
1455            raise
1456
1457    def fill_description(self, p_model1, p_model2):
1458        description = ""
1459        description += "This model gives the summation or multiplication of"
1460        description += "%s and %s. "% ( p_model1.name, p_model2.name )
1461        self.description += description
1462
1463    def get_fname(self):
1464        path = sys._getframe().f_code.co_filename
1465        basename  = os.path.basename(path)
1466        name, _ = os.path.splitext(basename)
1467        return name
1468
1469if __name__ == "__main__":
1470    m1= Model()
1471    #m1.setParam("p1_scale", 25)
1472    #m1.setParam("p1_length", 1000)
1473    #m1.setParam("p2_scale", 100)
1474    #m1.setParam("p2_rg", 100)
1475    out1 = m1.runXY(0.01)
1476
1477    m2= Model()
1478    #m2.p_model1.setParam("scale", 25)
1479    #m2.p_model1.setParam("length", 1000)
1480    #m2.p_model2.setParam("scale", 100)
1481    #m2.p_model2.setParam("rg", 100)
1482    out2 = m2.p_model1.runXY(0.01) %s m2.p_model2.runXY(0.01)\n
1483    print "My name is %s."% m1.name
1484    print out1, " = ", out2
1485    if out1 == out2:
1486        print "===> Simple Test: Passed!"
1487    else:
1488        print "===> Simple Test: Failed!"
1489"""
1490
1491if __name__ == "__main__": 
1492#    app = wx.PySimpleApp()
1493    app = wx.App()
1494    frame = TextDialog(id=1, model_list=["SphereModel", "CylinderModel"],plugin_dir='../fitting/plugin_models')   
1495#    frame = wx.Dialog()
1496    frame.ShowModal()
1497#    frame.Show(True)
1498    app.MainLoop()             
1499
1500#if __name__ == "__main__":
1501#    from sas.perspectives.fitting import models
1502#    dir_path = models.find_plugins_dir()
1503#    app = wx.App()
1504#    window = EditorWindow(parent=None, base=None, path=dir_path, title="Editor")
1505#    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.