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