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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249unittest-saveload
Last change on this file since 2a6058c was 2a6058c, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

fix broken plugin model tests. Fixes #1142

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