source: sasview/src/sas/perspectives/calculator/model_editor.py @ 892a2cc

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 892a2cc was 2d50115, checked in by butler, 10 years ago

Fixed problem of scrolling window on easy custom model editor. Also
fixed several bugs and cleaned up pylint errors.

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