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() newid = wx.NewId() self.function_tcl = EditWindow(self, newid, 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_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() newid = wx.NewId() self.function_tcl = EditWindow(self, newid, 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_param(self): """ Do the layout for parameter related widgets """ param_txt = wx.StaticText(self, -1, 'Fit Parameters: ') param_tip = "#Set the parameters and their initial values.\n" param_tip += "#Example:\n" param_tip += "A = 1\nB = 1" #param_txt.SetToolTipString(param_tip) newid = wx.NewId() self.param_tcl = EditWindow(self, newid, 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 __init__( self, parent=None, title=u'简单Maven', pos=wx.DefaultPosition, \ size=(800,600), style=wx.DEFAULT_FRAME_STYLE^wx.RESIZE_BORDER, name='mainFrame' ): wx.Frame.__init__( self, parent=parent, title=title, pos=pos, size=size, \ style=style, name=name ) self.SetBackgroundColour("gray") self.createStatusBar() self.createMenuBar() self.tree = wx.TreeCtrl(self, id=wx.NewId(), size=(300, -1), style=wx.TR_HAS_BUTTONS | wx.TR_LINES_AT_ROOT) self.tree.SetBackgroundColour("light gray") self.html = wx.html.HtmlWindow(self) self.html.SetHTMLBackgroundColour("medium goldenrod") self.log = EditWindow(self) #self.log.SetReadOnly(True) self.log.setDisplayLineNumbers(True) handler = MyLogHandler("root", control=self.log) logging.getLogger().setLevel(logging.INFO) logging.getLogger().addHandler(handler) vbox = wx.BoxSizer(wx.VERTICAL) box = wx.BoxSizer(wx.HORIZONTAL) box.Add(self.tree, -1, wx.EXPAND | wx.RIGHT, 3) box.Add(self.html, 2, wx.EXPAND, 3) vbox.Add(box, 5, wx.EXPAND | wx.ALL, 3) vbox.Add(self.log, 1, wx.EXPAND | wx.RIGHT | wx.LEFT | wx.BOTTOM, 3) self.SetSizer(vbox) #创建SimpleMaven对象,加载TreeData.xml文件 self.simpleMaven = SimpleMaven() wx.CallAfter(self.DataInit, controls=self.tree) self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnTreeRightClick, self.tree) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeChanged, self.tree)
def _layout_param(self): """ Do the layout for parameter related widgets """ param_txt = wx.StaticText(self, -1, 'Fit Parameters NOT requiring' + \ ' polydispersity (if any): ') param_tip = "#Set the parameters NOT requiring polydispersity " + \ "and their initial values.\n" param_tip += "#Example:\n" param_tip += "A = 1\nB = 1" #param_txt.SetToolTipString(param_tip) newid = wx.NewId() self.param_tcl = EditWindow(self, newid, 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)]) # Parameters with polydispersity pd_param_txt = wx.StaticText(self, -1, 'Fit Parameters requiring ' + \ 'polydispersity (if any): ') pd_param_tip = "#Set the parameters requiring polydispersity and " + \ "their initial values.\n" pd_param_tip += "#Example:\n" pd_param_tip += "C = 2\nD = 2" newid = wx.NewId() self.pd_param_tcl = EditWindow(self, newid, wx.DefaultPosition, wx.DefaultSize, wx.CLIP_CHILDREN | wx.SUNKEN_BORDER) self.pd_param_tcl.setDisplayLineNumbers(True) self.pd_param_tcl.SetToolTipString(pd_param_tip) self.param_sizer.AddMany([(pd_param_txt, 0, wx.LEFT, 10), (self.pd_param_tcl, 1, wx.EXPAND | wx.ALL, 10)])
def __init__(self, parent=None, id=-1, title='编辑工具', pos=wx.DefaultPosition, size=(800, 600), style=wx.DEFAULT_DIALOG_STYLE, name="edit"): wx.Dialog.__init__(self, parent=parent, id=id, title=title, pos=pos, size=size, style=style, name=name) self.edit = edit = EditWindow(self) edit.setDisplayLineNumbers(True) with open("TreeData.xml") as fp: edit.SetText(fp.read())
class EditorPanel(wx.ScrolledWindow): """ Simple Plugin 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.SetScrollbars(1, 1, 1, 1) 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.main_sizer = None self.name_sizer = None self.name_hsizer = None self.name_tcl = None self.overwrite_cb = None self.desc_sizer = None self.desc_tcl = None self.param_sizer = None self.param_tcl = None self.function_sizer = None self.func_horizon_sizer = None self.button_sizer = None self.param_strings = '' self.function_strings = '' self._notes = "" self._msg_box = None self.msg_sizer = None self.warning = "" #This does not seem to be used anywhere so commenting out for now # -- PDB 2/26/17 #self._description = "New Plugin Model" self.function_tcl = None self.math_combo = None self.bt_apply = None self.bt_close = None #self._default_save_location = os.getcwd() self._do_layout() 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 : ') self.overwrite_cb = wx.CheckBox( self, -1, "Overwrite existing plugin model of this name?", (10, 10)) self.overwrite_cb.SetValue(False) self.overwrite_cb.SetToolTipString("Overwrite it if already exists?") wx.EVT_CHECKBOX(self, self.overwrite_cb.GetId(), self.on_over_cb) 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 Model Function Name." self.name_tcl.SetToolTipString(hint_name) self.name_hsizer.AddMany([(self.name_tcl, 0, wx.LEFT | wx.TOP, 0), (self.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('') 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 NOT requiring' + \ ' polydispersity (if any): ') param_tip = "#Set the parameters NOT requiring polydispersity " + \ "and their initial values.\n" param_tip += "#Example:\n" param_tip += "A = 1\nB = 1" #param_txt.SetToolTipString(param_tip) newid = wx.NewId() self.param_tcl = EditWindow(self, newid, 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)]) # Parameters with polydispersity pd_param_txt = wx.StaticText(self, -1, 'Fit Parameters requiring ' + \ 'polydispersity (if any): ') pd_param_tip = "#Set the parameters requiring polydispersity and " + \ "their initial values.\n" pd_param_tip += "#Example:\n" pd_param_tip += "C = 2\nD = 2" newid = wx.NewId() self.pd_param_tcl = EditWindow(self, newid, wx.DefaultPosition, wx.DefaultSize, wx.CLIP_CHILDREN | wx.SUNKEN_BORDER) self.pd_param_tcl.setDisplayLineNumbers(True) self.pd_param_tcl.SetToolTipString(pd_param_tip) self.param_sizer.AddMany([(pd_param_txt, 0, wx.LEFT, 10), (self.pd_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() newid = wx.NewId() self.function_tcl = EditWindow(self, newid, 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_help = wx.Button(self, -1, "HELP", size=(_BOX_WIDTH, -1)) self.bt_help.SetToolTipString("Get Help For Model Editor") self.bt_help.Bind(wx.EVT_BUTTON, self.on_help) 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, 0), (self.bt_help, 0, wx.LEFT | wx.BOTTOM, 15), (self.bt_close, 0, wx.LEFT | wx.RIGHT, 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.ALIGN_RIGHT) ]) 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 Exception: 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.GetValue() 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 position length = len(label) print(length) if label[length - 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_value = event.GetEventObject() self.overwrite_name = cb_value.GetValue() def on_click_apply(self, event): """ Changes are saved in data object imported to edit. checks firs for valid name, then if it already exists then checks that a function was entered and finally that if entered it contains at least a return statement. If all passes writes file then tries to compile. If compile fails or import module fails or run method fails tries to remove any .py and pyc files that may have been created and sets error message. :todo this code still could do with a careful going over to clean up and simplify. the non GUI methods such as this one should be removed to computational code of SasView. Most of those computational methods would be the same for both the simple editors. """ #must post event here event.Skip() name = self.name_tcl.GetValue().lstrip().rstrip() info = 'Info' msg = '' result, check_err = '', '' # Sort out the errors if occur # First check for valid python name then if the name already exists if not name or not bool(re.match('^[A-Za-z0-9_]*$', name)): msg = '"%s" ' % name msg += "is not a valid model name. Name must not be empty and \n" msg += "may include only alpha numeric or underline characters \n" msg += "and no spaces" elif self.check_name(): description = self.desc_tcl.GetValue() param_str = self.param_tcl.GetText() pd_param_str = self.pd_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, name, description, param_str, pd_param_str, func_str) try: result, msg = check_model(self.fname), None except Exception: import traceback result, msg = None, "error building model" check_err = "\n" + traceback.format_exc(limit=2) 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." # if self.base is not None and not msg: self.base.update_custom_combo() # Prepare the messagebox if msg: info = 'Error' color = 'red' self.overwrite_cb.SetValue(True) self.overwrite_name = True else: self._notes = result msg = "Successful! Please look for %s in Plugin Models." % name msg += " " + self._notes info = 'Info' color = 'blue' self._msg_box.SetLabel(msg) self._msg_box.SetForegroundColour(color) # Send msg to the top window if self.base is not None: from sas.sasgui.guiframe.events import StatusEvent wx.PostEvent(self.base.parent, StatusEvent(status=msg + check_err, info=info)) self.warning = msg def write_file(self, fname, name, desc_str, param_str, pd_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 pd_param_str: content of params requiring polydispersity; Strings :param func_str: content of func; Strings """ out_f = open(fname, 'w') out_f.write( CUSTOM_TEMPLATE % { 'name': name, 'title': 'User model for ' + name, 'description': desc_str, 'date': datetime.datetime.now().strftime('%YYYY-%mm-%dd'), }) # Write out parameters param_names = [] # to store parameter names pd_params = [] out_f.write('parameters = [ \n') out_f.write( '# ["name", "units", default, [lower, upper], "type", "description"],\n' ) for pname, pvalue, desc in self.get_param_helper(param_str): param_names.append(pname) out_f.write(" ['%s', '', %s, [-inf, inf], '', '%s'],\n" % (pname, pvalue, desc)) for pname, pvalue, desc in self.get_param_helper(pd_param_str): param_names.append(pname) pd_params.append(pname) out_f.write(" ['%s', '', %s, [-inf, inf], 'volume', '%s'],\n" % (pname, pvalue, desc)) out_f.write(' ]\n') # Write out function definition out_f.write('def Iq(%s):\n' % ', '.join(['x'] + param_names)) out_f.write(' """Absolute scattering"""\n') if "scipy." in func_str: out_f.write(' import scipy') if "numpy." in func_str: out_f.write(' import numpy') if "np." in func_str: out_f.write(' import numpy as np') for func_line in func_str.split('\n'): out_f.write('%s%s\n' % (spaces4, func_line)) out_f.write('## uncomment the following if Iq works for vector x\n') out_f.write('#Iq.vectorized = True\n') # If polydisperse, create place holders for form_volume, ER and VR if pd_params: out_f.write('\n') out_f.write(CUSTOM_TEMPLATE_PD % {'args': ', '.join(pd_params)}) # Create place holder for Iqxy out_f.write('\n') out_f.write('#def Iqxy(%s):\n' % ', '.join(["x", "y"] + param_names)) out_f.write('# """Absolute scattering of oriented particles."""\n') out_f.write('# ...\n') out_f.write('# return oriented_form(x, y, args)\n') out_f.write( '## uncomment the following if Iqxy works for vector x, y\n') out_f.write('#Iqxy.vectorized = True\n') out_f.close() def get_param_helper(self, param_str): """ yield a sequence of name, value pairs for the parameters in param_str Parameters can be defined by one per line by name=value, or multiple on the same line by separating the pairs by semicolon or comma. The value is optional and defaults to "1.0". """ for line in param_str.replace(';', ',').split('\n'): for item in line.split(','): defn, desc = item.split('#', 1) if '#' in item else (item, '') name, value = defn.split('=', 1) if '=' in defn else (defn, '1.0') if name: yield [v.strip() for v in (name, value, desc)] 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 """ 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_help(self, event): """ Bring up the New Plugin Model Editor Documentation whenever the HELP button is clicked. Calls DocumentationWindow with the path of the location within the documentation tree (after /doc/ ....". Note that when using old versions of Wx (before 2.9) and thus not the release version of installers, the help comes up at the top level of the file as webbrowser does not pass anything past the # to the browser when it is running "file:///...." :param evt: Triggers on clicking the help button """ _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html" _PageAnchor = "#new-plugin-model" _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, _PageAnchor, "Plugin Model Editor Help") def on_close(self, event): """ leave data as it is and close """ self.parent.Show(False) #Close() event.Skip()
def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER): EditWindow.__init__(self, parent, id=id, pos=pos, size=size, style=style) self._selection = (-1, -1)
class MainFrame(wx.Frame): #主框架类 def __init__( self, parent=None, title=u'简单Maven', pos=wx.DefaultPosition, \ size=(800,600), style=wx.DEFAULT_FRAME_STYLE^wx.RESIZE_BORDER, name='mainFrame' ): wx.Frame.__init__( self, parent=parent, title=title, pos=pos, size=size, \ style=style, name=name ) self.SetBackgroundColour("gray") self.createStatusBar() self.createMenuBar() self.tree = wx.TreeCtrl(self, id=wx.NewId(), size=(300, -1), style=wx.TR_HAS_BUTTONS | wx.TR_LINES_AT_ROOT) self.tree.SetBackgroundColour("light gray") self.html = wx.html.HtmlWindow(self) self.html.SetHTMLBackgroundColour("medium goldenrod") self.log = EditWindow(self) #self.log.SetReadOnly(True) self.log.setDisplayLineNumbers(True) handler = MyLogHandler("root", control=self.log) logging.getLogger().setLevel(logging.INFO) logging.getLogger().addHandler(handler) vbox = wx.BoxSizer(wx.VERTICAL) box = wx.BoxSizer(wx.HORIZONTAL) box.Add(self.tree, -1, wx.EXPAND | wx.RIGHT, 3) box.Add(self.html, 2, wx.EXPAND, 3) vbox.Add(box, 5, wx.EXPAND | wx.ALL, 3) vbox.Add(self.log, 1, wx.EXPAND | wx.RIGHT | wx.LEFT | wx.BOTTOM, 3) self.SetSizer(vbox) #创建SimpleMaven对象,加载TreeData.xml文件 self.simpleMaven = SimpleMaven() wx.CallAfter(self.DataInit, controls=self.tree) self.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.OnTreeRightClick, self.tree) self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeChanged, self.tree) def createStatusBar(self): #创建状态栏 statusBar = self.CreateStatusBar(3) statusBar.SetStatusWidths([-1, -2, -3]) statusBar.SetStatusText(__version__, 0) statusBar.SetStatusText(__file__, 1) def createMenuBar(self): menuBar = wx.MenuBar() #工具菜单 menuTool = wx.Menu() edit_tree_data = menuTool.Append( wx.MenuItem(menuTool, id=wx.NewId(), text="文件预览", helpString="查看TreeData.xml文件")) self.Bind(wx.EVT_MENU, self.OnEditTreeData, edit_tree_data) menuBar.Append(menuTool, "&工具") self.SetMenuBar(menuBar) #事件处理 def OnTreeChanged(self, event): if event.GetItem() == event.GetEventObject().GetRootItem(): with open("README.md") as fp: self.html.SetPage(markdown.markdown(fp.read().decode("UTF-8"))) else: self.html.SetPage(event.GetEventObject().GetItemData( event.GetItem())) logging.info(event.GetEventObject().GetItemData(event.GetItem())) def OnEditTreeData(self, event): parentSize = self.GetClientSize() edit = EditDialog(self, size=(parentSize[0] * 0.8, parentSize[1] * 0.8), title="文件预览") edit.SetReadOnly() edit.CenterOnParent() ret = edit.ShowModal() if ret == wx.ID_CANCEL: edit.Destroy() def DataInit(self, controls=None): if controls is not None: if isinstance(controls, wx.TreeCtrl): self.simpleMaven.parseTree(controls, xmlFile=TREE_DATA_XML) with open("README.md") as fp: txt = markdown.markdown(fp.read().decode("UTF-8")) self.html.SetPage(txt) def OnTreeRightClick(self, event): if not hasattr(self, "addNodeId"): self.addNodeId = wx.NewId() self.removeNodeId = wx.NewId() self.changeNodeId = wx.NewId() self.Bind(wx.EVT_MENU, self.OnAddNode, id=self.addNodeId) self.Bind(wx.EVT_MENU, self.OnRemoveNode, id=self.removeNodeId) self.Bind(wx.EVT_MENU, self.OnChangeNode, id=self.changeNodeId) menu = wx.Menu() menu.Append(wx.MenuItem(menu, id=self.addNodeId, text="添加子节点")) menu.Append(wx.MenuItem(menu, id=self.removeNodeId, text="删除子节点")) menu.Append(wx.MenuItem(menu, id=self.changeNodeId, text="修改字节点")) self.PopupMenu(menu, event.GetPoint()) menu.Destroy() def OnAddNode(self, event): print self.tree.GetSelection() def OnRemoveNode(self, event): pass def OnChangeNode(self, event): pass
class EditorPanel(wx.ScrolledWindow): """ Simple Plugin 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.SetScrollbars(1,1,1,1) 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.main_sizer = None self.name_sizer = None self.name_hsizer = None self.name_tcl = None self.overwrite_cb = None self.desc_sizer = None self.desc_tcl = None self.param_sizer = None self.param_tcl = None self.function_sizer = None self.func_horizon_sizer = None self.button_sizer = None self.param_strings = '' self.function_strings = '' self._notes = "" self._msg_box = None self.msg_sizer = None self.warning = "" #This does not seem to be used anywhere so commenting out for now # -- PDB 2/26/17 #self._description = "New Plugin Model" self.function_tcl = None self.math_combo = None self.bt_apply = None self.bt_close = None #self._default_save_location = os.getcwd() self._do_layout() 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 : ') self.overwrite_cb = wx.CheckBox(self, -1, "Overwrite existing plugin model of this name?", (10, 10)) self.overwrite_cb.SetValue(False) self.overwrite_cb.SetToolTipString("Overwrite it if already exists?") wx.EVT_CHECKBOX(self, self.overwrite_cb.GetId(), self.on_over_cb) 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 Model Function Name." self.name_tcl.SetToolTipString(hint_name) self.name_hsizer.AddMany([(self.name_tcl, 0, wx.LEFT | wx.TOP, 0), (self.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('') 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 NOT requiring' + \ ' polydispersity (if any): ') param_tip = "#Set the parameters NOT requiring polydispersity " + \ "and their initial values.\n" param_tip += "#Example:\n" param_tip += "A = 1\nB = 1" #param_txt.SetToolTipString(param_tip) newid = wx.NewId() self.param_tcl = EditWindow(self, newid, 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)]) # Parameters with polydispersity pd_param_txt = wx.StaticText(self, -1, 'Fit Parameters requiring ' + \ 'polydispersity (if any): ') pd_param_tip = "#Set the parameters requiring polydispersity and " + \ "their initial values.\n" pd_param_tip += "#Example:\n" pd_param_tip += "C = 2\nD = 2" newid = wx.NewId() self.pd_param_tcl = EditWindow(self, newid, wx.DefaultPosition, wx.DefaultSize, wx.CLIP_CHILDREN | wx.SUNKEN_BORDER) self.pd_param_tcl.setDisplayLineNumbers(True) self.pd_param_tcl.SetToolTipString(pd_param_tip) self.param_sizer.AddMany([(pd_param_txt, 0, wx.LEFT, 10), (self.pd_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() newid = wx.NewId() self.function_tcl = EditWindow(self, newid, 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_help = wx.Button(self, -1, "HELP", size=(_BOX_WIDTH, -1)) self.bt_help.SetToolTipString("Get Help For Model Editor") self.bt_help.Bind(wx.EVT_BUTTON, self.on_help) 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,0), (self.bt_help, 0, wx.LEFT | wx.BOTTOM,15), (self.bt_close, 0, wx.LEFT | wx.RIGHT, 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.ALIGN_RIGHT)]) 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.GetValue() 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 position length = len(label) print(length) if label[length-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_value = event.GetEventObject() self.overwrite_name = cb_value.GetValue() def on_click_apply(self, event): """ Changes are saved in data object imported to edit. checks firs for valid name, then if it already exists then checks that a function was entered and finally that if entered it contains at least a return statement. If all passes writes file then tries to compile. If compile fails or import module fails or run method fails tries to remove any .py and pyc files that may have been created and sets error message. :todo this code still could do with a careful going over to clean up and simplify. the non GUI methods such as this one should be removed to computational code of SasView. Most of those computational methods would be the same for both the simple editors. """ #must post event here event.Skip() name = self.name_tcl.GetValue().lstrip().rstrip() info = 'Info' msg = '' result, check_err = '', '' # Sort out the errors if occur # First check for valid python name then if the name already exists if not name or not bool(re.match('^[A-Za-z0-9_]*$', name)): msg = '"%s" '%name msg += "is not a valid model name. Name must not be empty and \n" msg += "may include only alpha numeric or underline characters \n" msg += "and no spaces" elif self.check_name(): description = self.desc_tcl.GetValue() param_str = self.param_tcl.GetText() pd_param_str = self.pd_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, name, description, param_str, pd_param_str, func_str) try: result, msg = check_model(self.fname), None except Exception: import traceback result, msg = None, "error building model" check_err = "\n"+traceback.format_exc(limit=2) 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 the messagebox if self.base is not None and not msg: self.base.update_custom_combo() # Passed exception in import test as it will fail for sasmodels.sasview_model class # Should add similar test for new style? Model = None try: exec "from %s import Model" % name except: logger.error(sys.exc_value) # Prepare the messagebox if msg: info = 'Error' color = 'red' self.overwrite_cb.SetValue(True) self.overwrite_name = True else: self._notes = result msg = "Successful! Please look for %s in Plugin Models."%name msg += " " + self._notes info = 'Info' color = 'blue' self._msg_box.SetLabel(msg) self._msg_box.SetForegroundColour(color) # Send msg to the top window if self.base is not None: from sas.sasgui.guiframe.events import StatusEvent wx.PostEvent(self.base.parent, StatusEvent(status=msg+check_err, info=info)) self.warning = msg def write_file(self, fname, name, desc_str, param_str, pd_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 pd_param_str: content of params requiring polydispersity; 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.") if has_scipy: lines.insert(0, 'import scipy') # Think about 2D later #self.is_2d = func_str.count("#self.ndim = 2") #line_2d = '' #if self.is_2d: # line_2d = CUSTOM_2D_TEMP.split('\n') # Also think about test later #line_test = TEST_TEMPLATE.split('\n') #local_params = '' #spaces = ' '#8spaces spaces4 = ' '*4 spaces13 = ' '*13 spaces16 = ' '*16 param_names = [] # to store parameter names has_scipy = func_str.count("scipy.") if has_scipy: lines.insert(0, 'import scipy') # write function here for line in lines: # The location where to put the strings is # hard-coded in the template as shown below. out_f.write(line + '\n') if line.count('#name'): out_f.write('name = "%s" \n' % name) elif line.count('#title'): out_f.write('title = "User model for %s"\n' % name) elif line.count('#description'): out_f.write('description = "%s"\n' % desc_str) elif line.count('#parameters'): out_f.write('parameters = [ \n') for param_line in param_str.split('\n'): p_line = param_line.lstrip().rstrip() if p_line: pname, pvalue, desc = self.get_param_helper(p_line) param_names.append(pname) out_f.write("%s['%s', '', %s, [-numpy.inf, numpy.inf], '', '%s'],\n" % (spaces16, pname, pvalue, desc)) for param_line in pd_param_str.split('\n'): p_line = param_line.lstrip().rstrip() if p_line: pname, pvalue, desc = self.get_param_helper(p_line) param_names.append(pname) out_f.write("%s['%s', '', %s, [-numpy.inf, numpy.inf], 'volume', '%s'],\n" % (spaces16, pname, pvalue, desc)) out_f.write('%s]\n' % spaces13) # No form_volume or ER available in simple model editor out_f.write('def form_volume(*arg): \n') out_f.write(' return 1.0 \n') out_f.write('\n') out_f.write('def ER(*arg): \n') out_f.write(' return 1.0 \n') # function to compute out_f.write('\n') out_f.write('def Iq(x ') for name in param_names: out_f.write(', %s' % name) out_f.write('):\n') for func_line in func_str.split('\n'): out_f.write('%s%s\n' % (spaces4, func_line)) Iqxy_string = 'return Iq(numpy.sqrt(x**2+y**2) ' out_f.write('\n') out_f.write('def Iqxy(x, y ') for name in param_names: out_f.write(', %s' % name) Iqxy_string += ', ' + name out_f.write('):\n') Iqxy_string += ')' out_f.write('%s%s\n' % (spaces4, Iqxy_string)) out_f.close() def get_param_helper(self, line): """ Get string in line to define the params dictionary :param line: one line of string got from the param_str """ items = line.split(";") for item in items: name = item.split("=")[0].strip() description = "" try: value = item.split("=")[1].strip() if value.count("#"): # If line ends in a comment, remove it before parsing float index = value.index("#") description = value[(index + 1):].strip() value = value[:value.index("#")].strip() float(value) except ValueError: value = 1.0 # default return name, value, description 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 """ 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_help(self, event): """ Bring up the New Plugin Model Editor Documentation whenever the HELP button is clicked. Calls DocumentationWindow with the path of the location within the documentation tree (after /doc/ ....". Note that when using old versions of Wx (before 2.9) and thus not the release version of installers, the help comes up at the top level of the file as webbrowser does not pass anything past the # to the browser when it is running "file:///...." :param evt: Triggers on clicking the help button """ _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html" _PageAnchor = "#new-plugin-model" _doc_viewer = DocumentationWindow(self, -1, _TreeLocation, _PageAnchor, "Plugin Model Editor Help") def on_close(self, event): """ leave data as it is and close """ self.parent.Show(False)#Close() event.Skip()