source: sasview/src/sas/perspectives/calculator/model_editor.py @ 7801df8

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

Clean up minor errors in previous commits and add General Fitting Help
and button sizer and even handler for smearing help (but still need to
add button to panel so can be used.

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