source: sasview/src/sas/perspectives/calculator/model_editor.py @ 4a5fedc

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 4a5fedc was ac7be54, checked in by Paul Kienzle <pkienzle@…>, 10 years ago

fix sphinx errors in api manual

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