"""Crust combines the shell and filling into one control.""" __author__ = "Patrick K. O'Brien " import wx import os import pprint import re import sys from . import dispatcher from . import editwindow from .filling import Filling from . import frame from .shell import Shell from .version import VERSION class Crust(wx.SplitterWindow): """Crust based on SplitterWindow.""" name = 'Crust' sashoffset = 300 def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.SP_3D|wx.SP_LIVE_UPDATE, name='Crust Window', rootObject=None, rootLabel=None, rootIsNamespace=True, intro='', locals=None, InterpClass=None, startupScript=None, execStartupScript=True, *args, **kwds): """Create Crust instance.""" wx.SplitterWindow.__init__(self, parent, id, pos, size, style, name) # Turn off the tab-traversal style that is automatically # turned on by wx.SplitterWindow. We do this because on # Windows the event for Ctrl-Enter is stolen and used as a # navigation key, but the Shell window uses it to insert lines. style = self.GetWindowStyle() self.SetWindowStyle(style & ~wx.TAB_TRAVERSAL) self.shell = Shell(parent=self, introText=intro, locals=locals, InterpClass=InterpClass, startupScript=startupScript, execStartupScript=execStartupScript, *args, **kwds) self.editor = self.shell if rootObject is None: rootObject = self.shell.interp.locals self.notebook = wx.Notebook(parent=self, id=-1) self.shell.interp.locals['notebook'] = self.notebook self.filling = Filling(parent=self.notebook, rootObject=rootObject, rootLabel=rootLabel, rootIsNamespace=rootIsNamespace) # Add 'filling' to the interpreter's locals. self.shell.interp.locals['filling'] = self.filling self.notebook.AddPage(page=self.filling, text='Namespace', select=True) self.display = Display(parent=self.notebook) self.notebook.AddPage(page=self.display, text='Display') # Add 'pp' (pretty print) to the interpreter's locals. self.shell.interp.locals['pp'] = self.display.setItem self.display.nbTab = self.notebook.GetPageCount()-1 self.calltip = Calltip(parent=self.notebook) self.notebook.AddPage(page=self.calltip, text='Calltip') self.sessionlisting = SessionListing(parent=self.notebook) self.notebook.AddPage(page=self.sessionlisting, text='History') self.dispatcherlisting = DispatcherListing(parent=self.notebook) self.notebook.AddPage(page=self.dispatcherlisting, text='Dispatcher') # Initialize in an unsplit mode, and check later after loading # settings if we should split or not. self.shell.Hide() self.notebook.Hide() self.Initialize(self.shell) self._shouldsplit = True wx.CallAfter(self._CheckShouldSplit) self.SetMinimumPaneSize(100) self.Bind(wx.EVT_SIZE, self.SplitterOnSize) self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnChanged) self.Bind(wx.EVT_SPLITTER_DCLICK, self.OnSashDClick) def _CheckShouldSplit(self): if self._shouldsplit: self.SplitHorizontally(self.shell, self.notebook, -self.sashoffset) self.lastsashpos = self.GetSashPosition() else: self.lastsashpos = -1 self.issplit = self.IsSplit() def ToggleTools(self): """Toggle the display of the filling and other tools""" if self.issplit: self.Unsplit() else: self.SplitHorizontally(self.shell, self.notebook, -self.sashoffset) self.lastsashpos = self.GetSashPosition() self.issplit = self.IsSplit() def ToolsShown(self): return self.issplit def OnChanged(self, event): """update sash offset from the bottom of the window""" self.sashoffset = self.GetSize().height - event.GetSashPosition() self.lastsashpos = event.GetSashPosition() event.Skip() def OnSashDClick(self, event): self.Unsplit() self.issplit = False # Make the splitter expand the top window when resized def SplitterOnSize(self, event): splitter = event.GetEventObject() sz = splitter.GetSize() splitter.SetSashPosition(sz.height - self.sashoffset, True) event.Skip() def LoadSettings(self, config): self.shell.LoadSettings(config) self.filling.LoadSettings(config) pos = config.ReadInt('Sash/CrustPos', 400) wx.CallAfter(self.SetSashPosition, pos) def _updateSashPosValue(): sz = self.GetSize() self.sashoffset = sz.height - self.GetSashPosition() wx.CallAfter(_updateSashPosValue) zoom = config.ReadInt('View/Zoom/Display', -99) if zoom != -99: self.display.SetZoom(zoom) self.issplit = config.ReadInt('Sash/IsSplit', True) if not self.issplit: self._shouldsplit = False def SaveSettings(self, config): self.shell.SaveSettings(config) self.filling.SaveSettings(config) if self.lastsashpos != -1: config.WriteInt('Sash/CrustPos', self.lastsashpos) config.WriteInt('Sash/IsSplit', self.issplit) config.WriteInt('View/Zoom/Display', self.display.GetZoom()) class Display(editwindow.EditWindow): """STC used to display an object using Pretty Print.""" def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER, static=False): """Create Display instance.""" editwindow.EditWindow.__init__(self, parent, id, pos, size, style) # Configure various defaults and user preferences. self.SetReadOnly(True) self.SetWrapMode(False) if not static: dispatcher.connect(receiver=self.push, signal='Interpreter.push') def push(self, command, more): """Receiver for Interpreter.push signal.""" self.Refresh() def Refresh(self): if not hasattr(self, "item"): return self.SetReadOnly(False) text = pprint.pformat(self.item) self.SetText(text) self.SetReadOnly(True) def setItem(self, item): """Set item to pretty print in the notebook Display tab.""" self.item = item self.Refresh() if self.GetParent().GetSelection() != self.nbTab: focus = wx.Window.FindFocus() self.GetParent().SetSelection(self.nbTab) wx.CallAfter(focus.SetFocus) # TODO: Switch this to a editwindow.EditWindow class Calltip(wx.TextCtrl): """Text control containing the most recent shell calltip.""" def __init__(self, parent=None, id=-1,ShellClassName='Shell'): style = (wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2) wx.TextCtrl.__init__(self, parent, id, style=style) self.SetBackgroundColour(wx.Colour(255, 255, 208)) self.ShellClassName=ShellClassName dispatcher.connect(receiver=self.display, signal=self.ShellClassName+'.calltip') df = self.GetFont() font = wx.Font(df.GetPointSize(), wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) self.SetFont(font) def display(self, calltip): """Receiver for """+self.ShellClassName+""".calltip signal.""" ## self.SetValue(calltip) # Caused refresh problem on Windows. self.Clear() self.AppendText(calltip) self.SetInsertionPoint(0) # TODO: Switch this to a editwindow.EditWindow class SessionListing(wx.TextCtrl): """Text control containing all commands for session.""" def __init__(self, parent=None, id=-1,ShellClassName='Shell'): style = (wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP) wx.TextCtrl.__init__(self, parent, id, style=style) dispatcher.connect(receiver=self.addHistory, signal=ShellClassName+".addHistory") dispatcher.connect(receiver=self.clearHistory, signal=ShellClassName+".clearHistory") dispatcher.connect(receiver=self.loadHistory, signal=ShellClassName+".loadHistory") df = self.GetFont() font = wx.Font(df.GetPointSize(), wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) self.SetFont(font) def loadHistory(self, history): # preload the existing history, if any hist = history[:] hist.reverse() self.SetValue('\n'.join(hist) + '\n') self.SetInsertionPointEnd() def addHistory(self, command): if command: self.SetInsertionPointEnd() self.AppendText(command + '\n') def clearHistory(self): self.SetValue("") class DispatcherListing(wx.TextCtrl): """Text control containing all dispatches for session.""" def __init__(self, parent=None, id=-1): style = (wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2 | wx.TE_DONTWRAP) wx.TextCtrl.__init__(self, parent, id, style=style) dispatcher.connect(receiver=self.spy) df = self.GetFont() font = wx.Font(df.GetPointSize(), wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) self.SetFont(font) def spy(self, signal, sender): """Receiver for Any signal from Any sender.""" text = '%r from %s' % (signal, sender) self.SetInsertionPointEnd() start, end = self.GetSelection() if start != end: self.SetSelection(0, 0) self.AppendText(text + '\n') class CrustFrame(frame.Frame, frame.ShellFrameMixin): """Frame containing all the PyCrust components.""" name = 'CrustFrame' def __init__(self, parent=None, id=-1, title='PyCrust', pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, rootObject=None, rootLabel=None, rootIsNamespace=True, locals=None, InterpClass=None, config=None, dataDir=None, *args, **kwds): """Create CrustFrame instance.""" frame.Frame.__init__(self, parent, id, title, pos, size, style, shellName='PyCrust') frame.ShellFrameMixin.__init__(self, config, dataDir) if size == wx.DefaultSize: self.SetSize((800, 600)) intro = 'PyCrust %s - The Flakiest Python Shell' % VERSION self.SetStatusText(intro.replace('\n', ', ')) self.crust = Crust(parent=self, intro=intro, rootObject=rootObject, rootLabel=rootLabel, rootIsNamespace=rootIsNamespace, locals=locals, InterpClass=InterpClass, startupScript=self.startupScript, execStartupScript=self.execStartupScript, *args, **kwds) self.shell = self.crust.shell # Override the filling so that status messages go to the status bar. self.crust.filling.tree.setStatusText = self.SetStatusText # Override the shell so that status messages go to the status bar. self.shell.setStatusText = self.SetStatusText self.shell.SetFocus() self.LoadSettings() def OnClose(self, event): """Event handler for closing.""" self.SaveSettings() self.crust.shell.destroy() self.Destroy() def OnAbout(self, event): """Display an About window.""" title = 'About PyCrust' text = 'PyCrust %s\n\n' % VERSION + \ 'Yet another Python shell, only flakier.\n\n' + \ 'Half-baked by Patrick K. O\'Brien,\n' + \ 'the other half is still in the oven.\n\n' + \ 'Platform: %s\n' % sys.platform + \ 'Python Version: %s\n' % sys.version.split()[0] + \ 'wxPython Version: %s\n' % wx.VERSION_STRING + \ ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:])) dialog = wx.MessageDialog(self, text, title, wx.OK | wx.ICON_INFORMATION) dialog.ShowModal() dialog.Destroy() def ToggleTools(self): """Toggle the display of the filling and other tools""" return self.crust.ToggleTools() def ToolsShown(self): return self.crust.ToolsShown() def OnHelp(self, event): """Show a help dialog.""" frame.ShellFrameMixin.OnHelp(self, event) def LoadSettings(self): if self.config is not None: frame.ShellFrameMixin.LoadSettings(self) frame.Frame.LoadSettings(self, self.config) self.crust.LoadSettings(self.config) def SaveSettings(self, force=False): if self.config is not None: frame.ShellFrameMixin.SaveSettings(self,force) if self.autoSaveSettings or force: frame.Frame.SaveSettings(self, self.config) self.crust.SaveSettings(self.config) def DoSaveSettings(self): if self.config is not None: self.SaveSettings(force=True) self.config.Flush()