source: sasview/src/sas/perspectives/calculator/model_editor.py @ 18ac46b

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 18ac46b was 18ac46b, checked in by butler, 9 years ago

fixed error handling on new (easy custom editor) and cleaned up some of
the code therein but could use with refactoring as done for composite
model editor.

Also fixed a number of pyling errors.

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