source: sasview/calculatorview/src/sans/perspectives/calculator/model_editor.py @ 19b9e43

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 19b9e43 was 19b9e43, checked in by Jae Cho <jhjcho@…>, 12 years ago

trying to fix mac sum panel problem

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