]>
Commit | Line | Data |
---|---|---|
1 | """PyAlaCarte and PyAlaMode editors.""" | |
2 | ||
3 | __author__ = "Patrick K. O'Brien <pobrien@orbtech.com>" | |
4 | __cvsid__ = "$Id$" | |
5 | __revision__ = "$Revision$"[11:-2] | |
6 | ||
7 | import wx | |
8 | ||
9 | from buffer import Buffer | |
10 | import crust | |
11 | import dispatcher | |
12 | import editwindow | |
13 | import frame | |
14 | from shell import Shell | |
15 | import version | |
16 | ||
17 | ||
18 | class EditorFrame(frame.Frame): | |
19 | """Frame containing one editor.""" | |
20 | ||
21 | def __init__(self, parent=None, id=-1, title='PyAlaCarte', | |
22 | pos=wx.DefaultPosition, size=(800, 600), | |
23 | style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE, | |
24 | filename=None): | |
25 | """Create EditorFrame instance.""" | |
26 | frame.Frame.__init__(self, parent, id, title, pos, size, style) | |
27 | self.buffers = {} | |
28 | self.buffer = None # Current buffer. | |
29 | self.editor = None | |
30 | self._defaultText = title + ' - the tastiest Python editor.' | |
31 | self._statusText = self._defaultText | |
32 | self.SetStatusText(self._statusText) | |
33 | wx.EVT_IDLE(self, self.OnIdle) | |
34 | self._setup() | |
35 | if filename: | |
36 | self.bufferCreate(filename) | |
37 | ||
38 | def _setup(self): | |
39 | """Setup prior to first buffer creation. | |
40 | ||
41 | Useful for subclasses.""" | |
42 | pass | |
43 | ||
44 | def setEditor(self, editor): | |
45 | self.editor = editor | |
46 | self.buffer = self.editor.buffer | |
47 | self.buffers[self.buffer.id] = self.buffer | |
48 | ||
49 | def OnAbout(self, event): | |
50 | """Display an About window.""" | |
51 | title = 'About PyAlaCarte' | |
52 | text = 'Another fine, flaky program.' | |
53 | dialog = wx.MessageDialog(self, text, title, | |
54 | wx.OK | wx.ICON_INFORMATION) | |
55 | dialog.ShowModal() | |
56 | dialog.Destroy() | |
57 | ||
58 | def OnClose(self, event): | |
59 | """Event handler for closing.""" | |
60 | for buffer in self.buffers.values(): | |
61 | self.buffer = buffer | |
62 | if buffer.hasChanged(): | |
63 | cancel = self.bufferSuggestSave() | |
64 | if cancel and event.CanVeto(): | |
65 | event.Veto() | |
66 | return | |
67 | self.Destroy() | |
68 | ||
69 | def OnIdle(self, event): | |
70 | """Event handler for idle time.""" | |
71 | self._updateStatus() | |
72 | if hasattr(self, 'notebook'): | |
73 | self._updateTabText() | |
74 | self._updateTitle() | |
75 | event.Skip() | |
76 | ||
77 | def _updateStatus(self): | |
78 | """Show current status information.""" | |
79 | if self.editor and hasattr(self.editor, 'getStatus'): | |
80 | status = self.editor.getStatus() | |
81 | text = 'File: %s | Line: %d | Column: %d' % status | |
82 | else: | |
83 | text = self._defaultText | |
84 | if text != self._statusText: | |
85 | self.SetStatusText(text) | |
86 | self._statusText = text | |
87 | ||
88 | def _updateTabText(self): | |
89 | """Show current buffer information on notebook tab.""" | |
90 | ## suffix = ' **' | |
91 | ## notebook = self.notebook | |
92 | ## selection = notebook.GetSelection() | |
93 | ## if selection == -1: | |
94 | ## return | |
95 | ## text = notebook.GetPageText(selection) | |
96 | ## window = notebook.GetPage(selection) | |
97 | ## if window.editor and window.editor.buffer.hasChanged(): | |
98 | ## if text.endswith(suffix): | |
99 | ## pass | |
100 | ## else: | |
101 | ## notebook.SetPageText(selection, text + suffix) | |
102 | ## else: | |
103 | ## if text.endswith(suffix): | |
104 | ## notebook.SetPageText(selection, text[:len(suffix)]) | |
105 | ||
106 | def _updateTitle(self): | |
107 | """Show current title information.""" | |
108 | title = self.GetTitle() | |
109 | if self.bufferHasChanged(): | |
110 | if title.startswith('* '): | |
111 | pass | |
112 | else: | |
113 | self.SetTitle('* ' + title) | |
114 | else: | |
115 | if title.startswith('* '): | |
116 | self.SetTitle(title[2:]) | |
117 | ||
118 | def hasBuffer(self): | |
119 | """Return True if there is a current buffer.""" | |
120 | if self.buffer: | |
121 | return True | |
122 | else: | |
123 | return False | |
124 | ||
125 | def bufferClose(self): | |
126 | """Close buffer.""" | |
127 | if self.bufferHasChanged(): | |
128 | cancel = self.bufferSuggestSave() | |
129 | if cancel: | |
130 | return cancel | |
131 | self.bufferDestroy() | |
132 | cancel = False | |
133 | return cancel | |
134 | ||
135 | def bufferCreate(self, filename=None): | |
136 | """Create new buffer.""" | |
137 | self.bufferDestroy() | |
138 | buffer = Buffer() | |
139 | self.panel = panel = wx.Panel(parent=self, id=-1) | |
140 | wx.EVT_ERASE_BACKGROUND(panel, lambda x: x) | |
141 | editor = Editor(parent=panel) | |
142 | panel.editor = editor | |
143 | sizer = wx.BoxSizer(wx.VERTICAL) | |
144 | sizer.Add(editor.window, 1, wx.EXPAND) | |
145 | panel.SetSizer(sizer) | |
146 | panel.SetAutoLayout(True) | |
147 | sizer.Layout() | |
148 | buffer.addEditor(editor) | |
149 | buffer.open(filename) | |
150 | self.setEditor(editor) | |
151 | self.editor.setFocus() | |
152 | self.SendSizeEvent() | |
153 | ||
154 | ||
155 | def bufferDestroy(self): | |
156 | """Destroy the current buffer.""" | |
157 | if self.buffer: | |
158 | for editor in self.buffer.editors.values(): | |
159 | editor.destroy() | |
160 | self.editor = None | |
161 | del self.buffers[self.buffer.id] | |
162 | self.buffer = None | |
163 | self.panel.Destroy() | |
164 | ||
165 | ||
166 | def bufferHasChanged(self): | |
167 | """Return True if buffer has changed since last save.""" | |
168 | if self.buffer: | |
169 | return self.buffer.hasChanged() | |
170 | else: | |
171 | return False | |
172 | ||
173 | def bufferNew(self): | |
174 | """Create new buffer.""" | |
175 | if self.bufferHasChanged(): | |
176 | cancel = self.bufferSuggestSave() | |
177 | if cancel: | |
178 | return cancel | |
179 | self.bufferCreate() | |
180 | cancel = False | |
181 | return cancel | |
182 | ||
183 | def bufferOpen(self): | |
184 | """Open file in buffer.""" | |
185 | if self.bufferHasChanged(): | |
186 | cancel = self.bufferSuggestSave() | |
187 | if cancel: | |
188 | return cancel | |
189 | filedir = '' | |
190 | if self.buffer and self.buffer.doc.filedir: | |
191 | filedir = self.buffer.doc.filedir | |
192 | result = openSingle(directory=filedir) | |
193 | if result.path: | |
194 | self.bufferCreate(result.path) | |
195 | cancel = False | |
196 | return cancel | |
197 | ||
198 | ## def bufferPrint(self): | |
199 | ## """Print buffer.""" | |
200 | ## pass | |
201 | ||
202 | ## def bufferRevert(self): | |
203 | ## """Revert buffer to version of file on disk.""" | |
204 | ## pass | |
205 | ||
206 | def bufferSave(self): | |
207 | """Save buffer to its file.""" | |
208 | if self.buffer.doc.filepath: | |
209 | self.buffer.save() | |
210 | cancel = False | |
211 | else: | |
212 | cancel = self.bufferSaveAs() | |
213 | return cancel | |
214 | ||
215 | def bufferSaveAs(self): | |
216 | """Save buffer to a new filename.""" | |
217 | if self.bufferHasChanged() and self.buffer.doc.filepath: | |
218 | cancel = self.bufferSuggestSave() | |
219 | if cancel: | |
220 | return cancel | |
221 | filedir = '' | |
222 | if self.buffer and self.buffer.doc.filedir: | |
223 | filedir = self.buffer.doc.filedir | |
224 | result = saveSingle(directory=filedir) | |
225 | if result.path: | |
226 | self.buffer.saveAs(result.path) | |
227 | cancel = False | |
228 | else: | |
229 | cancel = True | |
230 | return cancel | |
231 | ||
232 | def bufferSuggestSave(self): | |
233 | """Suggest saving changes. Return True if user selected Cancel.""" | |
234 | result = messageDialog(parent=None, | |
235 | message='%s has changed.\n' | |
236 | 'Would you like to save it first' | |
237 | '?' % self.buffer.name, | |
238 | title='Save current file?') | |
239 | if result.positive: | |
240 | cancel = self.bufferSave() | |
241 | else: | |
242 | cancel = result.text == 'Cancel' | |
243 | return cancel | |
244 | ||
245 | def updateNamespace(self): | |
246 | """Update the buffer namespace for autocompletion and calltips.""" | |
247 | if self.buffer.updateNamespace(): | |
248 | self.SetStatusText('Namespace updated') | |
249 | else: | |
250 | self.SetStatusText('Error executing, unable to update namespace') | |
251 | ||
252 | ||
253 | class EditorNotebookFrame(EditorFrame): | |
254 | """Frame containing one or more editors in a notebook.""" | |
255 | ||
256 | def __init__(self, parent=None, id=-1, title='PyAlaMode', | |
257 | pos=wx.DefaultPosition, size=(800, 600), | |
258 | style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE, | |
259 | filename=None): | |
260 | """Create EditorNotebookFrame instance.""" | |
261 | self.notebook = None | |
262 | EditorFrame.__init__(self, parent, id, title, pos, | |
263 | size, style, filename) | |
264 | if self.notebook: | |
265 | dispatcher.connect(receiver=self._editorChange, | |
266 | signal='EditorChange', sender=self.notebook) | |
267 | ||
268 | def _setup(self): | |
269 | """Setup prior to first buffer creation. | |
270 | ||
271 | Called automatically by base class during init.""" | |
272 | self.notebook = EditorNotebook(parent=self) | |
273 | intro = 'Py %s' % version.VERSION | |
274 | import imp | |
275 | module = imp.new_module('__main__') | |
276 | import __builtin__ | |
277 | module.__dict__['__builtins__'] = __builtin__ | |
278 | namespace = module.__dict__.copy() | |
279 | self.crust = crust.Crust(parent=self.notebook, intro=intro, locals=namespace) | |
280 | self.shell = self.crust.shell | |
281 | # Override the filling so that status messages go to the status bar. | |
282 | self.crust.filling.tree.setStatusText = self.SetStatusText | |
283 | # Override the shell so that status messages go to the status bar. | |
284 | self.shell.setStatusText = self.SetStatusText | |
285 | # Fix a problem with the sash shrinking to nothing. | |
286 | self.crust.filling.SetSashPosition(200) | |
287 | self.notebook.AddPage(page=self.crust, text='*Shell*', select=True) | |
288 | self.setEditor(self.crust.editor) | |
289 | self.crust.editor.SetFocus() | |
290 | ||
291 | def _editorChange(self, editor): | |
292 | """Editor change signal receiver.""" | |
293 | self.setEditor(editor) | |
294 | ||
295 | def OnAbout(self, event): | |
296 | """Display an About window.""" | |
297 | title = 'About PyAlaMode' | |
298 | text = 'Another fine, flaky program.' | |
299 | dialog = wx.MessageDialog(self, text, title, | |
300 | wx.OK | wx.ICON_INFORMATION) | |
301 | dialog.ShowModal() | |
302 | dialog.Destroy() | |
303 | ||
304 | def _updateTitle(self): | |
305 | """Show current title information.""" | |
306 | pass | |
307 | ## title = self.GetTitle() | |
308 | ## if self.bufferHasChanged(): | |
309 | ## if title.startswith('* '): | |
310 | ## pass | |
311 | ## else: | |
312 | ## self.SetTitle('* ' + title) | |
313 | ## else: | |
314 | ## if title.startswith('* '): | |
315 | ## self.SetTitle(title[2:]) | |
316 | ||
317 | def bufferCreate(self, filename=None): | |
318 | """Create new buffer.""" | |
319 | buffer = Buffer() | |
320 | panel = wx.Panel(parent=self.notebook, id=-1) | |
321 | wx.EVT_ERASE_BACKGROUND(panel, lambda x: x) | |
322 | editor = Editor(parent=panel) | |
323 | panel.editor = editor | |
324 | sizer = wx.BoxSizer(wx.VERTICAL) | |
325 | sizer.Add(editor.window, 1, wx.EXPAND) | |
326 | panel.SetSizer(sizer) | |
327 | panel.SetAutoLayout(True) | |
328 | sizer.Layout() | |
329 | buffer.addEditor(editor) | |
330 | buffer.open(filename) | |
331 | self.setEditor(editor) | |
332 | self.notebook.AddPage(page=panel, text=self.buffer.name, select=True) | |
333 | self.editor.setFocus() | |
334 | ||
335 | def bufferDestroy(self): | |
336 | """Destroy the current buffer.""" | |
337 | selection = self.notebook.GetSelection() | |
338 | ## print "Destroy Selection:", selection | |
339 | if selection > 0: # Don't destroy the PyCrust tab. | |
340 | if self.buffer: | |
341 | del self.buffers[self.buffer.id] | |
342 | self.buffer = None # Do this before DeletePage(). | |
343 | self.notebook.DeletePage(selection) | |
344 | ||
345 | def bufferNew(self): | |
346 | """Create new buffer.""" | |
347 | self.bufferCreate() | |
348 | cancel = False | |
349 | return cancel | |
350 | ||
351 | def bufferOpen(self): | |
352 | """Open file in buffer.""" | |
353 | filedir = '' | |
354 | if self.buffer and self.buffer.doc.filedir: | |
355 | filedir = self.buffer.doc.filedir | |
356 | result = openMultiple(directory=filedir) | |
357 | for path in result.paths: | |
358 | self.bufferCreate(path) | |
359 | cancel = False | |
360 | return cancel | |
361 | ||
362 | ||
363 | class EditorNotebook(wx.Notebook): | |
364 | """A notebook containing a page for each editor.""" | |
365 | ||
366 | def __init__(self, parent): | |
367 | """Create EditorNotebook instance.""" | |
368 | wx.Notebook.__init__(self, parent, id=-1, style=wx.NO_FULL_REPAINT_ON_RESIZE) | |
369 | wx.EVT_NOTEBOOK_PAGE_CHANGING(self, self.GetId(), | |
370 | self.OnPageChanging) | |
371 | wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self.GetId(), | |
372 | self.OnPageChanged) | |
373 | wx.EVT_IDLE(self, self.OnIdle) | |
374 | ||
375 | def OnIdle(self, event): | |
376 | """Event handler for idle time.""" | |
377 | self._updateTabText() | |
378 | event.Skip() | |
379 | ||
380 | def _updateTabText(self): | |
381 | """Show current buffer display name on all but first tab.""" | |
382 | size = 3 | |
383 | changed = ' **' | |
384 | unchanged = ' --' | |
385 | selection = self.GetSelection() | |
386 | if selection < 1: | |
387 | return | |
388 | text = self.GetPageText(selection) | |
389 | window = self.GetPage(selection) | |
390 | if not window.editor: | |
391 | return | |
392 | if text.endswith(changed) or text.endswith(unchanged): | |
393 | name = text[:-size] | |
394 | else: | |
395 | name = text | |
396 | if name != window.editor.buffer.name: | |
397 | text = window.editor.buffer.name | |
398 | if window.editor.buffer.hasChanged(): | |
399 | if text.endswith(changed): | |
400 | text = None | |
401 | elif text.endswith(unchanged): | |
402 | text = text[:-size] + changed | |
403 | else: | |
404 | text += changed | |
405 | else: | |
406 | if text.endswith(changed): | |
407 | text = text[:-size] + unchanged | |
408 | elif text.endswith(unchanged): | |
409 | text = None | |
410 | else: | |
411 | text += unchanged | |
412 | if text is not None: | |
413 | self.SetPageText(selection, text) | |
414 | self.Refresh() # Needed on Win98. | |
415 | ||
416 | def OnPageChanging(self, event): | |
417 | """Page changing event handler.""" | |
418 | event.Skip() | |
419 | ||
420 | def OnPageChanged(self, event): | |
421 | """Page changed event handler.""" | |
422 | new = event.GetSelection() | |
423 | window = self.GetPage(new) | |
424 | dispatcher.send(signal='EditorChange', sender=self, | |
425 | editor=window.editor) | |
426 | window.SetFocus() | |
427 | event.Skip() | |
428 | ||
429 | ||
430 | class EditorShellNotebookFrame(EditorNotebookFrame): | |
431 | """Frame containing a notebook containing EditorShellNotebooks.""" | |
432 | ||
433 | def __init__(self, parent=None, id=-1, title='PyAlaModeTest', | |
434 | pos=wx.DefaultPosition, size=(600, 400), | |
435 | style=wx.DEFAULT_FRAME_STYLE, | |
436 | filename=None, singlefile=False): | |
437 | """Create EditorShellNotebookFrame instance.""" | |
438 | self._singlefile = singlefile | |
439 | EditorNotebookFrame.__init__(self, parent, id, title, pos, | |
440 | size, style, filename) | |
441 | ||
442 | def _setup(self): | |
443 | """Setup prior to first buffer creation. | |
444 | ||
445 | Called automatically by base class during init.""" | |
446 | if not self._singlefile: | |
447 | self.notebook = EditorNotebook(parent=self) | |
448 | ||
449 | def OnAbout(self, event): | |
450 | """Display an About window.""" | |
451 | title = 'About PyAlaModePlus' | |
452 | text = 'Another fine, flaky program.' | |
453 | dialog = wx.MessageDialog(self, text, title, | |
454 | wx.OK | wx.ICON_INFORMATION) | |
455 | dialog.ShowModal() | |
456 | dialog.Destroy() | |
457 | ||
458 | def bufferCreate(self, filename=None): | |
459 | """Create new buffer.""" | |
460 | if self._singlefile: | |
461 | self.bufferDestroy() | |
462 | notebook = EditorShellNotebook(parent=self, | |
463 | filename=filename) | |
464 | self.notebook = notebook | |
465 | else: | |
466 | notebook = EditorShellNotebook(parent=self.notebook, | |
467 | filename=filename) | |
468 | self.setEditor(notebook.editor) | |
469 | if not self._singlefile: | |
470 | self.notebook.AddPage(page=notebook, text=self.buffer.name, | |
471 | select=True) | |
472 | self.editor.setFocus() | |
473 | ||
474 | def bufferDestroy(self): | |
475 | """Destroy the current buffer.""" | |
476 | if self.buffer: | |
477 | self.editor = None | |
478 | del self.buffers[self.buffer.id] | |
479 | self.buffer = None # Do this before DeletePage(). | |
480 | if self._singlefile: | |
481 | self.notebook.Destroy() | |
482 | self.notebook = None | |
483 | else: | |
484 | selection = self.notebook.GetSelection() | |
485 | ## print "Destroy Selection:", selection | |
486 | self.notebook.DeletePage(selection) | |
487 | ||
488 | def bufferNew(self): | |
489 | """Create new buffer.""" | |
490 | if self._singlefile and self.bufferHasChanged(): | |
491 | cancel = self.bufferSuggestSave() | |
492 | if cancel: | |
493 | return cancel | |
494 | self.bufferCreate() | |
495 | cancel = False | |
496 | return cancel | |
497 | ||
498 | def bufferOpen(self): | |
499 | """Open file in buffer.""" | |
500 | if self._singlefile and self.bufferHasChanged(): | |
501 | cancel = self.bufferSuggestSave() | |
502 | if cancel: | |
503 | return cancel | |
504 | filedir = '' | |
505 | if self.buffer and self.buffer.doc.filedir: | |
506 | filedir = self.buffer.doc.filedir | |
507 | if self._singlefile: | |
508 | result = openSingle(directory=filedir) | |
509 | if result.path: | |
510 | self.bufferCreate(result.path) | |
511 | else: | |
512 | result = openMultiple(directory=filedir) | |
513 | for path in result.paths: | |
514 | self.bufferCreate(path) | |
515 | cancel = False | |
516 | return cancel | |
517 | ||
518 | ||
519 | class EditorShellNotebook(wx.Notebook): | |
520 | """A notebook containing an editor page and a shell page.""" | |
521 | ||
522 | def __init__(self, parent, filename=None): | |
523 | """Create EditorShellNotebook instance.""" | |
524 | wx.Notebook.__init__(self, parent, id=-1) | |
525 | usePanels = True | |
526 | if usePanels: | |
527 | editorparent = editorpanel = wx.Panel(self, -1) | |
528 | shellparent = shellpanel = wx.Panel(self, -1) | |
529 | else: | |
530 | editorparent = self | |
531 | shellparent = self | |
532 | self.buffer = Buffer() | |
533 | self.editor = Editor(parent=editorparent) | |
534 | self.buffer.addEditor(self.editor) | |
535 | self.buffer.open(filename) | |
536 | self.shell = Shell(parent=shellparent, locals=self.buffer.interp.locals, | |
537 | style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER) | |
538 | self.buffer.interp.locals.clear() | |
539 | if usePanels: | |
540 | self.AddPage(page=editorpanel, text='Editor', select=True) | |
541 | self.AddPage(page=shellpanel, text='Shell') | |
542 | # Setup sizers | |
543 | editorsizer = wx.BoxSizer(wx.VERTICAL) | |
544 | editorsizer.Add(self.editor.window, 1, wx.EXPAND) | |
545 | editorpanel.SetSizer(editorsizer) | |
546 | editorpanel.SetAutoLayout(True) | |
547 | shellsizer = wx.BoxSizer(wx.VERTICAL) | |
548 | shellsizer.Add(self.shell, 1, wx.EXPAND) | |
549 | shellpanel.SetSizer(shellsizer) | |
550 | shellpanel.SetAutoLayout(True) | |
551 | else: | |
552 | self.AddPage(page=self.editor.window, text='Editor', select=True) | |
553 | self.AddPage(page=self.shell, text='Shell') | |
554 | self.editor.setFocus() | |
555 | wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self.GetId(), self.OnPageChanged) | |
556 | ||
557 | def OnPageChanged(self, event): | |
558 | """Page changed event handler.""" | |
559 | selection = event.GetSelection() | |
560 | if selection == 0: | |
561 | self.editor.setFocus() | |
562 | else: | |
563 | self.shell.SetFocus() | |
564 | event.Skip() | |
565 | ||
566 | def SetFocus(self): | |
567 | wx.Notebook.SetFocus(self) | |
568 | selection = self.GetSelection() | |
569 | if selection == 0: | |
570 | self.editor.setFocus() | |
571 | else: | |
572 | self.shell.SetFocus() | |
573 | ||
574 | ||
575 | class Editor: | |
576 | """Editor having an EditWindow.""" | |
577 | ||
578 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, | |
579 | size=wx.DefaultSize, | |
580 | style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER): | |
581 | """Create Editor instance.""" | |
582 | self.window = EditWindow(self, parent, id, pos, size, style) | |
583 | self.id = self.window.GetId() | |
584 | self.buffer = None | |
585 | # Assign handlers for keyboard events. | |
586 | wx.EVT_CHAR(self.window, self.OnChar) | |
587 | wx.EVT_KEY_DOWN(self.window, self.OnKeyDown) | |
588 | ||
589 | def _setBuffer(self, buffer, text): | |
590 | """Set the editor to a buffer. Private callback called by buffer.""" | |
591 | self.buffer = buffer | |
592 | self.autoCompleteKeys = buffer.interp.getAutoCompleteKeys() | |
593 | self.clearAll() | |
594 | self.setText(text) | |
595 | self.emptyUndoBuffer() | |
596 | self.setSavePoint() | |
597 | ||
598 | def destroy(self): | |
599 | """Destroy all editor objects.""" | |
600 | self.window.Destroy() | |
601 | ||
602 | def clearAll(self): | |
603 | self.window.ClearAll() | |
604 | ||
605 | def emptyUndoBuffer(self): | |
606 | self.window.EmptyUndoBuffer() | |
607 | ||
608 | def getStatus(self): | |
609 | """Return (filepath, line, column) status tuple.""" | |
610 | if self.window: | |
611 | pos = self.window.GetCurrentPos() | |
612 | line = self.window.LineFromPosition(pos) + 1 | |
613 | col = self.window.GetColumn(pos) | |
614 | if self.buffer: | |
615 | name = self.buffer.doc.filepath or self.buffer.name | |
616 | else: | |
617 | name = '' | |
618 | status = (name, line, col) | |
619 | return status | |
620 | else: | |
621 | return ('', 0, 0) | |
622 | ||
623 | def getText(self): | |
624 | """Return contents of editor.""" | |
625 | return self.window.GetText() | |
626 | ||
627 | def hasChanged(self): | |
628 | """Return True if contents have changed.""" | |
629 | return self.window.GetModify() | |
630 | ||
631 | def setFocus(self): | |
632 | """Set the input focus to the editor window.""" | |
633 | self.window.SetFocus() | |
634 | ||
635 | def setSavePoint(self): | |
636 | self.window.SetSavePoint() | |
637 | ||
638 | def setText(self, text): | |
639 | """Set contents of editor.""" | |
640 | self.window.SetText(text) | |
641 | ||
642 | def OnChar(self, event): | |
643 | """Keypress event handler. | |
644 | ||
645 | Only receives an event if OnKeyDown calls event.Skip() for the | |
646 | corresponding event.""" | |
647 | ||
648 | key = event.KeyCode() | |
649 | if key in self.autoCompleteKeys: | |
650 | # Usually the dot (period) key activates auto completion. | |
651 | if self.window.AutoCompActive(): | |
652 | self.window.AutoCompCancel() | |
653 | self.window.ReplaceSelection('') | |
654 | self.window.AddText(chr(key)) | |
655 | text, pos = self.window.GetCurLine() | |
656 | text = text[:pos] | |
657 | if self.window.autoComplete: | |
658 | self.autoCompleteShow(text) | |
659 | elif key == ord('('): | |
660 | # The left paren activates a call tip and cancels an | |
661 | # active auto completion. | |
662 | if self.window.AutoCompActive(): | |
663 | self.window.AutoCompCancel() | |
664 | self.window.ReplaceSelection('') | |
665 | self.window.AddText('(') | |
666 | text, pos = self.window.GetCurLine() | |
667 | text = text[:pos] | |
668 | self.autoCallTipShow(text) | |
669 | else: | |
670 | # Allow the normal event handling to take place. | |
671 | event.Skip() | |
672 | ||
673 | def OnKeyDown(self, event): | |
674 | """Key down event handler.""" | |
675 | ||
676 | key = event.KeyCode() | |
677 | # If the auto-complete window is up let it do its thing. | |
678 | if self.window.AutoCompActive(): | |
679 | event.Skip() | |
680 | return | |
681 | controlDown = event.ControlDown() | |
682 | altDown = event.AltDown() | |
683 | shiftDown = event.ShiftDown() | |
684 | # Let Ctrl-Alt-* get handled normally. | |
685 | if controlDown and altDown: | |
686 | event.Skip() | |
687 | # Increase font size. | |
688 | elif controlDown and key in (ord(']'),): | |
689 | dispatcher.send(signal='FontIncrease') | |
690 | # Decrease font size. | |
691 | elif controlDown and key in (ord('['),): | |
692 | dispatcher.send(signal='FontDecrease') | |
693 | # Default font size. | |
694 | elif controlDown and key in (ord('='),): | |
695 | dispatcher.send(signal='FontDefault') | |
696 | else: | |
697 | event.Skip() | |
698 | ||
699 | def autoCompleteShow(self, command): | |
700 | """Display auto-completion popup list.""" | |
701 | list = self.buffer.interp.getAutoCompleteList(command, | |
702 | includeMagic=self.window.autoCompleteIncludeMagic, | |
703 | includeSingle=self.window.autoCompleteIncludeSingle, | |
704 | includeDouble=self.window.autoCompleteIncludeDouble) | |
705 | if list: | |
706 | options = ' '.join(list) | |
707 | offset = 0 | |
708 | self.window.AutoCompShow(offset, options) | |
709 | ||
710 | def autoCallTipShow(self, command): | |
711 | """Display argument spec and docstring in a popup window.""" | |
712 | if self.window.CallTipActive(): | |
713 | self.window.CallTipCancel() | |
714 | (name, argspec, tip) = self.buffer.interp.getCallTip(command) | |
715 | if tip: | |
716 | dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip) | |
717 | if not self.window.autoCallTip: | |
718 | return | |
719 | if argspec: | |
720 | startpos = self.window.GetCurrentPos() | |
721 | self.window.AddText(argspec + ')') | |
722 | endpos = self.window.GetCurrentPos() | |
723 | self.window.SetSelection(endpos, startpos) | |
724 | if tip: | |
725 | curpos = self.window.GetCurrentPos() | |
726 | size = len(name) | |
727 | tippos = curpos - (size + 1) | |
728 | fallback = curpos - self.window.GetColumn(curpos) | |
729 | # In case there isn't enough room, only go back to the | |
730 | # fallback. | |
731 | tippos = max(tippos, fallback) | |
732 | self.window.CallTipShow(tippos, tip) | |
733 | self.window.CallTipSetHighlight(0, size) | |
734 | ||
735 | ||
736 | class EditWindow(editwindow.EditWindow): | |
737 | """EditWindow based on StyledTextCtrl.""" | |
738 | ||
739 | def __init__(self, editor, parent, id=-1, pos=wx.DefaultPosition, | |
740 | size=wx.DefaultSize, | |
741 | style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER): | |
742 | """Create EditWindow instance.""" | |
743 | editwindow.EditWindow.__init__(self, parent, id, pos, size, style) | |
744 | self.editor = editor | |
745 | ||
746 | ||
747 | class DialogResults: | |
748 | """DialogResults class.""" | |
749 | ||
750 | def __init__(self, returned): | |
751 | """Create wrapper for results returned by dialog.""" | |
752 | self.returned = returned | |
753 | self.positive = returned in (wx.ID_OK, wx.ID_YES) | |
754 | self.text = self._asString() | |
755 | ||
756 | ||
757 | def __repr__(self): | |
758 | return str(self.__dict__) | |
759 | ||
760 | def _asString(self): | |
761 | returned = self.returned | |
762 | if returned == wx.ID_OK: | |
763 | return "Ok" | |
764 | elif returned == wx.ID_CANCEL: | |
765 | return "Cancel" | |
766 | elif returned == wx.ID_YES: | |
767 | return "Yes" | |
768 | elif returned == wx.ID_NO: | |
769 | return "No" | |
770 | ||
771 | ||
772 | def fileDialog(parent=None, title='Open', directory='', filename='', | |
773 | wildcard='All Files (*.*)|*.*', | |
774 | style=wx.OPEN | wx.MULTIPLE): | |
775 | """File dialog wrapper function.""" | |
776 | dialog = wx.FileDialog(parent, title, directory, filename, | |
777 | wildcard, style) | |
778 | result = DialogResults(dialog.ShowModal()) | |
779 | if result.positive: | |
780 | result.paths = dialog.GetPaths() | |
781 | else: | |
782 | result.paths = [] | |
783 | dialog.Destroy() | |
784 | return result | |
785 | ||
786 | ||
787 | def openSingle(parent=None, title='Open', directory='', filename='', | |
788 | wildcard='All Files (*.*)|*.*', style=wx.OPEN): | |
789 | """File dialog wrapper function.""" | |
790 | dialog = wx.FileDialog(parent, title, directory, filename, | |
791 | wildcard, style) | |
792 | result = DialogResults(dialog.ShowModal()) | |
793 | if result.positive: | |
794 | result.path = dialog.GetPath() | |
795 | else: | |
796 | result.path = None | |
797 | dialog.Destroy() | |
798 | return result | |
799 | ||
800 | ||
801 | def openMultiple(parent=None, title='Open', directory='', filename='', | |
802 | wildcard='All Files (*.*)|*.*', | |
803 | style=wx.OPEN | wx.MULTIPLE): | |
804 | """File dialog wrapper function.""" | |
805 | return fileDialog(parent, title, directory, filename, wildcard, style) | |
806 | ||
807 | ||
808 | def saveSingle(parent=None, title='Save', directory='', filename='', | |
809 | wildcard='All Files (*.*)|*.*', | |
810 | style=wx.SAVE | wx.HIDE_READONLY | wx.OVERWRITE_PROMPT): | |
811 | """File dialog wrapper function.""" | |
812 | dialog = wx.FileDialog(parent, title, directory, filename, | |
813 | wildcard, style) | |
814 | result = DialogResults(dialog.ShowModal()) | |
815 | if result.positive: | |
816 | result.path = dialog.GetPath() | |
817 | else: | |
818 | result.path = None | |
819 | dialog.Destroy() | |
820 | return result | |
821 | ||
822 | ||
823 | def directory(parent=None, message='Choose a directory', path='', style=0, | |
824 | pos=wx.DefaultPosition, size=wx.DefaultSize): | |
825 | """Dir dialog wrapper function.""" | |
826 | dialog = wx.DirDialog(parent, message, path, style, pos, size) | |
827 | result = DialogResults(dialog.ShowModal()) | |
828 | if result.positive: | |
829 | result.path = dialog.GetPath() | |
830 | else: | |
831 | result.path = None | |
832 | dialog.Destroy() | |
833 | return result | |
834 | ||
835 | ||
836 | def messageDialog(parent=None, message='', title='Message box', | |
837 | style=wx.YES_NO | wx.CANCEL | wx.CENTRE | wx.ICON_QUESTION, | |
838 | pos=wx.DefaultPosition): | |
839 | """Message dialog wrapper function.""" | |
840 | dialog = wx.MessageDialog(parent, message, title, style, pos) | |
841 | result = DialogResults(dialog.ShowModal()) | |
842 | dialog.Destroy() | |
843 | return result |