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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 0912b405 was 0912b405, checked in by Piotr Rozyczko <rozyczko@…>, 7 years ago

Initial implementation of C model editor in pyconsole. Fixes #662

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