]>
Commit | Line | Data |
---|---|---|
d14a1e28 | 1 | """PyAlaCarte and PyAlaMode editors.""" |
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 | 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 | ||
d14a1e28 RD |
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) | |
02b800ce | 33 | self.Bind(wx.EVT_IDLE, self.OnIdle) |
d14a1e28 RD |
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) | |
02b800ce | 140 | panel.Bind (wx.EVT_ERASE_BACKGROUND, lambda x: x) |
d14a1e28 RD |
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) | |
02b800ce | 321 | panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: x) |
d14a1e28 RD |
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.""" | |
c6ca8c02 | 368 | wx.Notebook.__init__(self, parent, id=-1, style=wx.CLIP_CHILDREN) |
02b800ce RD |
369 | self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging, id=self.GetId()) |
370 | self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=self.GetId()) | |
371 | self.Bind(wx.EVT_IDLE, self.OnIdle) | |
d14a1e28 RD |
372 | |
373 | def OnIdle(self, event): | |
374 | """Event handler for idle time.""" | |
375 | self._updateTabText() | |
376 | event.Skip() | |
377 | ||
378 | def _updateTabText(self): | |
379 | """Show current buffer display name on all but first tab.""" | |
380 | size = 3 | |
381 | changed = ' **' | |
382 | unchanged = ' --' | |
383 | selection = self.GetSelection() | |
384 | if selection < 1: | |
385 | return | |
386 | text = self.GetPageText(selection) | |
387 | window = self.GetPage(selection) | |
388 | if not window.editor: | |
389 | return | |
390 | if text.endswith(changed) or text.endswith(unchanged): | |
391 | name = text[:-size] | |
392 | else: | |
393 | name = text | |
394 | if name != window.editor.buffer.name: | |
395 | text = window.editor.buffer.name | |
396 | if window.editor.buffer.hasChanged(): | |
397 | if text.endswith(changed): | |
398 | text = None | |
399 | elif text.endswith(unchanged): | |
400 | text = text[:-size] + changed | |
401 | else: | |
402 | text += changed | |
403 | else: | |
404 | if text.endswith(changed): | |
405 | text = text[:-size] + unchanged | |
406 | elif text.endswith(unchanged): | |
407 | text = None | |
408 | else: | |
409 | text += unchanged | |
410 | if text is not None: | |
411 | self.SetPageText(selection, text) | |
412 | self.Refresh() # Needed on Win98. | |
413 | ||
414 | def OnPageChanging(self, event): | |
415 | """Page changing event handler.""" | |
416 | event.Skip() | |
417 | ||
418 | def OnPageChanged(self, event): | |
419 | """Page changed event handler.""" | |
420 | new = event.GetSelection() | |
421 | window = self.GetPage(new) | |
422 | dispatcher.send(signal='EditorChange', sender=self, | |
423 | editor=window.editor) | |
424 | window.SetFocus() | |
425 | event.Skip() | |
426 | ||
427 | ||
428 | class EditorShellNotebookFrame(EditorNotebookFrame): | |
429 | """Frame containing a notebook containing EditorShellNotebooks.""" | |
430 | ||
431 | def __init__(self, parent=None, id=-1, title='PyAlaModeTest', | |
432 | pos=wx.DefaultPosition, size=(600, 400), | |
433 | style=wx.DEFAULT_FRAME_STYLE, | |
434 | filename=None, singlefile=False): | |
435 | """Create EditorShellNotebookFrame instance.""" | |
436 | self._singlefile = singlefile | |
437 | EditorNotebookFrame.__init__(self, parent, id, title, pos, | |
438 | size, style, filename) | |
439 | ||
440 | def _setup(self): | |
441 | """Setup prior to first buffer creation. | |
442 | ||
443 | Called automatically by base class during init.""" | |
444 | if not self._singlefile: | |
445 | self.notebook = EditorNotebook(parent=self) | |
446 | ||
447 | def OnAbout(self, event): | |
448 | """Display an About window.""" | |
449 | title = 'About PyAlaModePlus' | |
450 | text = 'Another fine, flaky program.' | |
451 | dialog = wx.MessageDialog(self, text, title, | |
452 | wx.OK | wx.ICON_INFORMATION) | |
453 | dialog.ShowModal() | |
454 | dialog.Destroy() | |
455 | ||
456 | def bufferCreate(self, filename=None): | |
457 | """Create new buffer.""" | |
458 | if self._singlefile: | |
459 | self.bufferDestroy() | |
460 | notebook = EditorShellNotebook(parent=self, | |
461 | filename=filename) | |
462 | self.notebook = notebook | |
463 | else: | |
464 | notebook = EditorShellNotebook(parent=self.notebook, | |
465 | filename=filename) | |
466 | self.setEditor(notebook.editor) | |
467 | if not self._singlefile: | |
468 | self.notebook.AddPage(page=notebook, text=self.buffer.name, | |
469 | select=True) | |
470 | self.editor.setFocus() | |
471 | ||
472 | def bufferDestroy(self): | |
473 | """Destroy the current buffer.""" | |
474 | if self.buffer: | |
475 | self.editor = None | |
476 | del self.buffers[self.buffer.id] | |
477 | self.buffer = None # Do this before DeletePage(). | |
478 | if self._singlefile: | |
479 | self.notebook.Destroy() | |
480 | self.notebook = None | |
481 | else: | |
482 | selection = self.notebook.GetSelection() | |
483 | ## print "Destroy Selection:", selection | |
484 | self.notebook.DeletePage(selection) | |
485 | ||
486 | def bufferNew(self): | |
487 | """Create new buffer.""" | |
488 | if self._singlefile and self.bufferHasChanged(): | |
489 | cancel = self.bufferSuggestSave() | |
490 | if cancel: | |
491 | return cancel | |
492 | self.bufferCreate() | |
493 | cancel = False | |
494 | return cancel | |
495 | ||
496 | def bufferOpen(self): | |
497 | """Open file in buffer.""" | |
498 | if self._singlefile and self.bufferHasChanged(): | |
499 | cancel = self.bufferSuggestSave() | |
500 | if cancel: | |
501 | return cancel | |
502 | filedir = '' | |
503 | if self.buffer and self.buffer.doc.filedir: | |
504 | filedir = self.buffer.doc.filedir | |
505 | if self._singlefile: | |
506 | result = openSingle(directory=filedir) | |
507 | if result.path: | |
508 | self.bufferCreate(result.path) | |
509 | else: | |
510 | result = openMultiple(directory=filedir) | |
511 | for path in result.paths: | |
512 | self.bufferCreate(path) | |
513 | cancel = False | |
514 | return cancel | |
515 | ||
516 | ||
517 | class EditorShellNotebook(wx.Notebook): | |
518 | """A notebook containing an editor page and a shell page.""" | |
519 | ||
520 | def __init__(self, parent, filename=None): | |
521 | """Create EditorShellNotebook instance.""" | |
522 | wx.Notebook.__init__(self, parent, id=-1) | |
523 | usePanels = True | |
524 | if usePanels: | |
525 | editorparent = editorpanel = wx.Panel(self, -1) | |
526 | shellparent = shellpanel = wx.Panel(self, -1) | |
527 | else: | |
528 | editorparent = self | |
529 | shellparent = self | |
530 | self.buffer = Buffer() | |
531 | self.editor = Editor(parent=editorparent) | |
532 | self.buffer.addEditor(self.editor) | |
533 | self.buffer.open(filename) | |
534 | self.shell = Shell(parent=shellparent, locals=self.buffer.interp.locals, | |
535 | style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER) | |
536 | self.buffer.interp.locals.clear() | |
537 | if usePanels: | |
538 | self.AddPage(page=editorpanel, text='Editor', select=True) | |
539 | self.AddPage(page=shellpanel, text='Shell') | |
540 | # Setup sizers | |
541 | editorsizer = wx.BoxSizer(wx.VERTICAL) | |
542 | editorsizer.Add(self.editor.window, 1, wx.EXPAND) | |
543 | editorpanel.SetSizer(editorsizer) | |
544 | editorpanel.SetAutoLayout(True) | |
545 | shellsizer = wx.BoxSizer(wx.VERTICAL) | |
546 | shellsizer.Add(self.shell, 1, wx.EXPAND) | |
547 | shellpanel.SetSizer(shellsizer) | |
548 | shellpanel.SetAutoLayout(True) | |
549 | else: | |
550 | self.AddPage(page=self.editor.window, text='Editor', select=True) | |
551 | self.AddPage(page=self.shell, text='Shell') | |
552 | self.editor.setFocus() | |
02b800ce | 553 | self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=self.GetId()) |
d14a1e28 RD |
554 | |
555 | def OnPageChanged(self, event): | |
556 | """Page changed event handler.""" | |
557 | selection = event.GetSelection() | |
558 | if selection == 0: | |
559 | self.editor.setFocus() | |
560 | else: | |
561 | self.shell.SetFocus() | |
562 | event.Skip() | |
563 | ||
564 | def SetFocus(self): | |
565 | wx.Notebook.SetFocus(self) | |
566 | selection = self.GetSelection() | |
567 | if selection == 0: | |
568 | self.editor.setFocus() | |
569 | else: | |
570 | self.shell.SetFocus() | |
571 | ||
572 | ||
573 | class Editor: | |
574 | """Editor having an EditWindow.""" | |
575 | ||
576 | def __init__(self, parent, id=-1, pos=wx.DefaultPosition, | |
577 | size=wx.DefaultSize, | |
578 | style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER): | |
579 | """Create Editor instance.""" | |
580 | self.window = EditWindow(self, parent, id, pos, size, style) | |
581 | self.id = self.window.GetId() | |
582 | self.buffer = None | |
583 | # Assign handlers for keyboard events. | |
02b800ce RD |
584 | self.window.Bind(wx.EVT_CHAR, self.OnChar) |
585 | self.window.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) | |
d14a1e28 RD |
586 | |
587 | def _setBuffer(self, buffer, text): | |
588 | """Set the editor to a buffer. Private callback called by buffer.""" | |
589 | self.buffer = buffer | |
590 | self.autoCompleteKeys = buffer.interp.getAutoCompleteKeys() | |
591 | self.clearAll() | |
592 | self.setText(text) | |
593 | self.emptyUndoBuffer() | |
594 | self.setSavePoint() | |
595 | ||
596 | def destroy(self): | |
597 | """Destroy all editor objects.""" | |
598 | self.window.Destroy() | |
599 | ||
600 | def clearAll(self): | |
601 | self.window.ClearAll() | |
602 | ||
603 | def emptyUndoBuffer(self): | |
604 | self.window.EmptyUndoBuffer() | |
605 | ||
606 | def getStatus(self): | |
607 | """Return (filepath, line, column) status tuple.""" | |
9513c5b6 RD |
608 | if self.window: |
609 | pos = self.window.GetCurrentPos() | |
610 | line = self.window.LineFromPosition(pos) + 1 | |
611 | col = self.window.GetColumn(pos) | |
612 | if self.buffer: | |
613 | name = self.buffer.doc.filepath or self.buffer.name | |
614 | else: | |
615 | name = '' | |
616 | status = (name, line, col) | |
617 | return status | |
d14a1e28 | 618 | else: |
9513c5b6 | 619 | return ('', 0, 0) |
d14a1e28 RD |
620 | |
621 | def getText(self): | |
622 | """Return contents of editor.""" | |
623 | return self.window.GetText() | |
624 | ||
625 | def hasChanged(self): | |
626 | """Return True if contents have changed.""" | |
627 | return self.window.GetModify() | |
628 | ||
629 | def setFocus(self): | |
630 | """Set the input focus to the editor window.""" | |
631 | self.window.SetFocus() | |
632 | ||
633 | def setSavePoint(self): | |
634 | self.window.SetSavePoint() | |
635 | ||
636 | def setText(self, text): | |
637 | """Set contents of editor.""" | |
638 | self.window.SetText(text) | |
639 | ||
640 | def OnChar(self, event): | |
641 | """Keypress event handler. | |
642 | ||
643 | Only receives an event if OnKeyDown calls event.Skip() for the | |
644 | corresponding event.""" | |
645 | ||
a3cee65e | 646 | key = event.GetKeyCode() |
d14a1e28 RD |
647 | if key in self.autoCompleteKeys: |
648 | # Usually the dot (period) key activates auto completion. | |
649 | if self.window.AutoCompActive(): | |
650 | self.window.AutoCompCancel() | |
651 | self.window.ReplaceSelection('') | |
652 | self.window.AddText(chr(key)) | |
653 | text, pos = self.window.GetCurLine() | |
654 | text = text[:pos] | |
655 | if self.window.autoComplete: | |
656 | self.autoCompleteShow(text) | |
657 | elif key == ord('('): | |
658 | # The left paren activates a call tip and cancels an | |
659 | # active auto completion. | |
660 | if self.window.AutoCompActive(): | |
661 | self.window.AutoCompCancel() | |
662 | self.window.ReplaceSelection('') | |
663 | self.window.AddText('(') | |
664 | text, pos = self.window.GetCurLine() | |
665 | text = text[:pos] | |
666 | self.autoCallTipShow(text) | |
667 | else: | |
668 | # Allow the normal event handling to take place. | |
669 | event.Skip() | |
670 | ||
671 | def OnKeyDown(self, event): | |
672 | """Key down event handler.""" | |
673 | ||
a3cee65e | 674 | key = event.GetKeyCode() |
d14a1e28 RD |
675 | # If the auto-complete window is up let it do its thing. |
676 | if self.window.AutoCompActive(): | |
677 | event.Skip() | |
678 | return | |
679 | controlDown = event.ControlDown() | |
680 | altDown = event.AltDown() | |
681 | shiftDown = event.ShiftDown() | |
682 | # Let Ctrl-Alt-* get handled normally. | |
683 | if controlDown and altDown: | |
684 | event.Skip() | |
685 | # Increase font size. | |
686 | elif controlDown and key in (ord(']'),): | |
687 | dispatcher.send(signal='FontIncrease') | |
688 | # Decrease font size. | |
689 | elif controlDown and key in (ord('['),): | |
690 | dispatcher.send(signal='FontDecrease') | |
691 | # Default font size. | |
692 | elif controlDown and key in (ord('='),): | |
693 | dispatcher.send(signal='FontDefault') | |
694 | else: | |
695 | event.Skip() | |
696 | ||
697 | def autoCompleteShow(self, command): | |
698 | """Display auto-completion popup list.""" | |
699 | list = self.buffer.interp.getAutoCompleteList(command, | |
700 | includeMagic=self.window.autoCompleteIncludeMagic, | |
701 | includeSingle=self.window.autoCompleteIncludeSingle, | |
702 | includeDouble=self.window.autoCompleteIncludeDouble) | |
703 | if list: | |
704 | options = ' '.join(list) | |
705 | offset = 0 | |
706 | self.window.AutoCompShow(offset, options) | |
707 | ||
708 | def autoCallTipShow(self, command): | |
709 | """Display argument spec and docstring in a popup window.""" | |
710 | if self.window.CallTipActive(): | |
711 | self.window.CallTipCancel() | |
712 | (name, argspec, tip) = self.buffer.interp.getCallTip(command) | |
713 | if tip: | |
714 | dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip) | |
715 | if not self.window.autoCallTip: | |
716 | return | |
717 | if argspec: | |
718 | startpos = self.window.GetCurrentPos() | |
719 | self.window.AddText(argspec + ')') | |
720 | endpos = self.window.GetCurrentPos() | |
721 | self.window.SetSelection(endpos, startpos) | |
722 | if tip: | |
723 | curpos = self.window.GetCurrentPos() | |
724 | size = len(name) | |
725 | tippos = curpos - (size + 1) | |
726 | fallback = curpos - self.window.GetColumn(curpos) | |
727 | # In case there isn't enough room, only go back to the | |
728 | # fallback. | |
729 | tippos = max(tippos, fallback) | |
730 | self.window.CallTipShow(tippos, tip) | |
731 | self.window.CallTipSetHighlight(0, size) | |
732 | ||
733 | ||
734 | class EditWindow(editwindow.EditWindow): | |
735 | """EditWindow based on StyledTextCtrl.""" | |
736 | ||
737 | def __init__(self, editor, parent, id=-1, pos=wx.DefaultPosition, | |
738 | size=wx.DefaultSize, | |
739 | style=wx.CLIP_CHILDREN | wx.SUNKEN_BORDER): | |
740 | """Create EditWindow instance.""" | |
741 | editwindow.EditWindow.__init__(self, parent, id, pos, size, style) | |
742 | self.editor = editor | |
743 | ||
744 | ||
745 | class DialogResults: | |
746 | """DialogResults class.""" | |
747 | ||
748 | def __init__(self, returned): | |
749 | """Create wrapper for results returned by dialog.""" | |
750 | self.returned = returned | |
751 | self.positive = returned in (wx.ID_OK, wx.ID_YES) | |
752 | self.text = self._asString() | |
753 | ||
754 | ||
755 | def __repr__(self): | |
756 | return str(self.__dict__) | |
757 | ||
758 | def _asString(self): | |
759 | returned = self.returned | |
760 | if returned == wx.ID_OK: | |
761 | return "Ok" | |
762 | elif returned == wx.ID_CANCEL: | |
763 | return "Cancel" | |
764 | elif returned == wx.ID_YES: | |
765 | return "Yes" | |
766 | elif returned == wx.ID_NO: | |
767 | return "No" | |
768 | ||
769 | ||
770 | def fileDialog(parent=None, title='Open', directory='', filename='', | |
771 | wildcard='All Files (*.*)|*.*', | |
772 | style=wx.OPEN | wx.MULTIPLE): | |
773 | """File dialog wrapper function.""" | |
774 | dialog = wx.FileDialog(parent, title, directory, filename, | |
775 | wildcard, style) | |
776 | result = DialogResults(dialog.ShowModal()) | |
777 | if result.positive: | |
778 | result.paths = dialog.GetPaths() | |
779 | else: | |
780 | result.paths = [] | |
781 | dialog.Destroy() | |
782 | return result | |
783 | ||
784 | ||
785 | def openSingle(parent=None, title='Open', directory='', filename='', | |
786 | wildcard='All Files (*.*)|*.*', style=wx.OPEN): | |
787 | """File dialog wrapper function.""" | |
788 | dialog = wx.FileDialog(parent, title, directory, filename, | |
789 | wildcard, style) | |
790 | result = DialogResults(dialog.ShowModal()) | |
791 | if result.positive: | |
792 | result.path = dialog.GetPath() | |
793 | else: | |
794 | result.path = None | |
795 | dialog.Destroy() | |
796 | return result | |
797 | ||
798 | ||
799 | def openMultiple(parent=None, title='Open', directory='', filename='', | |
800 | wildcard='All Files (*.*)|*.*', | |
801 | style=wx.OPEN | wx.MULTIPLE): | |
802 | """File dialog wrapper function.""" | |
803 | return fileDialog(parent, title, directory, filename, wildcard, style) | |
804 | ||
805 | ||
806 | def saveSingle(parent=None, title='Save', directory='', filename='', | |
807 | wildcard='All Files (*.*)|*.*', | |
dc690d95 | 808 | style=wx.SAVE | wx.OVERWRITE_PROMPT): |
d14a1e28 RD |
809 | """File dialog wrapper function.""" |
810 | dialog = wx.FileDialog(parent, title, directory, filename, | |
811 | wildcard, style) | |
812 | result = DialogResults(dialog.ShowModal()) | |
813 | if result.positive: | |
814 | result.path = dialog.GetPath() | |
815 | else: | |
816 | result.path = None | |
817 | dialog.Destroy() | |
818 | return result | |
819 | ||
820 | ||
821 | def directory(parent=None, message='Choose a directory', path='', style=0, | |
822 | pos=wx.DefaultPosition, size=wx.DefaultSize): | |
823 | """Dir dialog wrapper function.""" | |
824 | dialog = wx.DirDialog(parent, message, path, style, pos, size) | |
825 | result = DialogResults(dialog.ShowModal()) | |
826 | if result.positive: | |
827 | result.path = dialog.GetPath() | |
828 | else: | |
829 | result.path = None | |
830 | dialog.Destroy() | |
831 | return result | |
832 | ||
833 | ||
834 | def messageDialog(parent=None, message='', title='Message box', | |
835 | style=wx.YES_NO | wx.CANCEL | wx.CENTRE | wx.ICON_QUESTION, | |
836 | pos=wx.DefaultPosition): | |
837 | """Message dialog wrapper function.""" | |
838 | dialog = wx.MessageDialog(parent, message, title, style, pos) | |
839 | result = DialogResults(dialog.ShowModal()) | |
840 | dialog.Destroy() | |
841 | return result |