source: sasview/src/sas/sasgui/perspectives/calculator/pyconsole.py @ ae2f623

Last change on this file since ae2f623 was 4627657, checked in by lewis, 7 years ago

Run plug-in model unit tests when 'Check model' is clicked

  • Property mode set to 100644
File size: 11.3 KB
Line 
1"""
2Console Module display Python console
3"""
4import sys
5import os
6
7import numpy as np
8
9import wx
10from wx.lib.dialogs import ScrolledMessageDialog
11from wx.lib import layoutf
12
13import wx.py.editor as editor
14
15if sys.platform.count("win32") > 0:
16    PANEL_WIDTH = 800
17    PANEL_HEIGHT = 700
18    FONT_VARIANT = 0
19else:
20    PANEL_WIDTH = 830
21    PANEL_HEIGHT = 730
22    FONT_VARIANT = 1
23ID_CHECK_MODEL = wx.NewId()
24ID_RUN = wx.NewId()
25
26def check_model(path):
27    """
28    Check that the model on the path can run.
29    """
30    # try running the model
31    from sasmodels.sasview_model import load_custom_model
32    Model = load_custom_model(path)
33    model = Model()
34    q =  np.array([0.01, 0.1])
35    Iq = model.evalDistribution(q)
36    qx, qy =  np.array([0.01, 0.01]), np.array([0.1, 0.1])
37    Iqxy = model.evalDistribution([qx, qy])
38
39    # check the model's unit tests run
40    from sasmodels.model_test import run_one
41    result = run_one(path)
42
43    return result
44
45def show_model_output(parent, fname):
46    # Make sure we have a python file; not sure why we care though...
47    if not (fname and os.path.exists(fname) and fname.endswith('.py')):
48        mssg = "\n This is not a python file."
49        wx.MessageBox(str(mssg), 'Error', style=wx.ICON_ERROR)
50        return False
51
52    try:
53        result, errmsg = check_model(fname), None
54    except Exception:
55        import traceback
56        result, errmsg = None, traceback.format_exc()
57
58    parts = ["Running model '%s'..." % os.path.basename(fname)]
59    if errmsg is not None:
60        parts.extend(["", "Error occurred:", errmsg, ""])
61        title, icon = "Error", wx.ICON_ERROR
62    else:
63        parts.extend(["", "Success:", result, ""])
64        title, icon = "Info", wx.ICON_INFORMATION
65    text = "\n".join(parts)
66    dlg = ResizableScrolledMessageDialog(parent, text, title, size=((550, 250)))
67    fnt = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL)
68    dlg.GetChildren()[0].SetFont(fnt)
69    dlg.GetChildren()[0].SetInsertionPoint(0)
70    dlg.ShowModal()
71    dlg.Destroy()
72    return errmsg is None
73
74class ResizableScrolledMessageDialog(wx.Dialog):
75    """
76    Custom version of wx ScrolledMessageDialog, allowing border resize
77    """
78    def __init__(self, parent, msg, caption,
79        pos=wx.DefaultPosition, size=(500,300),
80        style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER ):
81        # Notice, that style can be overrriden in the caller.
82        wx.Dialog.__init__(self, parent, -1, caption, pos, size, style)
83        x, y = pos
84        if x == -1 and y == -1:
85            self.CenterOnScreen(wx.BOTH)
86
87        text = wx.TextCtrl(self, -1, msg, style=wx.TE_MULTILINE | wx.TE_READONLY)
88        ok = wx.Button(self, wx.ID_OK, "OK")
89
90        # Mysterious constraint layouts from
91        # https://www.wxpython.org/docs/api/wx.lib.layoutf.Layoutf-class.html
92        lc = layoutf.Layoutf('t=t5#1;b=t5#2;l=l5#1;r=r5#1', (self,ok))
93        text.SetConstraints(lc)
94        lc = layoutf.Layoutf('b=b5#1;x%w50#1;w!80;h!25', (self,))
95        ok.SetConstraints(lc)
96
97        self.SetAutoLayout(1)
98        self.Layout()
99
100class PyConsole(editor.EditorNotebookFrame):
101    ## Internal nickname for the window, used by the AUI manager
102    window_name = "Custom Model Editor"
103    ## Name to appear on the window title bar
104    window_caption = "Plugin Model Editor"
105    ## Flag to tell the AUI manager to put this panel in the center pane
106    CENTER_PANE = False
107    def __init__(self, parent=None, base=None, manager=None, panel=None,
108                    title='Python Shell/Editor', filename=None,
109                    size=(PANEL_WIDTH, PANEL_HEIGHT)):
110        self.config = None
111
112        editor.EditorNotebookFrame.__init__(self, parent=parent,
113                                        title=title, size=size)
114        self.parent = parent
115        self._manager = manager
116        self.base = base
117        self.panel = panel
118        self._add_menu()
119        if filename is not None:
120            dataDir = os.path.dirname(filename)
121        elif self.parent is not None:
122            dataDir = self.parent._default_save_location
123        else:
124             dataDir = None
125        self.dataDir = dataDir
126        self.Centre()
127
128        # See if there is a corresponding C file
129        if filename is not None:
130            c_filename = os.path.splitext(filename)[0] + ".c"
131            if os.path.isfile(c_filename):
132                self.bufferCreate(c_filename)
133
134            # If not, just open the requested .py, if any.
135            # Needs to be after the C file so the tab focus is correct.
136            if os.path.isfile(filename):
137                    self.bufferCreate(filename)
138
139        self.Bind(wx.EVT_MENU, self.OnNewFile, id=wx.ID_NEW)
140        self.Bind(wx.EVT_MENU, self.OnOpenFile, id=wx.ID_OPEN)
141        self.Bind(wx.EVT_MENU, self.OnSaveFile, id=wx.ID_SAVE)
142        self.Bind(wx.EVT_MENU, self.OnSaveAsFile, id=wx.ID_SAVEAS)
143        self.Bind(wx.EVT_MENU, self.OnCheckModel, id=ID_CHECK_MODEL)
144        self.Bind(wx.EVT_MENU, self.OnRun, id=ID_RUN)
145        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateCompileMenu, id=ID_CHECK_MODEL)
146        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateCompileMenu, id=ID_RUN)
147        self.Bind(wx.EVT_CLOSE, self.on_close)
148        if not title.count('Python Shell'):
149            # Delete menu item (open and new) if not python shell
150            #self.fileMenu.Delete(wx.ID_NEW)
151            self.fileMenu.Delete(wx.ID_OPEN)
152
153
154    def _add_menu(self):
155        """
156        Add menu
157        """
158        self.compileMenu = wx.Menu()
159        self.compileMenu.Append(ID_CHECK_MODEL, 'Check model',
160                 'Loading and run the model')
161        self.compileMenu.AppendSeparator()
162        self.compileMenu.Append(ID_RUN, 'Run in Shell',
163                 'Run the file in the Python Shell')
164        self.MenuBar.Insert(3, self.compileMenu, '&Run')
165
166    def OnHelp(self, event):
167        """
168        Show a help dialog.
169        """
170        import  wx.lib.dialogs
171        title = 'Help on key bindings'
172        text = wx.py.shell.HELP_TEXT
173        dlg = wx.lib.dialogs.ScrolledMessageDialog(self, text, title,
174                                                   size=((700, 540)))
175        fnt = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL)
176        dlg.GetChildren()[0].SetFont(fnt)
177        dlg.GetChildren()[0].SetInsertionPoint(0)
178        dlg.ShowModal()
179        dlg.Destroy()
180
181    def set_manager(self, manager):
182        """
183        Set the manager of this window
184        """
185        self._manager = manager
186
187    def OnAbout(self, event):
188        """
189        On About
190        """
191        message = ABOUT
192        dial = wx.MessageDialog(self, message, 'About',
193                           wx.OK | wx.ICON_INFORMATION)
194        dial.ShowModal()
195
196    def OnNewFile(self, event):
197        """
198        OnFileOpen
199        """
200        self.OnFileNew(event)
201
202    def OnOpenFile(self, event):
203        """
204        OnFileOpen
205        """
206        self.OnFileOpen(event)
207        self.Show(False)
208        self.Show(True)
209
210    def OnSaveFile(self, event):
211        """
212        OnFileSave overwrite
213        """
214        self.OnFileSave(event)
215        self.Show(False)
216        self.Show(True)
217
218    def OnSaveAsFile(self, event):
219        """
220        OnFileSaveAs overwrite
221        """
222        self.OnFileSaveAs(event)
223        self.Show(False)
224        self.Show(True)
225
226    def bufferOpen(self):
227        """
228        Open file in buffer, bypassing editor bufferOpen
229        """
230        if self.bufferHasChanged():
231            cancel = self.bufferSuggestSave()
232            if cancel:
233                return cancel
234        filedir = ''
235        if self.buffer and self.buffer.doc.filedir:
236            filedir = self.buffer.doc.filedir
237        if not filedir:
238            filedir = self.dataDir
239        result = editor.openSingle(directory=filedir,
240                            wildcard='Python Files (*.py)|*.py')
241        if result.path:
242            self.bufferCreate(result.path)
243
244        # See if there is a corresponding C file
245        if result.path is not None:
246            c_filename = os.path.splitext(result.path)[0] + ".c"
247            if os.path.isfile(c_filename):
248                self.bufferCreate(c_filename)
249
250        cancel = False
251        return cancel
252
253    def bufferSaveAs(self):
254        """
255        Save buffer to a new filename: Bypassing editor bufferSaveAs
256        """
257        filedir = ''
258        if self.buffer and self.buffer.doc.filedir:
259            filedir = self.buffer.doc.filedir
260        if not filedir:
261            filedir = self.dataDir
262        result = editor.saveSingle(directory=filedir,
263                                   filename='untitled.py',
264                                   wildcard='Python Files (*.py)|*.py')
265        if result.path:
266            self.buffer.confirmed = True
267            self.buffer.saveAs(result.path)
268            cancel = False
269        else:
270            cancel = True
271        return cancel
272
273    def OnRun(self, event):
274        """
275        Run
276        """
277        if not self._check_saved():
278            return True
279        if self.buffer and self.buffer.doc.filepath:
280            self.editor.setFocus()
281            # Why we have to do this (Otherwise problems on Windows)?
282            forward_path = self.buffer.doc.filepath.replace('\\', '/')
283            self.shell.Execute("execfile('%s')" % forward_path)
284            self.shell.Hide()
285            self.shell.Show(True)
286            return self.shell.GetText().split(">>>")[-2]
287        else:
288            mssg = "\n This is not a python file."
289            title = 'Error'
290            icon = wx.ICON_ERROR
291            wx.MessageBox(str(mssg), title, style=icon)
292            return False
293
294    def OnCheckModel(self, event):
295        """
296        Compile
297        """
298        if not self._check_saved():
299            return True
300        fname = self.editor.getStatus()[0]
301        success = show_model_output(self, fname)
302
303        # Update plugin model list in fitpage combobox
304        if success and self._manager is not None and self.panel is not None:
305            self._manager.set_edit_menu_helper(self.parent)
306            wx.CallAfter(self._manager.update_custom_combo)
307
308    def _check_saved(self):
309        """
310        If content was changed, suggest to save it first
311        """
312        if self.bufferHasChanged() and self.buffer.doc.filepath:
313            cancel = self.bufferSuggestSave()
314            return not cancel
315        return True
316
317    def OnUpdateCompileMenu(self, event):
318        """
319        Update Compile menu items based on current tap.
320        """
321        win = wx.Window.FindFocus()
322        id = event.GetId()
323        event.Enable(True)
324        try:
325            if id == ID_CHECK_MODEL or id == ID_RUN:
326                menu_on = False
327                if self.buffer and self.buffer.doc.filepath:
328                    menu_on = True
329                event.Enable(menu_on)
330        except AttributeError:
331            # This menu option is not supported in the current context.
332            event.Enable(False)
333
334    def on_close(self, event):
335        """
336        Close event
337        """
338        if self.base is not None:
339            self.base.py_frame = None
340        self.Destroy()
341
342ABOUT = "Welcome to Python %s! \n\n" % sys.version.split()[0]
343ABOUT += "This uses Py Shell/Editor in wx (developed by Patrick K. O'Brien).\n"
344ABOUT += "If this is your first time using Python, \n"
345ABOUT += "you should definitely check out the tutorial "
346ABOUT += "on the Internet at http://www.python.org/doc/tut/."
347
348
349if __name__ == "__main__":
350
351    app = wx.App()
352    dlg = PyConsole()
353    dlg.Show()
354    app.MainLoop()
Note: See TracBrowser for help on using the repository browser.