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

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

Save before pushing… Fixes #1142

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