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