Index: src/sas/perspectives/calculator/model_editor.py
===================================================================
--- src/sas/perspectives/calculator/model_editor.py	(revision 794922220f2fb3b23dd08c648aabf87b4850596c)
+++ src/sas/perspectives/calculator/model_editor.py	(revision 794922220f2fb3b23dd08c648aabf87b4850596c)
@@ -0,0 +1,1415 @@
+################################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#
+#See the license text in license.txt
+#
+#copyright 2009, University of Tennessee
+################################################################################
+import wx
+import sys
+import os
+import math
+from wx.py.editwindow import EditWindow
+
+if sys.platform.count("win32") > 0:
+    FONT_VARIANT = 0
+    PNL_WIDTH = 450
+    PNL_HITE = 320
+else:
+    FONT_VARIANT = 1
+    PNL_WIDTH = 590
+    PNL_HITE = 350
+M_NAME = 'Model'
+EDITOR_WIDTH = 800
+EDITOR_HEIGTH = 735
+PANEL_WIDTH = 500
+_BOX_WIDTH = 55
+
+    
+def _compileFile(path):
+    """
+    Compile the file in the path
+    """
+    try:
+        import py_compile
+        py_compile.compile(file=path, doraise=True)
+        return ''
+    except:
+        _, value, _ = sys.exc_info()
+        return value
+    
+def _deleteFile(path):
+    """
+    Delete file in the path
+    """
+    try:
+        os.remove(path)
+    except:
+        raise
+
+ 
+class TextDialog(wx.Dialog):
+    """
+    Dialog for easy custom sum models  
+    """
+    def __init__(self, parent=None, base=None, id=None, title='', 
+                 model_list=[], plugin_dir=None):
+        """
+        Dialog window popup when selecting 'Easy Custom Sum/Multiply' 
+        on the menu
+        """
+        wx.Dialog.__init__(self, parent=parent, id=id, 
+                           title=title, size=(PNL_WIDTH, PNL_HITE))
+        self.parent = base
+        #Font
+        self.SetWindowVariant(variant=FONT_VARIANT)
+        # default
+        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
+        self.font.SetPointSize(10)
+        self.overwrite_name = False
+        self.plugin_dir = plugin_dir
+        self.model_list = model_list
+        self.model1_string = "SphereModel"
+        self.model2_string = "CylinderModel"
+        self.name = 'Sum' + M_NAME
+        self.factor = 'scale_factor'
+        self._notes = ''
+        self.operator = '+'
+        self.operator_cbox = None
+        self.explanation = ''
+        self.explanationctr = None
+        self.sizer = None
+        self.name_sizer = None
+        self.name_hsizer = None
+        self.desc_sizer = None
+        self.desc_tcl = None
+        self.model1 = None
+        self.model2 = None
+        self.static_line_1 = None
+        self.okButton = None
+        self.closeButton = None
+        self._msg_box = None
+        self.msg_sizer = None
+        self.fname = None
+        self.cm_list = None
+        self.is_p1_custom = False
+        self.is_p2_custom = False
+        self._build_sizer()
+        self.model1_name = str(self.model1.GetValue())
+        self.model2_name = str(self.model2.GetValue())
+        self.good_name = True
+        self.fill_oprator_combox()
+        
+    def _layout_name(self):
+        """
+        Do the layout for file/function name related widgets
+        """
+        self.name_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.name_hsizer = wx.BoxSizer(wx.HORIZONTAL)
+        #title name [string]
+        name_txt = wx.StaticText(self, -1, 'Function Name : ')  
+        self.name_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
+        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
+        self.name_tcl.SetValue('')
+        self.name_tcl.SetFont(self.font)
+        hint_name = "Unique Sum/Multiply Model Function Name."
+        self.name_tcl.SetToolTipString(hint_name)
+        self.name_hsizer.AddMany([(name_txt, 0, wx.LEFT|wx.TOP, 10),
+                            (self.name_tcl, -1, 
+                             wx.EXPAND|wx.RIGHT|wx.TOP|wx.BOTTOM, 10)])
+        self.name_sizer.AddMany([(self.name_hsizer, -1, 
+                                        wx.LEFT|wx.TOP, 10)])
+        
+        
+    def _layout_description(self):
+        """
+        Do the layout for description related widgets
+        """
+        self.desc_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        #title name [string]
+        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')  
+        self.desc_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
+        self.desc_tcl.SetValue('')
+        #self.name_tcl.SetFont(self.font)
+        hint_desc = "Write a short description of this model function."
+        self.desc_tcl.SetToolTipString(hint_desc)
+        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT|wx.TOP, 10),
+                                (self.desc_tcl, -1, 
+                                wx.EXPAND|wx.RIGHT|wx.TOP|wx.BOTTOM, 10)])     
+  
+    def _build_sizer(self):
+        """
+        Build gui
+        """
+        box_width = 195 # combobox width
+        vbox  = wx.BoxSizer(wx.VERTICAL)
+        self.sizer = wx.GridBagSizer(1, 3)
+        self._layout_name()
+        self._layout_description()
+        
+        
+        sum_description = wx.StaticBox(self, -1, 'Select', 
+                                       size=(PNL_WIDTH-30, 70))
+        sum_box = wx.StaticBoxSizer(sum_description, wx.VERTICAL)
+        model1_box = wx.BoxSizer(wx.HORIZONTAL)
+        model2_box = wx.BoxSizer(wx.HORIZONTAL)
+        model_vbox = wx.BoxSizer(wx.VERTICAL)
+        self.model1 =  wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.model1, -1, self.on_model1)
+        self.model1.SetMinSize((box_width*5/6, -1))
+        self.model1.SetToolTipString("model1")
+        
+        self.operator_cbox = wx.ComboBox(self, -1, size=(50, -1), 
+                                         style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.operator_cbox, -1, self.on_select_operator)
+        operation_tip = "Add: +, Multiply: * "
+        self.operator_cbox.SetToolTipString(operation_tip)
+        
+        self.model2 =  wx.ComboBox(self, -1, style=wx.CB_READONLY)
+        wx.EVT_COMBOBOX(self.model2, -1, self.on_model2)
+        self.model2.SetMinSize((box_width*5/6, -1))
+        self.model2.SetToolTipString("model2")
+        self._set_model_list()
+        
+         # Buttons on the bottom
+        self.static_line_1 = wx.StaticLine(self, -1)
+        self.okButton = wx.Button(self,wx.ID_OK, 'Apply', size=(box_width/2, 25))
+        self.okButton.Bind(wx.EVT_BUTTON, self.check_name)
+        self.closeButton = wx.Button(self,wx.ID_CANCEL, 'Close', 
+                                     size=(box_width/2, 25))
+        # Intro
+        self.explanation  = "  custom model = %s %s "% (self.factor, '*')
+        self.explanation  += "(model1 %s model2)\n"% self.operator
+        #explanation  += "  Note: This will overwrite the previous sum model.\n"
+        model_string = " Model%s (p%s):"
+        # msg
+        self._msg_box = wx.StaticText(self, -1, self._notes)
+        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 0)
+        vbox.Add(self.name_hsizer)
+        vbox.Add(self.desc_sizer)
+        vbox.Add(self.sizer)
+        ix = 0
+        iy = 1
+        self.explanationctr = wx.StaticText(self, -1, self.explanation)
+        self.sizer.Add(self.explanationctr , (iy, ix),
+                 (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        model1_box.Add(wx.StaticText(self, -1, model_string% (1, 1)), -1, 0)
+        model1_box.Add((box_width-15, 10))
+        model1_box.Add(wx.StaticText(self, -1, model_string% (2, 2)), -1, 0)
+        model2_box.Add(self.model1, -1, 0)
+        model2_box.Add((15, 10))
+        model2_box.Add(self.operator_cbox, 0, 0)
+        model2_box.Add((15, 10))
+        model2_box.Add(self.model2, -1, 0)
+        model_vbox.Add(model1_box, -1, 0)
+        model_vbox.Add(model2_box, -1, 0)
+        sum_box.Add(model_vbox, -1, 10)
+        iy += 1
+        ix = 0
+        self.sizer.Add(sum_box, (iy, ix),
+                  (1, 1), wx.LEFT|wx.EXPAND|wx.ADJUST_MINSIZE, 15)
+        vbox.Add((10, 10))
+        vbox.Add(self.static_line_1, 0, wx.EXPAND, 10)
+        vbox.Add(self.msg_sizer, 0, 
+                 wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE|wx.BOTTOM, 10)
+        sizer_button = wx.BoxSizer(wx.HORIZONTAL)
+        sizer_button.Add((20, 20), 1, wx.EXPAND|wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(self.okButton, 0, 
+                         wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 0)
+        sizer_button.Add(self.closeButton, 0,
+                          wx.LEFT|wx.RIGHT|wx.ADJUST_MINSIZE, 10)        
+        vbox.Add(sizer_button, 0, wx.EXPAND|wx.BOTTOM|wx.TOP, 10)
+         
+        self.SetSizer(vbox)
+        self.Centre()
+        
+    def on_change_name(self, event=None):
+        """
+        Change name
+        """
+        if event is not None:
+            event.Skip()
+        self.name_tcl.SetBackgroundColour('white')
+        self.Refresh()
+    
+    def check_name(self, event=None):
+        """
+        Check name if exist already
+        """
+        mname = M_NAME
+        self.on_change_name(None)
+        list_fnames = os.listdir(self.plugin_dir)
+        # fake existing regular model name list
+        m_list = [model + ".py" for model in self.model_list]
+        list_fnames.append(m_list)
+        # function/file name
+        title = self.name_tcl.GetValue().lstrip().rstrip()
+        if title == '':
+            text = self.operator
+            if text.count('+') > 0:
+                mname = 'Sum'
+            else:
+                mname = 'Multi'
+            mname += M_NAME
+            title = mname
+        self.name = title
+        t_fname = title + '.py'
+        if not self.overwrite_name:
+            if t_fname in list_fnames and title != mname:
+                self.name_tcl.SetBackgroundColour('pink')
+                self.good_name = False
+                info = 'Error'
+                msg = "Name exists already."
+                wx.MessageBox(msg, info)  
+                self._notes = msg
+                color = 'red'
+                self._msg_box.SetLabel(msg)
+                self._msg_box.SetForegroundColour(color)
+                return self.good_name
+        self.fname = os.path.join(self.plugin_dir, t_fname)
+        s_title = title
+        if len(title) > 20:
+            s_title = title[0:19] + '...'
+        self._notes = "Model function (%s) has been set! \n" % str(s_title)
+        self.good_name = True
+        self.on_apply(self.fname)
+        return self.good_name
+    
+    def on_apply(self, path):
+        """
+        On Apply
+        """
+        try:
+            label = self.getText()
+            fname = path
+            name1 = label[0]
+            name2 = label[1]
+            self.write_string(fname, name1, name2)
+            self.compile_file(fname)
+            self.parent.update_custom_combo()
+            msg = self._notes
+            info = 'Info'
+            color = 'blue'
+        except:
+            msg= "Easy Custom Sum/Multipy: Error occurred..."
+            info = 'Error'
+            color = 'red'
+        self._msg_box.SetLabel(msg)
+        self._msg_box.SetForegroundColour(color)
+        if self.parent.parent != None:
+            from sas.guiframe.events import StatusEvent 
+            wx.PostEvent(self.parent.parent, StatusEvent(status = msg, 
+                                                      info=info))
+        else:
+            raise
+                  
+    def _set_model_list(self):
+        """
+        Set the list of models
+        """
+        # list of model names
+        cm_list = []
+        # models
+        list = self.model_list
+        # custom models
+        al_list = os.listdir(self.plugin_dir)
+        for c_name in al_list:
+            if c_name.split('.')[-1] == 'py' and \
+                    c_name.split('.')[0] != '__init__':
+                name = str(c_name.split('.')[0])
+                cm_list.append(name)
+                if name not in list:
+                    list.append(name)
+        self.cm_list = cm_list
+        if len(list) > 1:
+            list.sort()
+        for idx in range(len(list)):
+            self.model1.Append(str(list[idx]), idx) 
+            self.model2.Append(str(list[idx]), idx)
+        self.model1.SetStringSelection(self.model1_string)
+        self.model2.SetStringSelection(self.model2_string)
+    
+    def update_cm_list(self):
+        """
+        Update custom model list
+        """
+        cm_list = []
+        al_list = os.listdir(self.plugin_dir)
+        for c_name in al_list:
+            if c_name.split('.')[-1] == 'py' and \
+                    c_name.split('.')[0] != '__init__':
+                name = str(c_name.split('.')[0])
+                cm_list.append(name)
+        self.cm_list = cm_list 
+              
+    def on_model1(self, event):
+        """
+        Set model1
+        """
+        event.Skip()
+        self.update_cm_list()
+        self.model1_name = str(self.model1.GetValue())
+        self.model1_string = self.model1_name
+        if self.model1_name in self.cm_list:
+            self.is_p1_custom = True
+        else:
+            self.is_p1_custom = False
+            
+    def on_model2(self, event):
+        """
+        Set model2
+        """
+        event.Skip()
+        self.update_cm_list()
+        self.model2_name = str(self.model2.GetValue())
+        self.model2_string = self.model2_name
+        if self.model2_name in self.cm_list:
+            self.is_p2_custom = True
+        else:
+            self.is_p2_custom = False
+        
+    def on_select_operator(self, event=None):
+        """
+        On Select an Operator
+        """
+        # For Mac
+        if event != None:
+            event.Skip()
+        name = ''    
+        item = event.GetEventObject()
+        text = item.GetValue()
+        if text.count('*') > 0:
+            name = 'Multi'
+            factor = 'BackGround'
+            f_oper = '+'
+        else:
+            name = 'Sum'
+            factor = 'scale_factor'
+            f_oper = '*'
+
+        self.factor = factor
+        self.operator = text
+        self.explanation = "  Custom Model = %s %s (model1 %s model2)\n"% \
+                    (self.factor, f_oper, self.operator)
+        self.explanationctr.SetLabel(self.explanation)
+        self.name = name + M_NAME 
+        self.sizer.Layout()
+             
+    def fill_oprator_combox(self):
+        """
+        fill the current combobox with the operator
+        """   
+        operator_list = [' +', ' *']
+        for oper in operator_list:
+            pos = self.operator_cbox.Append(str(oper))
+            self.operator_cbox.SetClientData(pos, str(oper.strip()))
+        self.operator_cbox.SetSelection(0)
+            
+    def getText(self):
+        """
+        Returns model name string as list
+        """
+        return [self.model1_name, self.model2_name]
+    
+    def write_string(self, fname, name1, name2):
+        """
+        Write and Save file
+        """
+        self.fname = fname  
+        description = self.desc_tcl.GetValue().lstrip().rstrip()
+        if description == '':
+            description = name1 + self.operator + name2
+        name = self.name_tcl.GetValue().lstrip().rstrip()
+        text = self.operator_cbox.GetValue()
+        if text.count('+') > 0:
+            factor = 'scale_factor'
+            f_oper = '*'
+            default_val = '1.0'
+        else:
+            factor = 'BackGround'
+            f_oper = '+'
+            default_val = '0.0'
+        path = self.fname
+        try:
+            out_f =  open(path,'w')
+        except :
+            raise
+        lines = SUM_TEMPLATE.split('\n')
+        for line in lines:
+            try:
+                if line.count("scale_factor"):
+                    line = line.replace('scale_factor', factor)
+                    #print "scale_factor", line
+                if line.count("= %s"):
+                    out_f.write(line % (default_val) + "\n")
+                elif line.count("import Model as P1"):
+                    if self.is_p1_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % name1 + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("import %s as P1"):
+                    if not self.is_p1_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % (name1, name1) + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("import Model as P2"):
+                    if self.is_p2_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % name2 + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("import %s as P2"):
+                    if not self.is_p2_custom:
+                        line = line.replace('#', '')
+                        out_f.write(line % (name2, name2) + "\n")
+                    else:
+                        out_f.write(line + "\n")
+                elif line.count("self.description = '%s'"):
+                    out_f.write(line % description + "\n")
+                #elif line.count("run") and line.count("%s"):
+                #    out_f.write(line % self.operator + "\n")
+                #elif line.count("evalDistribution") and line.count("%s"):
+                #    out_f.write(line % self.operator + "\n")
+                elif line.count("return") and line.count("%s") == 2:
+                    #print "line return", line
+                    out_f.write(line % (f_oper, self.operator) + "\n")
+                elif line.count("out2")and line.count("%s"):
+                    out_f.write(line % self.operator + "\n")
+                else:
+                    out_f.write(line + "\n")
+            except:
+                raise
+        out_f.close()
+        #else:
+        #    msg = "Name exists already."
+        
+    def compile_file(self, path):
+        """
+        Compile the file in the path
+        """
+        path = self.fname
+        _compileFile(path)
+        
+    def delete_file(self, path):
+        """
+        Delete file in the path
+        """
+        _deleteFile(path)
+
+
+class EditorPanel(wx.ScrolledWindow):
+    """
+    Custom model function editor
+    """
+    def __init__(self, parent, base, path, title, *args, **kwds):
+        kwds['name'] = title
+        kwds["size"] = (EDITOR_WIDTH, EDITOR_HEIGTH)
+        kwds["style"] = wx.FULL_REPAINT_ON_RESIZE
+        wx.ScrolledWindow.__init__(self, parent, *args, **kwds)
+        #self.SetupScrolling()
+        self.parent = parent
+        self.base = base
+        self.path = path
+        self.font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
+        self.font.SetPointSize(10)
+        self.reader = None
+        self.name = 'untitled'
+        self.overwrite_name = False
+        self.is_2d = False
+        self.fname = None
+        self.param_strings = ''
+        self.function_strings = ''
+        self._notes = ""
+        self._msg_box = None
+        self.msg_sizer = None
+        self.warning = ""
+        self._description = "New Custom Model"
+        self.function_tcl = None
+        #self._default_save_location = os.getcwd()
+        self._do_layout()
+        #self.bt_apply.Disable()
+
+             
+    def _define_structure(self):
+        """
+        define initial sizer 
+        """
+        #w, h = self.parent.GetSize()
+        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.name_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.name_hsizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.desc_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.param_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.function_sizer = wx.BoxSizer(wx.VERTICAL)
+        self.func_horizon_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        self.msg_sizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+    def _layout_name(self):
+        """
+        Do the layout for file/function name related widgets
+        """
+        #title name [string]
+        name_txt = wx.StaticText(self, -1, 'Function Name : ')  
+        overwrite_cb = wx.CheckBox(self, -1, "Overwrite?", (10, 10))
+        overwrite_cb.SetValue(False)
+        overwrite_cb.SetToolTipString("Overwrite it if already exists?")
+        wx.EVT_CHECKBOX(self, overwrite_cb.GetId(), self.on_over_cb)
+        #overwrite_cb.Show(False)
+        self.name_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
+        self.name_tcl.Bind(wx.EVT_TEXT_ENTER, self.on_change_name)
+        self.name_tcl.SetValue('MyFunction')
+        self.name_tcl.SetFont(self.font)
+        hint_name = "Unique Model Function Name."
+        self.name_tcl.SetToolTipString(hint_name)
+        self.name_hsizer.AddMany([(self.name_tcl, 0, wx.LEFT|wx.TOP, 0),
+                                       (overwrite_cb, 0, wx.LEFT, 20)])
+        self.name_sizer.AddMany([(name_txt, 0, wx.LEFT|wx.TOP, 10),
+                                       (self.name_hsizer, 0, 
+                                        wx.LEFT|wx.TOP|wx.BOTTOM, 10)])
+        
+        
+    def _layout_description(self):
+        """
+        Do the layout for description related widgets
+        """
+        #title name [string]
+        desc_txt = wx.StaticText(self, -1, 'Description (optional) : ')  
+        self.desc_tcl = wx.TextCtrl(self, -1, size=(PANEL_WIDTH*3/5, -1)) 
+        self.desc_tcl.SetValue('')
+        #self.name_tcl.SetFont(self.font)
+        hint_desc = "Write a short description of the model function."
+        self.desc_tcl.SetToolTipString(hint_desc)
+        self.desc_sizer.AddMany([(desc_txt, 0, wx.LEFT|wx.TOP, 10),
+                                       (self.desc_tcl, 0, 
+                                        wx.LEFT|wx.TOP|wx.BOTTOM, 10)])     
+    def _layout_param(self):
+        """
+        Do the layout for parameter related widgets
+        """
+        param_txt = wx.StaticText(self, -1, 'Fit Parameters (if any): ') 
+        
+        param_tip = "#Set the parameters and initial values.\n"
+        param_tip += "#Example:\n"
+        param_tip += "A = 1\nB = 1"
+        #param_txt.SetToolTipString(param_tip)
+        id  = wx.NewId() 
+        self.param_tcl = EditWindow(self, id, wx.DefaultPosition, 
+                            wx.DefaultSize, wx.CLIP_CHILDREN|wx.SUNKEN_BORDER)
+        self.param_tcl.setDisplayLineNumbers(True)
+        self.param_tcl.SetToolTipString(param_tip)
+
+        self.param_sizer.AddMany([(param_txt, 0, wx.LEFT, 10),
+                        (self.param_tcl, 1, wx.EXPAND|wx.ALL, 10)])
+                
+    def _layout_function(self):
+        """
+        Do the layout for function related widgets
+        """
+        function_txt = wx.StaticText(self, -1, 'Function(x) : ') 
+        hint_function = "#Example:\n"
+        hint_function += "if x <= 0:\n"
+        hint_function += "    y = A + B\n"
+        hint_function += "else:\n"
+        hint_function += "    y = A + B * cos(2 * pi * x)\n"
+        hint_function += "return y\n"
+        math_txt = wx.StaticText(self, -1, '*Useful math functions: ')
+        math_combo = self._fill_math_combo()
+        
+        id  = wx.NewId() 
+        self.function_tcl = EditWindow(self, id, wx.DefaultPosition, 
+                            wx.DefaultSize, wx.CLIP_CHILDREN|wx.SUNKEN_BORDER)
+        self.function_tcl.setDisplayLineNumbers(True)
+        self.function_tcl.SetToolTipString(hint_function)
+        
+        self.func_horizon_sizer.Add(function_txt)
+        self.func_horizon_sizer.Add(math_txt, 0, wx.LEFT, 250)
+        self.func_horizon_sizer.Add(math_combo, 0, wx.LEFT, 10)
+
+        self.function_sizer.Add(self.func_horizon_sizer, 0, wx.LEFT, 10)
+        self.function_sizer.Add( self.function_tcl, 1, wx.EXPAND|wx.ALL, 10)
+        
+    def _layout_msg(self):
+        """
+        Layout msg
+        """
+        self._msg_box = wx.StaticText(self, -1, self._notes, 
+                                      size=(PANEL_WIDTH, -1))
+        self.msg_sizer.Add(self._msg_box, 0, wx.LEFT, 10)  
+                    
+    def _layout_button(self):  
+        """
+        Do the layout for the button widgets
+        """         
+        self.bt_apply = wx.Button(self, -1, "Apply", size=(_BOX_WIDTH, -1))
+        self.bt_apply.SetToolTipString("Save changes into the imported data.")
+        self.bt_apply.Bind(wx.EVT_BUTTON, self.on_click_apply)
+        
+        self.bt_close = wx.Button(self, -1, 'Close', size=(_BOX_WIDTH, -1))
+        self.bt_close.Bind(wx.EVT_BUTTON, self.on_close)
+        self.bt_close.SetToolTipString("Close this panel.")
+        
+        self.button_sizer.AddMany([(self.bt_apply, 0, 
+                                    wx.LEFT, EDITOR_WIDTH * 0.8),
+                                   (self.bt_close, 0, 
+                                    wx.LEFT|wx.BOTTOM, 15)])
+          
+    def _do_layout(self):
+        """
+        Draw the current panel
+        """
+        self._define_structure()
+        self._layout_name()
+        self._layout_description()
+        self._layout_param()
+        self._layout_function()
+        self._layout_msg()
+        self._layout_button()
+        self.main_sizer.AddMany([(self.name_sizer, 0,  
+                                        wx.EXPAND|wx.ALL, 5),
+                                 (wx.StaticLine(self), 0, 
+                                       wx.ALL|wx.EXPAND, 5),
+                                 (self.desc_sizer, 0,  
+                                        wx.EXPAND|wx.ALL, 5),
+                                 (wx.StaticLine(self), 0, 
+                                       wx.ALL|wx.EXPAND, 5),
+                                (self.param_sizer, 1,
+                                         wx.EXPAND|wx.ALL, 5),
+                                 (wx.StaticLine(self), 0, 
+                                       wx.ALL|wx.EXPAND, 5),
+                                (self.function_sizer, 2,
+                                         wx.EXPAND|wx.ALL, 5),
+                                 (wx.StaticLine(self), 0, 
+                                       wx.ALL|wx.EXPAND, 5),
+                                 (self.msg_sizer, 0, 
+                                        wx.EXPAND|wx.ALL, 5),
+                                (self.button_sizer, 0,
+                                         wx.EXPAND|wx.ALL, 5)])
+        self.SetSizer(self.main_sizer)
+        self.SetAutoLayout(True)
+
+    def _fill_math_combo(self):
+        """
+        Fill up the math combo box
+        """
+        self.math_combo = wx.ComboBox(self, -1, size=(100, -1), 
+                                      style=wx.CB_READONLY) 
+        for item in dir(math):
+            if item.count("_") < 1:
+                try:
+                    exec "float(math.%s)"% item
+                    self.math_combo.Append(str(item))
+                except:
+                    self.math_combo.Append(str(item) + "()" )
+        self.math_combo.Bind(wx.EVT_COMBOBOX, self._on_math_select)
+        self.math_combo.SetSelection(0)
+        return self.math_combo
+        
+    def _on_math_select(self, event):
+        """
+        On math selection on ComboBox
+        """
+        event.Skip()
+        label = self.math_combo.GetLabel() 
+        self.function_tcl.SetFocus()
+        # Put the text at the cursor position
+        pos = self.function_tcl.GetCurrentPos()
+        self.function_tcl.InsertText(pos, label)  
+        # Put the cursor at appropriate postion
+        length = len(label)
+        if label[-1] == ')':
+            length -= 1
+        f_pos = pos + length
+        self.function_tcl.GotoPos(f_pos)   
+
+    def get_notes(self):
+        """
+        return notes
+        """
+        return self._notes
+                 
+    def on_change_name(self, event=None):
+        """
+        Change name
+        """
+        if event is not None:
+            event.Skip()
+        self.name_tcl.SetBackgroundColour('white')
+        self.Refresh()
+    
+    def check_name(self):
+        """
+        Check name if exist already
+        """
+        self._notes = ''
+        self.on_change_name(None)
+        plugin_dir = self.path
+        list_fnames = os.listdir(plugin_dir)
+        # function/file name
+        title = self.name_tcl.GetValue().lstrip().rstrip()
+        self.name = title
+        t_fname = title + '.py'
+        if not self.overwrite_name:
+            if t_fname in list_fnames:
+                self.name_tcl.SetBackgroundColour('pink')
+                return False
+        self.fname = os.path.join(plugin_dir, t_fname)
+        s_title = title
+        if len(title) > 20:
+            s_title = title[0:19] + '...'
+        self._notes += "Model function name is set "
+        self._notes += "to %s. \n" % str(s_title)
+        return True
+    
+    def on_over_cb(self, event):
+        """
+        Set overwrite name flag on cb event
+        """
+        if event is not None:
+            event.Skip()
+        cb = event.GetEventObject()
+        self.overwrite_name = cb.GetValue()
+        
+    def on_click_apply(self, event):
+        """   
+        Changes are saved in data object imported to edit
+        """
+        #must post event here
+        event.Skip()
+        info = 'Info'
+        msg = ''
+        # Sort out the errors if occur
+        if self.check_name():
+            name = self.name_tcl.GetValue().lstrip().rstrip()
+            description = self.desc_tcl.GetValue()
+            param_str = self.param_tcl.GetText()
+            func_str = self.function_tcl.GetText()
+            # No input for the model function
+            if func_str.lstrip().rstrip():     
+                if func_str.count('return') > 0:
+                    self.write_file(self.fname, description, param_str, 
+                                                                    func_str)
+                    tr_msg = _compileFile(self.fname)
+                    msg = str(tr_msg.__str__())
+                    # Compile error
+                    if msg:
+                        msg.replace("  ", "\n")
+                        msg +=  "\nCompiling Failed"
+                else:
+                    msg = "Error: The func(x) must 'return' a value at least.\n"
+                    msg += "For example: \n\nreturn 2*x"
+            else:
+                msg = 'Error: Function is not defined.'
+        else:
+            msg = "Name exists already."
+        # Prepare for the messagebox
+        if self.base != None and not msg:
+            self.base.update_custom_combo()
+            Model  = None
+            exec "from %s import Model"% name 
+            try:
+                Model().run(0.01) 
+            except:
+                msg = "Error "
+                _, value, _ = sys.exc_info()
+                msg += "in %s:\n%s\n" % (name,  value)
+        if msg:
+            info = 'Error'
+            color = 'red' 
+            try:
+                # try to remove pyc file if exists
+                _deleteFile(self.fname)
+                _deleteFile(self.fname + "c")
+            except:
+                pass
+        else:
+            msg = "Successful! "
+            msg += "  " + self._notes
+            msg += " Please look for it in the Customized Models."
+            info = 'Info'
+            color = 'blue'
+        # Not to display long error msg
+        if info == 'Error':
+            mss = info
+        else:
+            mss = msg
+        self._msg_box.SetLabel(mss)
+        self._msg_box.SetForegroundColour(color)
+        # Send msg to the top window  
+        if self.base != None:
+                from sas.guiframe.events import StatusEvent 
+                wx.PostEvent(self.base.parent, StatusEvent(status = msg, 
+                                                      info=info))
+        self.warning = msg
+
+                
+    def write_file(self, fname, desc_str, param_str, func_str):  
+        """
+        Write content in file
+        
+        :param fname: full file path
+        :param desc_str: content of the description strings
+        :param param_str: content of params; Strings  
+        :param func_str: content of func; Strings
+        """ 
+        try:
+            out_f =  open(fname,'w')
+        except :
+            raise
+        # Prepare the content of the function
+        lines = CUSTOM_TEMPLATE.split('\n')
+
+        has_scipy = func_str.count("scipy.")
+        self.is_2d = func_str.count("#self.ndim = 2")
+        line_2d = ''
+        if self.is_2d:
+            line_2d = CUSTOM_2D_TEMP.split('\n')
+        line_test = TEST_TEMPLATE.split('\n')
+        local_params = ''
+        spaces = '        '#8spaces
+        # write function here
+        for line in lines:
+            # The location where to put the strings is 
+            # hard-coded in the template as shown below.
+            if line.count("#self.params here"):
+                for param_line in param_str.split('\n'):
+                    p_line = param_line.lstrip().rstrip()
+                    if p_line:
+                        p0_line = self.set_param_helper(p_line)
+                        local_params += self.set_function_helper(p_line)
+                        out_f.write(p0_line)
+            elif line.count("#local params here"):
+                if local_params:
+                    out_f.write(local_params)
+            elif line.count("self.description = "):
+                des0 = self.name + "\\n"
+                desc = str(desc_str.lstrip().rstrip().replace('\"', ''))
+                out_f.write(line% (des0 + desc) + "\n")
+            elif line.count("def function(self, x=0.0%s):"):
+                if self.is_2d:
+                    y_str = ', y=0.0'
+                    out_f.write(line% y_str + "\n")
+                else:
+                    out_f.write(line% '' + "\n")
+            elif line.count("#function here"):
+                for func_line in func_str.split('\n'):
+                    f_line = func_line.rstrip()
+                    if f_line:
+                        out_f.write(spaces + f_line + "\n")
+                if not func_str:
+                    dep_var = 'y'
+                    if self.is_2d:
+                        dep_var = 'z'
+                    out_f.write(spaces + 'return %s'% dep_var + "\n")
+            elif line.count("#import scipy?"):
+                if has_scipy:
+                    out_f.write("import scipy" + "\n")
+            #elif line.count("name = "):
+            #    out_f.write(line % self.name + "\n")
+            elif line:
+                out_f.write(line + "\n")
+        # run string for 2d
+        if line_2d:
+            for line in line_2d:
+                out_f.write(line + "\n")
+        # Test strins
+        for line in line_test:
+            out_f.write(line + "\n")
+   
+        out_f.close() 
+    
+    def set_param_helper(self, line):   
+        """
+        Get string in line to define the params dictionary
+        
+        :param line: one line of string got from the param_str
+        """
+        flag = True
+        params_str = ''
+        spaces = '        '#8spaces
+        items = line.split(";")
+        for item in items:
+            name = item.split("=")[0].lstrip().rstrip()
+            try:
+                value = item.split("=")[1].lstrip().rstrip()
+                float(value)
+            except:
+                value = 1.0 # default
+            params_str += spaces + "self.params['%s'] = %s\n"% (name, value)
+            
+        return params_str
+
+    def set_function_helper(self, line):   
+        """
+        Get string in line to define the local params
+        
+        :param line: one line of string got from the param_str
+        """
+        flag = True
+        params_str = ''
+        spaces = '        '#8spaces
+        items = line.split(";")
+        for item in items:
+            name = item.split("=")[0].lstrip().rstrip()
+            params_str += spaces + "%s = self.params['%s']\n"% (name, name)
+        return params_str
+    
+    def get_warning(self):
+        """
+        Get the warning msg 
+        """
+        return self.warning
+        
+    def on_close(self, event):
+        """
+        leave data as it is and close
+        """
+        self.parent.Show(False)#Close()
+        event.Skip()
+        
+class EditorWindow(wx.Frame):
+    """
+    Editor Window
+    """
+    def __init__(self, parent, base, path, title, 
+                 size=(EDITOR_WIDTH, EDITOR_HEIGTH), *args, **kwds):
+        """
+        Init
+        """
+        kwds["title"] = title
+        kwds["size"] = size
+        wx.Frame.__init__(self, parent=None, *args, **kwds)
+        self.parent = parent
+        self.panel = EditorPanel(parent=self, base=parent, 
+                                 path=path, title=title)
+        self.Show(True)
+        wx.EVT_CLOSE(self, self.OnClose)
+    
+    def OnClose(self, event):  
+        """
+        On close event
+        """
+        self.Show(False)
+        #if self.parent != None:
+        #    self.parent.new_model_frame = None
+        #self.Destroy()  
+
+## Templates for custom models
+CUSTOM_TEMPLATE = """
+from sas.models.pluginmodel import Model1DPlugin
+from math import *
+import os
+import sys
+import numpy
+#import scipy?
+class Model(Model1DPlugin):
+    name = ""                             
+    def __init__(self):
+        Model1DPlugin.__init__(self, name=self.name)  
+        #set name same as file name 
+        self.name = self.get_fname()                                                   
+        #self.params here
+        self.description = "%s"
+        self.set_details()
+    def function(self, x=0.0%s):
+        #local params here
+        #function here
+"""
+CUSTOM_2D_TEMP = """
+    def run(self, x=0.0, y=0.0):
+        if x.__class__.__name__ == 'list':
+            x_val = x[0]
+            y_val = y[0]*0.0
+            return self.function(x_val, y_val)
+        elif x.__class__.__name__ == 'tuple':
+            msg = "Tuples are not allowed as input to BaseComponent models"
+            raise ValueError, msg
+        else:
+            return self.function(x, 0.0)
+    def runXY(self, x=0.0, y=0.0):
+        if x.__class__.__name__ == 'list':
+            return self.function(x, y)
+        elif x.__class__.__name__ == 'tuple':
+            msg = "Tuples are not allowed as input to BaseComponent models"
+            raise ValueError, msg
+        else:
+            return self.function(x, y)
+    def evalDistribution(self, qdist):
+        if qdist.__class__.__name__ == 'list':
+            msg = "evalDistribution expects a list of 2 ndarrays"
+            if len(qdist)!=2:
+                raise RuntimeError, msg
+            if qdist[0].__class__.__name__ != 'ndarray':
+                raise RuntimeError, msg
+            if qdist[1].__class__.__name__ != 'ndarray':
+                raise RuntimeError, msg
+            v_model = numpy.vectorize(self.runXY, otypes=[float])
+            iq_array = v_model(qdist[0], qdist[1])
+            return iq_array
+        elif qdist.__class__.__name__ == 'ndarray':
+            v_model = numpy.vectorize(self.runXY, otypes=[float])
+            iq_array = v_model(qdist)
+            return iq_array
+"""
+TEST_TEMPLATE = """
+    def get_fname(self):
+        path = sys._getframe().f_code.co_filename
+        basename  = os.path.basename(path)
+        name, _ = os.path.splitext(basename)
+        return name
+######################################################################
+## THIS IS FOR TEST. DO NOT MODIFY THE FOLLOWING LINES!!!!!!!!!!!!!!!!       
+if __name__ == "__main__": 
+    m= Model() 
+    out1 = m.runXY(0.0)
+    out2 = m.runXY(0.01)
+    isfine1 = numpy.isfinite(out1)
+    isfine2 = numpy.isfinite(out2)
+    print "Testing the value at Q = 0.0:"
+    print out1, " : finite? ", isfine1
+    print "Testing the value at Q = 0.01:"
+    print out2, " : finite? ", isfine2
+    if isfine1 and isfine2:
+        print "===> Simple Test: Passed!"
+    else:
+        print "===> Simple Test: Failed!"
+"""
+SUM_TEMPLATE = """
+# A sample of an experimental model function for Sum/Multiply(Pmodel1,Pmodel2)
+import copy
+from sas.models.pluginmodel import Model1DPlugin
+# User can change the name of the model (only with single functional model)
+#P1_model: 
+#from sas.models.%s import %s as P1
+#from %s import Model as P1 
+
+#P2_model: 
+#from sas.models.%s import %s as P2
+#from %s import Model as P2 
+import os
+import sys
+
+class Model(Model1DPlugin):
+    name = ""
+    def __init__(self):
+        Model1DPlugin.__init__(self, name='')
+        p_model1 = P1()
+        p_model2 = P2()
+        ## Setting  model name model description
+        self.description = '%s'
+        self.name = self.get_fname()
+        if self.name.rstrip().lstrip() == '':
+            self.name = self._get_name(p_model1.name, p_model2.name)
+        if self.description.rstrip().lstrip() == '':
+            self.description = p_model1.name
+            self.description += p_model2.name
+            self.fill_description(p_model1, p_model2)
+
+        ## Define parameters
+        self.params = {}
+
+        ## Parameter details [units, min, max]
+        self.details = {}
+        ## Magnetic Panrameters
+        self.magnetic_params = []
+        # non-fittable parameters
+        self.non_fittable = p_model1.non_fittable  
+        self.non_fittable += p_model2.non_fittable  
+            
+        ##models 
+        self.p_model1= p_model1
+        self.p_model2= p_model2
+        
+       
+        ## dispersion
+        self._set_dispersion()
+        ## Define parameters
+        self._set_params()
+        ## New parameter:scaling_factor
+        self.params['scale_factor'] = %s
+        
+        ## Parameter details [units, min, max]
+        self._set_details()
+        self.details['scale_factor'] = ['', None, None]
+
+        
+        #list of parameter that can be fitted
+        self._set_fixed_params()  
+        ## parameters with orientation
+        for item in self.p_model1.orientation_params:
+            new_item = "p1_" + item
+            if not new_item in self.orientation_params:
+                self.orientation_params.append(new_item)
+            
+        for item in self.p_model2.orientation_params:
+            new_item = "p2_" + item
+            if not new_item in self.orientation_params:
+                self.orientation_params.append(new_item)
+        ## magnetic params
+        for item in self.p_model1.magnetic_params:
+            new_item = "p1_" + item
+            if not new_item in self.magnetic_params:
+                self.magnetic_params.append(new_item)
+            
+        for item in self.p_model2.magnetic_params:
+            new_item = "p2_" + item
+            if not new_item in self.magnetic_params:
+                self.magnetic_params.append(new_item)
+        # get multiplicity if model provide it, else 1.
+        try:
+            multiplicity1 = p_model1.multiplicity
+            try:
+                multiplicity2 = p_model2.multiplicity
+            except:
+                multiplicity2 = 1
+        except:
+            multiplicity1 = 1
+            multiplicity2 = 1
+        ## functional multiplicity of the model
+        self.multiplicity1 = multiplicity1  
+        self.multiplicity2 = multiplicity2    
+        self.multiplicity_info = []   
+        
+    def _clone(self, obj):
+        obj.params     = copy.deepcopy(self.params)
+        obj.description     = copy.deepcopy(self.description)
+        obj.details    = copy.deepcopy(self.details)
+        obj.dispersion = copy.deepcopy(self.dispersion)
+        obj.p_model1  = self.p_model1.clone()
+        obj.p_model2  = self.p_model2.clone()
+        #obj = copy.deepcopy(self)
+        return obj
+    
+    def _get_name(self, name1, name2):
+        p1_name = self._get_upper_name(name1)
+        if not p1_name:
+            p1_name = name1
+        name = p1_name
+        name += "_and_"
+        p2_name = self._get_upper_name(name2)
+        if not p2_name:
+            p2_name = name2
+        name += p2_name
+        return name
+    
+    def _get_upper_name(self, name=None):
+        if name == None:
+            return ""
+        upper_name = ""
+        str_name = str(name)
+        for index in range(len(str_name)):
+            if str_name[index].isupper():
+                upper_name += str_name[index]
+        return upper_name
+        
+    def _set_dispersion(self):
+        ##set dispersion only from p_model 
+        for name , value in self.p_model1.dispersion.iteritems():
+            #if name.lower() not in self.p_model1.orientation_params:
+            new_name = "p1_" + name
+            self.dispersion[new_name]= value 
+        for name , value in self.p_model2.dispersion.iteritems():
+            #if name.lower() not in self.p_model2.orientation_params:
+            new_name = "p2_" + name
+            self.dispersion[new_name]= value 
+            
+    def function(self, x=0.0): 
+        return 0
+                               
+    def getProfile(self):
+        try:
+            x,y = self.p_model1.getProfile()
+        except:
+            x = None
+            y = None
+            
+        return x, y
+    
+    def _set_params(self):
+        for name , value in self.p_model1.params.iteritems():
+            # No 2D-supported
+            #if name not in self.p_model1.orientation_params:
+            new_name = "p1_" + name
+            self.params[new_name]= value
+            
+        for name , value in self.p_model2.params.iteritems():
+            # No 2D-supported
+            #if name not in self.p_model2.orientation_params:
+            new_name = "p2_" + name
+            self.params[new_name]= value
+                
+        # Set "scale" as initializing
+        self._set_scale_factor()
+      
+            
+    def _set_details(self):
+        for name ,detail in self.p_model1.details.iteritems():
+            new_name = "p1_" + name
+            #if new_name not in self.orientation_params:
+            self.details[new_name]= detail
+            
+        for name ,detail in self.p_model2.details.iteritems():
+            new_name = "p2_" + name
+            #if new_name not in self.orientation_params:
+            self.details[new_name]= detail
+    
+    def _set_scale_factor(self):
+        pass
+        
+                
+    def setParam(self, name, value):
+        # set param to this (p1, p2) model
+        self._setParamHelper(name, value)
+        
+        ## setParam to p model 
+        model_pre = ''
+        new_name = ''
+        name_split = name.split('_', 1)
+        if len(name_split) == 2:
+            model_pre = name.split('_', 1)[0]
+            new_name = name.split('_', 1)[1]
+        if model_pre == "p1":
+            if new_name in self.p_model1.getParamList():
+                self.p_model1.setParam(new_name, value)
+        elif model_pre == "p2":
+             if new_name in self.p_model2.getParamList():
+                self.p_model2.setParam(new_name, value)
+        elif name == 'scale_factor':
+            self.params['scale_factor'] = value
+        else:
+            raise ValueError, "Model does not contain parameter %s" % name
+            
+    def getParam(self, name):
+        # Look for dispersion parameters
+        toks = name.split('.')
+        if len(toks)==2:
+            for item in self.dispersion.keys():
+                # 2D not supported
+                if item.lower()==toks[0].lower():
+                    for par in self.dispersion[item]:
+                        if par.lower() == toks[1].lower():
+                            return self.dispersion[item][par]
+        else:
+            # Look for standard parameter
+            for item in self.params.keys():
+                if item.lower()==name.lower():
+                    return self.params[item]
+        return  
+        #raise ValueError, "Model does not contain parameter %s" % name
+       
+    def _setParamHelper(self, name, value):
+        # Look for dispersion parameters
+        toks = name.split('.')
+        if len(toks)== 2:
+            for item in self.dispersion.keys():
+                if item.lower()== toks[0].lower():
+                    for par in self.dispersion[item]:
+                        if par.lower() == toks[1].lower():
+                            self.dispersion[item][par] = value
+                            return
+        else:
+            # Look for standard parameter
+            for item in self.params.keys():
+                if item.lower()== name.lower():
+                    self.params[item] = value
+                    return
+            
+        raise ValueError, "Model does not contain parameter %s" % name
+             
+   
+    def _set_fixed_params(self):
+        for item in self.p_model1.fixed:
+            new_item = "p1" + item
+            self.fixed.append(new_item)
+        for item in self.p_model2.fixed:
+            new_item = "p2" + item
+            self.fixed.append(new_item)
+
+        self.fixed.sort()
+                
+                    
+    def run(self, x = 0.0):
+        self._set_scale_factor()
+        return self.params['scale_factor'] %s \
+(self.p_model1.run(x) %s self.p_model2.run(x))
+    
+    def runXY(self, x = 0.0):
+        self._set_scale_factor()
+        return self.params['scale_factor'] %s \
+(self.p_model1.runXY(x) %s self.p_model2.runXY(x))
+    
+    ## Now (May27,10) directly uses the model eval function 
+    ## instead of the for-loop in Base Component.
+    def evalDistribution(self, x = []):
+        self._set_scale_factor()
+        return self.params['scale_factor'] %s \
+(self.p_model1.evalDistribution(x) %s \
+self.p_model2.evalDistribution(x))
+
+    def set_dispersion(self, parameter, dispersion):
+        value= None
+        new_pre = parameter.split("_", 1)[0]
+        new_parameter = parameter.split("_", 1)[1]
+        try:
+            if new_pre == 'p1' and \
+new_parameter in self.p_model1.dispersion.keys():
+                value= self.p_model1.set_dispersion(new_parameter, dispersion)
+            if new_pre == 'p2' and \
+new_parameter in self.p_model2.dispersion.keys():
+                value= self.p_model2.set_dispersion(new_parameter, dispersion)
+            self._set_dispersion()
+            return value
+        except:
+            raise 
+
+    def fill_description(self, p_model1, p_model2):
+        description = ""
+        description += "This model gives the summation or multiplication of"
+        description += "%s and %s. "% ( p_model1.name, p_model2.name )
+        self.description += description
+          
+    def get_fname(self):
+        path = sys._getframe().f_code.co_filename
+        basename  = os.path.basename(path)
+        name, _ = os.path.splitext(basename)
+        return name     
+           
+if __name__ == "__main__": 
+    m1= Model() 
+    #m1.setParam("p1_scale", 25)  
+    #m1.setParam("p1_length", 1000)
+    #m1.setParam("p2_scale", 100) 
+    #m1.setParam("p2_rg", 100) 
+    out1 = m1.runXY(0.01)
+
+    m2= Model()
+    #m2.p_model1.setParam("scale", 25) 
+    #m2.p_model1.setParam("length", 1000) 
+    #m2.p_model2.setParam("scale", 100)
+    #m2.p_model2.setParam("rg", 100)
+    out2 = m2.p_model1.runXY(0.01) %s m2.p_model2.runXY(0.01)\n
+    print "My name is %s."% m1.name
+    print out1, " = ", out2
+    if out1 == out2:
+        print "===> Simple Test: Passed!"
+    else:
+        print "===> Simple Test: Failed!"
+"""
+      
+#if __name__ == "__main__": 
+#    app = wx.PySimpleApp()
+#    frame = TextDialog(id=1, model_list=["SphereModel", "CylinderModel"])   
+#    frame.Show(True)
+#    app.MainLoop()             
+
+if __name__ == "__main__":
+    from sas.perspectives.fitting import models
+    dir_path = models.find_plugins_dir()
+    app  = wx.App()
+    window = EditorWindow(parent=None, base=None, path=dir_path, title="Editor")
+    app.MainLoop()         
