source: sasview/calculatorview/src/sans/perspectives/calculator/model_editor.py @ 0203ade

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 0203ade was 657e52c, checked in by Jae Cho <jhjcho@…>, 12 years ago

merging from the release 2.2.0

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