| 1 | """Crust combines the shell and filling into one control.""" |
| 2 | |
| 3 | __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" |
| 4 | __cvsid__ = "$Id$" |
| 5 | __revision__ = "$Revision$"[11:-2] |
| 6 | |
| 7 | import wx |
| 8 | |
| 9 | import os |
| 10 | import pprint |
| 11 | import re |
| 12 | import sys |
| 13 | |
| 14 | import dispatcher |
| 15 | import editwindow |
| 16 | from filling import Filling |
| 17 | import frame |
| 18 | from shell import Shell |
| 19 | from version import VERSION |
| 20 | |
| 21 | |
| 22 | class Crust(wx.SplitterWindow): |
| 23 | """Crust based on SplitterWindow.""" |
| 24 | |
| 25 | name = 'Crust' |
| 26 | revision = __revision__ |
| 27 | sashoffset = 300 |
| 28 | |
| 29 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, |
| 30 | size=wx.DefaultSize, style=wx.SP_3D|wx.SP_LIVE_UPDATE, |
| 31 | name='Crust Window', rootObject=None, rootLabel=None, |
| 32 | rootIsNamespace=True, intro='', locals=None, |
| 33 | InterpClass=None, |
| 34 | startupScript=None, execStartupScript=True, |
| 35 | *args, **kwds): |
| 36 | """Create Crust instance.""" |
| 37 | wx.SplitterWindow.__init__(self, parent, id, pos, size, style, name) |
| 38 | self.shell = Shell(parent=self, introText=intro, |
| 39 | locals=locals, InterpClass=InterpClass, |
| 40 | startupScript=startupScript, |
| 41 | execStartupScript=execStartupScript, |
| 42 | *args, **kwds) |
| 43 | self.editor = self.shell |
| 44 | if rootObject is None: |
| 45 | rootObject = self.shell.interp.locals |
| 46 | self.notebook = wx.Notebook(parent=self, id=-1) |
| 47 | self.shell.interp.locals['notebook'] = self.notebook |
| 48 | self.filling = Filling(parent=self.notebook, |
| 49 | rootObject=rootObject, |
| 50 | rootLabel=rootLabel, |
| 51 | rootIsNamespace=rootIsNamespace) |
| 52 | # Add 'filling' to the interpreter's locals. |
| 53 | self.shell.interp.locals['filling'] = self.filling |
| 54 | self.notebook.AddPage(page=self.filling, text='Namespace', select=True) |
| 55 | |
| 56 | self.display = Display(parent=self.notebook) |
| 57 | self.notebook.AddPage(page=self.display, text='Display') |
| 58 | # Add 'pp' (pretty print) to the interpreter's locals. |
| 59 | self.shell.interp.locals['pp'] = self.display.setItem |
| 60 | self.display.nbTab = self.notebook.GetPageCount()-1 |
| 61 | |
| 62 | self.calltip = Calltip(parent=self.notebook) |
| 63 | self.notebook.AddPage(page=self.calltip, text='Calltip') |
| 64 | |
| 65 | self.sessionlisting = SessionListing(parent=self.notebook) |
| 66 | self.notebook.AddPage(page=self.sessionlisting, text='History') |
| 67 | |
| 68 | self.dispatcherlisting = DispatcherListing(parent=self.notebook) |
| 69 | self.notebook.AddPage(page=self.dispatcherlisting, text='Dispatcher') |
| 70 | |
| 71 | self.SplitHorizontally(self.shell, self.notebook, -self.sashoffset) |
| 72 | self.SetMinimumPaneSize(100) |
| 73 | |
| 74 | self.Bind(wx.EVT_SIZE, self.SplitterOnSize) |
| 75 | self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnChanged) |
| 76 | |
| 77 | |
| 78 | def OnChanged(self, event): |
| 79 | """update sash offset from the bottom of the window""" |
| 80 | self.sashoffset = self.GetSize().height - event.GetSashPosition() |
| 81 | event.Skip() |
| 82 | |
| 83 | |
| 84 | # Make the splitter expand the top window when resized |
| 85 | def SplitterOnSize(self, event): |
| 86 | splitter = event.GetEventObject() |
| 87 | sz = splitter.GetSize() |
| 88 | splitter.SetSashPosition(sz.height - self.sashoffset, True) |
| 89 | event.Skip() |
| 90 | |
| 91 | |
| 92 | def LoadSettings(self, config): |
| 93 | self.shell.LoadSettings(config) |
| 94 | self.filling.LoadSettings(config) |
| 95 | |
| 96 | pos = config.ReadInt('Sash/CrustPos', 400) |
| 97 | wx.CallAfter(self.SetSashPosition, pos) |
| 98 | def _updateSashPosValue(): |
| 99 | sz = self.GetSize() |
| 100 | self.sashoffset = sz.height - self.GetSashPosition() |
| 101 | wx.CallAfter(_updateSashPosValue) |
| 102 | zoom = config.ReadInt('View/Zoom/Display', -99) |
| 103 | if zoom != -99: |
| 104 | self.display.SetZoom(zoom) |
| 105 | |
| 106 | |
| 107 | def SaveSettings(self, config): |
| 108 | self.shell.SaveSettings(config) |
| 109 | self.filling.SaveSettings(config) |
| 110 | |
| 111 | config.WriteInt('Sash/CrustPos', self.GetSashPosition()) |
| 112 | config.WriteInt('View/Zoom/Display', self.display.GetZoom()) |
| 113 | |
| 114 | |
| 115 | |
| 116 | |
| 117 | |
| 118 | class Display(editwindow.EditWindow): |
| 119 | """STC used to display an object using Pretty Print.""" |
| 120 | |
| 121 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, |
| 122 | size=wx.DefaultSize, |
| 123 | style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER, |
| 124 | static=False): |
| 125 | """Create Display instance.""" |
| 126 | editwindow.EditWindow.__init__(self, parent, id, pos, size, style) |
| 127 | # Configure various defaults and user preferences. |
| 128 | self.SetReadOnly(True) |
| 129 | self.SetWrapMode(False) |
| 130 | if not static: |
| 131 | dispatcher.connect(receiver=self.push, signal='Interpreter.push') |
| 132 | |
| 133 | def push(self, command, more): |
| 134 | """Receiver for Interpreter.push signal.""" |
| 135 | self.Refresh() |
| 136 | |
| 137 | def Refresh(self): |
| 138 | if not hasattr(self, "item"): |
| 139 | return |
| 140 | self.SetReadOnly(False) |
| 141 | text = pprint.pformat(self.item) |
| 142 | self.SetText(text) |
| 143 | self.SetReadOnly(True) |
| 144 | |
| 145 | def setItem(self, item): |
| 146 | """Set item to pretty print in the notebook Display tab.""" |
| 147 | self.item = item |
| 148 | self.Refresh() |
| 149 | if self.GetParent().GetSelection() != self.nbTab: |
| 150 | focus = wx.Window.FindFocus() |
| 151 | self.GetParent().SetSelection(self.nbTab) |
| 152 | wx.CallAfter(focus.SetFocus) |
| 153 | |
| 154 | |
| 155 | # TODO: Switch this to a editwindow.EditWindow |
| 156 | class Calltip(wx.TextCtrl): |
| 157 | """Text control containing the most recent shell calltip.""" |
| 158 | |
| 159 | def __init__(self, parent=None, id=-1): |
| 160 | style = (wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH2) |
| 161 | wx.TextCtrl.__init__(self, parent, id, style=style) |
| 162 | self.SetBackgroundColour(wx.Colour(255, 255, 208)) |
| 163 | dispatcher.connect(receiver=self.display, signal='Shell.calltip') |
| 164 | |
| 165 | df = self.GetFont() |
| 166 | font = wx.Font(df.GetPointSize(), wx.TELETYPE, wx.NORMAL, wx.NORMAL) |
| 167 | self.SetFont(font) |
| 168 | |
| 169 | def display(self, calltip): |
| 170 | """Receiver for Shell.calltip signal.""" |
| 171 | ## self.SetValue(calltip) # Caused refresh problem on Windows. |
| 172 | self.Clear() |
| 173 | self.AppendText(calltip) |
| 174 | |
| 175 | |
| 176 | # TODO: Switch this to a editwindow.EditWindow |
| 177 | class SessionListing(wx.TextCtrl): |
| 178 | """Text control containing all commands for session.""" |
| 179 | |
| 180 | def __init__(self, parent=None, id=-1): |
| 181 | style = (wx.TE_MULTILINE | wx.TE_READONLY | |
| 182 | wx.TE_RICH2 | wx.TE_DONTWRAP) |
| 183 | wx.TextCtrl.__init__(self, parent, id, style=style) |
| 184 | dispatcher.connect(receiver=self.addHistory, signal="Shell.addHistory") |
| 185 | dispatcher.connect(receiver=self.clearHistory, signal="Shell.clearHistory") |
| 186 | dispatcher.connect(receiver=self.loadHistory, signal="Shell.loadHistory") |
| 187 | |
| 188 | df = self.GetFont() |
| 189 | font = wx.Font(df.GetPointSize(), wx.TELETYPE, wx.NORMAL, wx.NORMAL) |
| 190 | self.SetFont(font) |
| 191 | |
| 192 | def loadHistory(self, history): |
| 193 | # preload the existing history, if any |
| 194 | hist = history[:] |
| 195 | hist.reverse() |
| 196 | self.SetValue('\n'.join(hist) + '\n') |
| 197 | self.SetInsertionPointEnd() |
| 198 | |
| 199 | def addHistory(self, command): |
| 200 | if command: |
| 201 | self.SetInsertionPointEnd() |
| 202 | self.AppendText(command + '\n') |
| 203 | |
| 204 | def clearHistory(self): |
| 205 | self.SetValue("") |
| 206 | |
| 207 | |
| 208 | class DispatcherListing(wx.TextCtrl): |
| 209 | """Text control containing all dispatches for session.""" |
| 210 | |
| 211 | def __init__(self, parent=None, id=-1): |
| 212 | style = (wx.TE_MULTILINE | wx.TE_READONLY | |
| 213 | wx.TE_RICH2 | wx.TE_DONTWRAP) |
| 214 | wx.TextCtrl.__init__(self, parent, id, style=style) |
| 215 | dispatcher.connect(receiver=self.spy) |
| 216 | |
| 217 | df = self.GetFont() |
| 218 | font = wx.Font(df.GetPointSize(), wx.TELETYPE, wx.NORMAL, wx.NORMAL) |
| 219 | self.SetFont(font) |
| 220 | |
| 221 | def spy(self, signal, sender): |
| 222 | """Receiver for Any signal from Any sender.""" |
| 223 | text = '%r from %s' % (signal, sender) |
| 224 | self.SetInsertionPointEnd() |
| 225 | start, end = self.GetSelection() |
| 226 | if start != end: |
| 227 | self.SetSelection(0, 0) |
| 228 | self.AppendText(text + '\n') |
| 229 | |
| 230 | |
| 231 | |
| 232 | class CrustFrame(frame.Frame, frame.ShellFrameMixin): |
| 233 | """Frame containing all the PyCrust components.""" |
| 234 | |
| 235 | name = 'CrustFrame' |
| 236 | revision = __revision__ |
| 237 | |
| 238 | |
| 239 | def __init__(self, parent=None, id=-1, title='PyCrust', |
| 240 | pos=wx.DefaultPosition, size=wx.DefaultSize, |
| 241 | style=wx.DEFAULT_FRAME_STYLE, |
| 242 | rootObject=None, rootLabel=None, rootIsNamespace=True, |
| 243 | locals=None, InterpClass=None, |
| 244 | config=None, dataDir=None, |
| 245 | *args, **kwds): |
| 246 | """Create CrustFrame instance.""" |
| 247 | frame.Frame.__init__(self, parent, id, title, pos, size, style) |
| 248 | frame.ShellFrameMixin.__init__(self, config, dataDir) |
| 249 | |
| 250 | if size == wx.DefaultSize: |
| 251 | self.SetSize((800, 600)) |
| 252 | |
| 253 | intro = 'PyCrust %s - The Flakiest Python Shell' % VERSION |
| 254 | self.SetStatusText(intro.replace('\n', ', ')) |
| 255 | self.crust = Crust(parent=self, intro=intro, |
| 256 | rootObject=rootObject, |
| 257 | rootLabel=rootLabel, |
| 258 | rootIsNamespace=rootIsNamespace, |
| 259 | locals=locals, |
| 260 | InterpClass=InterpClass, |
| 261 | startupScript=self.startupScript, |
| 262 | execStartupScript=self.execStartupScript, |
| 263 | *args, **kwds) |
| 264 | self.shell = self.crust.shell |
| 265 | |
| 266 | # Override the filling so that status messages go to the status bar. |
| 267 | self.crust.filling.tree.setStatusText = self.SetStatusText |
| 268 | |
| 269 | # Override the shell so that status messages go to the status bar. |
| 270 | self.shell.setStatusText = self.SetStatusText |
| 271 | |
| 272 | self.shell.SetFocus() |
| 273 | self.LoadSettings() |
| 274 | |
| 275 | |
| 276 | def OnClose(self, event): |
| 277 | """Event handler for closing.""" |
| 278 | self.SaveSettings() |
| 279 | self.crust.shell.destroy() |
| 280 | self.Destroy() |
| 281 | |
| 282 | |
| 283 | def OnAbout(self, event): |
| 284 | """Display an About window.""" |
| 285 | title = 'About PyCrust' |
| 286 | text = 'PyCrust %s\n\n' % VERSION + \ |
| 287 | 'Yet another Python shell, only flakier.\n\n' + \ |
| 288 | 'Half-baked by Patrick K. O\'Brien,\n' + \ |
| 289 | 'the other half is still in the oven.\n\n' + \ |
| 290 | 'Shell Revision: %s\n' % self.shell.revision + \ |
| 291 | 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \ |
| 292 | 'Platform: %s\n' % sys.platform + \ |
| 293 | 'Python Version: %s\n' % sys.version.split()[0] + \ |
| 294 | 'wxPython Version: %s\n' % wx.VERSION_STRING + \ |
| 295 | ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:])) |
| 296 | dialog = wx.MessageDialog(self, text, title, |
| 297 | wx.OK | wx.ICON_INFORMATION) |
| 298 | dialog.ShowModal() |
| 299 | dialog.Destroy() |
| 300 | |
| 301 | |
| 302 | def OnHelp(self, event): |
| 303 | """Show a help dialog.""" |
| 304 | frame.ShellFrameMixin.OnHelp(self, event) |
| 305 | |
| 306 | |
| 307 | def LoadSettings(self): |
| 308 | if self.config is not None: |
| 309 | frame.ShellFrameMixin.LoadSettings(self) |
| 310 | frame.Frame.LoadSettings(self, self.config) |
| 311 | self.crust.LoadSettings(self.config) |
| 312 | |
| 313 | |
| 314 | def SaveSettings(self, force=False): |
| 315 | if self.config is not None: |
| 316 | frame.ShellFrameMixin.SaveSettings(self) |
| 317 | if self.autoSaveSettings or force: |
| 318 | frame.Frame.SaveSettings(self, self.config) |
| 319 | self.crust.SaveSettings(self.config) |
| 320 | |
| 321 | |
| 322 | def DoSaveSettings(self): |
| 323 | if self.config is not None: |
| 324 | self.SaveSettings(force=True) |
| 325 | self.config.Flush() |
| 326 | |
| 327 | |
| 328 | |