diff --git a/wxPython/wxPython/py/__init__.py b/wxPython/wxPython/py/__init__.py index 0b331a6f53..fde6e5d75f 100644 --- a/wxPython/wxPython/py/__init__.py +++ b/wxPython/wxPython/py/__init__.py @@ -4,12 +4,12 @@ __author__ = "Patrick K. O'Brien " __cvsid__ = "$Id$" __revision__ = "$Revision$"[11:-2] -import base import buffer import crust import dispatcher import document import editor +import editwindow import filling import frame import images diff --git a/wxPython/wxPython/py/base.py b/wxPython/wxPython/py/base.py index 43a0a75e7b..1427ec00d7 100644 --- a/wxPython/wxPython/py/base.py +++ b/wxPython/wxPython/py/base.py @@ -1,4 +1,4 @@ -"""Base editor.""" +"""EditWindow class.""" __author__ = "Patrick K. O'Brien " __cvsid__ = "$Id$" @@ -42,14 +42,14 @@ else: # GTK } -class Editor(stc.wxStyledTextCtrl): - """Editor based on StyledTextCtrl.""" +class EditWindow(stc.wxStyledTextCtrl): + """EditWindow based on StyledTextCtrl.""" revision = __revision__ def __init__(self, parent, id=-1, pos=wx.wxDefaultPosition, size=wx.wxDefaultSize, style=wx.wxCLIP_CHILDREN | wx.wxSUNKEN_BORDER): - """Create an Editor instance.""" + """Create EditWindow instance.""" stc.wxStyledTextCtrl.__init__(self, parent, id, pos, size, style) self.__config() stc.EVT_STC_UPDATEUI(self, id, self.OnUpdateUI) diff --git a/wxPython/wxPython/py/buffer.py b/wxPython/wxPython/py/buffer.py index a5a7ad902a..5a5f6b9d53 100644 --- a/wxPython/wxPython/py/buffer.py +++ b/wxPython/wxPython/py/buffer.py @@ -6,6 +6,7 @@ __revision__ = "$Revision$"[11:-2] from wxPython import wx +from interpreter import Interpreter import imp import os import sys @@ -24,13 +25,14 @@ class Buffer: id = 0 - def __init__(self, editor, interp, filename=None): + def __init__(self, filename=None): """Create a Buffer instance.""" Buffer.id += 1 self.id = Buffer.id + self.interp = Interpreter(locals={}) self.name = '' - self.editor = editor - self.interp = interp + self.editors = {} + self.editor = None self.modules = sys.modules.keys() self.syspath = sys.path[:] while True: @@ -45,18 +47,17 @@ class Buffer: break self.open(filename) - def getStatus(self): - """Return (filepath, line, column) status tuple.""" - editor = self.editor - pos = editor.GetCurrentPos() - line = editor.LineFromPosition(pos) + 1 - col = editor.GetColumn(pos) - status = (self.doc.filepath or self.name, line, col) - return status + def addEditor(self, editor): + """Add an editor.""" + self.editor = editor + self.editors[editor.id] = editor def hasChanged(self): """Return True if text in editor has changed since last save.""" - return self.editor.GetModify() + if self.editor: + return self.editor.hasChanged() + else: + return False def new(self, filepath): """New empty buffer.""" @@ -72,14 +73,16 @@ class Buffer: self.doc = document.Document(filename) self.name = self.doc.filename or ('Untitled:' + str(self.id)) self.modulename = self.doc.filebase - if self.doc.filepath and os.path.exists(self.doc.filepath): - self.editor.ClearAll() - self.editor.SetText(self.doc.read()) - self.editor.EmptyUndoBuffer() - self.editor.SetSavePoint() - self.confirmed = True + # XXX This should really make sure filedir is first item in syspath. + # XXX Or maybe this should be moved to the update namespace method. if self.doc.filedir and self.doc.filedir not in self.syspath: + # To create the proper context for updateNamespace. self.syspath.insert(0, self.doc.filedir) + if self.doc.filepath and os.path.exists(self.doc.filepath): + self.confirmed = True + if self.editor: + text = self.doc.read() + self.editor._setBuffer(buffer=self, text=text) def overwriteConfirm(filepath): """Confirm overwriting an existing file.""" @@ -95,43 +98,36 @@ class Buffer: if not self.confirmed: self.confirmed = self.overwriteConfirm(filepath) if self.confirmed: - self.doc.write(self.editor.GetText()) - self.editor.SetSavePoint() + self.doc.write(self.editor.getText()) + if self.editor: + self.editor.setSavePoint() def saveAs(self, filename): """Save buffer.""" self.doc = document.Document(filename) self.name = self.doc.filename self.modulename = self.doc.filebase - filepath = self.doc.filepath - if not filepath: - return # XXX Get filename -## if not os.path.exists(filepath): - self.confirmed = True - if not self.confirmed: - self.confirmed = self.overwriteConfirm(filepath) - if self.confirmed: - self.doc.write(self.editor.GetText()) - self.editor.SetSavePoint() + self.save() def updateNamespace(self): """Update the namespace for autocompletion and calltips. Return True if updated, False if there was an error.""" - backup = self.interp.locals + if not self.interp or not hasattr(self.editor, 'getText'): + return False syspath = sys.path sys.path = self.syspath - code = self.editor.GetText() + code = self.editor.getText() module = imp.new_module(str(self.modulename)) namespace = module.__dict__.copy() try: try: exec code in namespace except: - self.interp.locals = backup return False else: - self.interp.locals = namespace + self.interp.locals.clear() + self.interp.locals.update(namespace) return True finally: sys.path = syspath diff --git a/wxPython/wxPython/py/crust.py b/wxPython/wxPython/py/crust.py index b5b7894886..b06e6b6984 100644 --- a/wxPython/wxPython/py/crust.py +++ b/wxPython/wxPython/py/crust.py @@ -38,7 +38,7 @@ class Crust(wx.wxSplitterWindow): self.shell = Shell(parent=self, introText=intro, locals=locals, InterpClass=InterpClass, *args, **kwds) - self.buffer = self.shell.buffer + self.editor = self.shell if rootObject is None: rootObject = self.shell.interp.locals self.notebook = wx.wxNotebook(parent=self, id=-1) diff --git a/wxPython/wxPython/py/document.py b/wxPython/wxPython/py/document.py index 6e7b5001a0..fd32591e88 100644 --- a/wxPython/wxPython/py/document.py +++ b/wxPython/wxPython/py/document.py @@ -30,11 +30,14 @@ class Document: def read(self): """Return contents of file.""" - f = file(self.filepath, 'rb') - try: - return f.read() - finally: - f.close() + if self.filepath and os.path.exists(self.filepath): + f = file(self.filepath, 'rb') + try: + return f.read() + finally: + f.close() + else: + return '' def write(self, text): """Write text to file.""" diff --git a/wxPython/wxPython/py/editor.py b/wxPython/wxPython/py/editor.py index 4b2ad1be00..78ff5e235c 100644 --- a/wxPython/wxPython/py/editor.py +++ b/wxPython/wxPython/py/editor.py @@ -6,13 +6,12 @@ __revision__ = "$Revision$"[11:-2] from wxPython import wx -import base -import buffer +from buffer import Buffer import crust import dispatcher +import editwindow import frame -import interpreter -import shell +from shell import Shell import version try: @@ -28,12 +27,13 @@ class EditorFrame(frame.Frame): def __init__(self, parent=None, id=-1, title='PyAlaCarte', pos=wx.wxDefaultPosition, size=(800, 600), style=wx.wxDEFAULT_FRAME_STYLE, filename=None): - """Create an EditorFrame instance.""" + """Create EditorFrame instance.""" frame.Frame.__init__(self, parent, id, title, pos, size, style) - self._buffers = {} - self._buffer = None # Current buffer. + self.buffers = {} + self.buffer = None # Current buffer. self.editor = None - self._statusText = title + ' - the tastiest Python editor.' + self._defaultText = title + ' - the tastiest Python editor.' + self._statusText = self._defaultText self.SetStatusText(self._statusText) wx.EVT_IDLE(self, self.OnIdle) self._setup() @@ -46,6 +46,11 @@ class EditorFrame(frame.Frame): Useful for subclasses.""" pass + def setEditor(self, editor): + self.editor = editor + self.buffer = self.editor.buffer + self.buffers[self.buffer.id] = self.buffer + def OnAbout(self, event): """Display an About window.""" title = 'About PyAlaCarte' @@ -57,8 +62,8 @@ class EditorFrame(frame.Frame): def OnClose(self, event): """Event handler for closing.""" - for buffer in self._buffers.values(): - self._buffer = buffer + for buffer in self.buffers.values(): + self.buffer = buffer if buffer.hasChanged(): cancel = self.bufferSuggestSave() if cancel and event.CanVeto(): @@ -74,12 +79,14 @@ class EditorFrame(frame.Frame): def _updateStatus(self): """Show current status information.""" - if self._buffer: - status = self._buffer.getStatus() + if self.editor and hasattr(self.editor, 'getStatus'): + status = self.editor.getStatus() text = 'File: %s | Line: %d | Column: %d' % status - if text != self._statusText: - self.SetStatusText(text) - self._statusText = text + else: + text = self._defaultText + if text != self._statusText: + self.SetStatusText(text) + self._statusText = text def _updateTitle(self): """Show current title information.""" @@ -95,7 +102,7 @@ class EditorFrame(frame.Frame): def hasBuffer(self): """Return True if there is a current buffer.""" - if self._buffer: + if self.buffer: return True else: return False @@ -113,25 +120,26 @@ class EditorFrame(frame.Frame): def bufferCreate(self, filename=None): """Create new buffer.""" self.bufferDestroy() - interp = interpreter.Interpreter(locals={}) - self.editor = Editor(interp=interp, parent=self, filename=filename) - self._buffer = self.editor.buffer - self._buffers[self._buffer.id] = self._buffer - self._buffer.editor.SetFocus() + buffer = Buffer() + editor = Editor(parent=self) + buffer.addEditor(editor) + buffer.open(filename) + self.setEditor(editor) + self.editor.setFocus() def bufferDestroy(self): """Destroy the current buffer.""" - if self._buffer: - del self._buffers[self._buffer.id] - self._buffer = None - if self.editor: - self.editor.Destroy() + if self.buffer: + for editor in self.buffer.editors.values(): + editor.destroy() self.editor = None + del self.buffers[self.buffer.id] + self.buffer = None def bufferHasChanged(self): """Return True if buffer has changed since last save.""" - if self._buffer: - return self._buffer.hasChanged() + if self.buffer: + return self.buffer.hasChanged() else: return False @@ -152,8 +160,8 @@ class EditorFrame(frame.Frame): if cancel: return cancel filedir = '' - if self._buffer and self._buffer.doc.filedir: - filedir = self._buffer.doc.filedir + if self.buffer and self.buffer.doc.filedir: + filedir = self.buffer.doc.filedir result = openSingle(directory=filedir) if result.path: self.bufferCreate(result.path) @@ -170,8 +178,8 @@ class EditorFrame(frame.Frame): def bufferSave(self): """Save buffer to its file.""" - if self._buffer.doc.filepath: - self._buffer.save() + if self.buffer.doc.filepath: + self.buffer.save() cancel = False else: cancel = self.bufferSaveAs() @@ -179,16 +187,16 @@ class EditorFrame(frame.Frame): def bufferSaveAs(self): """Save buffer to a new filename.""" - if self.bufferHasChanged() and self._buffer.doc.filepath: + if self.bufferHasChanged() and self.buffer.doc.filepath: cancel = self.bufferSuggestSave() if cancel: return cancel filedir = '' - if self._buffer and self._buffer.doc.filedir: - filedir = self._buffer.doc.filedir + if self.buffer and self.buffer.doc.filedir: + filedir = self.buffer.doc.filedir result = saveSingle(directory=filedir) if result.path: - self._buffer.saveAs(result.path) + self.buffer.saveAs(result.path) cancel = False else: cancel = True @@ -199,7 +207,7 @@ class EditorFrame(frame.Frame): result = messageDialog(parent=None, message='%s has changed.\n' 'Would you like to save it first' - '?' % self._buffer.name, + '?' % self.buffer.name, title='Save current file?') if result.positive: cancel = self.bufferSave() @@ -209,7 +217,7 @@ class EditorFrame(frame.Frame): def updateNamespace(self): """Update the buffer namespace for autocompletion and calltips.""" - if self._buffer.updateNamespace(): + if self.buffer.updateNamespace(): self.SetStatusText('Namespace updated') else: self.SetStatusText('Error executing, unable to update namespace') @@ -221,24 +229,26 @@ class EditorNotebookFrame(EditorFrame): def __init__(self, parent=None, id=-1, title='PyAlaMode', pos=wx.wxDefaultPosition, size=(800, 600), style=wx.wxDEFAULT_FRAME_STYLE, filename=None): - """Create an EditorNotebookFrame instance.""" + """Create EditorNotebookFrame instance.""" + self.notebook = None EditorFrame.__init__(self, parent, id, title, pos, size, style, filename) + if self.notebook: + dispatcher.connect(receiver=self._editorChange, + signal='EditorChange', sender=self.notebook) def _setup(self): """Setup prior to first buffer creation. - Useful for subclasses.""" - self._notebook = BufferNotebook(parent=self) - dispatcher.connect(receiver=self._bufferChange, - signal='BufferChange', sender=self._notebook) + Called automatically by base class during init.""" + self.notebook = EditorNotebook(parent=self) intro = 'PyCrust %s' % version.VERSION import imp module = imp.new_module('__main__') import __builtin__ module.__dict__['__builtins__'] = __builtin__ namespace = module.__dict__.copy() - self.crust = crust.Crust(parent=self._notebook, intro=intro, locals=namespace) + self.crust = crust.Crust(parent=self.notebook, intro=intro, locals=namespace) self.shell = self.crust.shell # Override the filling so that status messages go to the status bar. self.crust.filling.tree.setStatusText = self.SetStatusText @@ -246,14 +256,12 @@ class EditorNotebookFrame(EditorFrame): self.shell.setStatusText = self.SetStatusText # Fix a problem with the sash shrinking to nothing. self.crust.filling.SetSashPosition(200) - self._notebook.AddPage(page=self.crust, text='PyCrust', select=True) - self._buffer = self.crust.buffer - self._buffers[self._buffer.id] = self._buffer - self._buffer.editor.SetFocus() + self.notebook.AddPage(page=self.crust, text='PyCrust', select=True) + self.setEditor(self.crust.editor) - def _bufferChange(self, buffer): - """Buffer change signal receiver.""" - self._buffer = buffer + def _editorChange(self, editor): + """Editor change signal receiver.""" + self.setEditor(editor) def OnAbout(self, event): """Display an About window.""" @@ -278,24 +286,24 @@ class EditorNotebookFrame(EditorFrame): def bufferCreate(self, filename=None): """Create new buffer.""" - interp = interpreter.Interpreter(locals={}) - editor = Editor(interp=interp, parent=self._notebook, - filename=filename) - self._buffer = editor.buffer - self._buffers[self._buffer.id] = self._buffer - self._notebook.AddPage(page=editor, text=self._buffer.name, - select=True) - self._buffer.editor.SetFocus() + buffer = Buffer() + editor = Editor(parent=self.notebook) + buffer.addEditor(editor) + buffer.open(filename) + self.setEditor(editor) + self.notebook.AddPage(page=editor.window, text=self.buffer.name, + select=True) + self.editor.setFocus() def bufferDestroy(self): """Destroy the current buffer.""" - selection = self._notebook.GetSelection() + selection = self.notebook.GetSelection() ## print "Destroy Selection:", selection if selection > 0: # Don't destroy the PyCrust tab. - if self._buffer: - del self._buffers[self._buffer.id] - self._buffer = None # Do this before DeletePage(). - self._notebook.DeletePage(selection) + if self.buffer: + del self.buffers[self.buffer.id] + self.buffer = None # Do this before DeletePage(). + self.notebook.DeletePage(selection) def bufferNew(self): """Create new buffer.""" @@ -306,8 +314,8 @@ class EditorNotebookFrame(EditorFrame): def bufferOpen(self): """Open file in buffer.""" filedir = '' - if self._buffer and self._buffer.doc.filedir: - filedir = self._buffer.doc.filedir + if self.buffer and self.buffer.doc.filedir: + filedir = self.buffer.doc.filedir result = openMultiple(directory=filedir) for path in result.paths: self.bufferCreate(path) @@ -315,14 +323,16 @@ class EditorNotebookFrame(EditorFrame): return cancel -class BufferNotebook(wx.wxNotebook): - """A notebook containing a page for each buffer.""" +class EditorNotebook(wx.wxNotebook): + """A notebook containing a page for each editor.""" def __init__(self, parent): - """Create a BufferNotebook instance.""" + """Create EditorNotebook instance.""" wx.wxNotebook.__init__(self, parent, id=-1) - wx.EVT_NOTEBOOK_PAGE_CHANGING(self, self.GetId(), self.OnPageChanging) - wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self.GetId(), self.OnPageChanged) + wx.EVT_NOTEBOOK_PAGE_CHANGING(self, self.GetId(), + self.OnPageChanging) + wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self.GetId(), + self.OnPageChanged) def OnPageChanging(self, event): """Page changing event handler.""" @@ -338,87 +348,70 @@ class BufferNotebook(wx.wxNotebook): ## print "Changed from:", old new = event.GetSelection() ## print "Changed to new:", new - page = self.GetPage(new) - buffer = page.buffer - buffer.editor.SetFocus() - dispatcher.send(signal='BufferChange', sender=self, buffer=buffer) + window = self.GetPage(new) + dispatcher.send(signal='EditorChange', sender=self, + editor=window.editor) + window.SetFocus() event.Skip() -class BufferEditorShellNotebookFrame(EditorFrame): - """Frame containing one or more editor notebooks.""" +class EditorShellNotebookFrame(EditorNotebookFrame): + """Frame containing a notebook containing EditorShellNotebooks.""" def __init__(self, parent=None, id=-1, title='PyAlaMode', pos=wx.wxDefaultPosition, size=(600, 400), style=wx.wxDEFAULT_FRAME_STYLE, filename=None, singlefile=False): - """Create a BufferEditorShellNotebookFrame instance.""" + """Create EditorShellNotebookFrame instance.""" self._singlefile = singlefile - EditorFrame.__init__(self, parent, id, title, pos, - size, style, filename) + EditorNotebookFrame.__init__(self, parent, id, title, pos, + size, style, filename) def _setup(self): """Setup prior to first buffer creation. - Useful for subclasses.""" + Called automatically by base class during init.""" if not self._singlefile: - self._notebook = BufferNotebook(parent=self) - dispatcher.connect(receiver=self._bufferChange, - signal='BufferChange', sender=self._notebook) - - def _bufferChange(self, buffer): - """Buffer change signal receiver.""" - self._buffer = buffer + self.notebook = EditorNotebook(parent=self) def OnAbout(self, event): """Display an About window.""" - title = 'About PyAlaMode' + title = 'About PyAlaModePlus' text = 'Another fine, flaky program.' dialog = wx.wxMessageDialog(self, text, title, wx.wxOK | wx.wxICON_INFORMATION) dialog.ShowModal() dialog.Destroy() - def _updateTitle(self): - """Show current title information.""" - title = self.GetTitle() - if self.bufferHasChanged(): - if title.startswith('* '): - pass - else: - self.SetTitle('* ' + title) - else: - if title.startswith('* '): - self.SetTitle(title[2:]) - def bufferCreate(self, filename=None): """Create new buffer.""" if self._singlefile: self.bufferDestroy() - notebook = self._notebook = EditorShellNotebook(parent=self, - filename=filename) - else: - notebook = EditorShellNotebook(parent=self._notebook, + notebook = EditorShellNotebook(parent=self, filename=filename) - self._buffer = notebook.buffer + self.notebook = notebook + else: + notebook = EditorShellNotebook(parent=self.notebook, + filename=filename) + self.setEditor(notebook.editor) if not self._singlefile: - self._notebook.AddPage(page=notebook, text=self._buffer.name, - select=True) - self._buffers[self._buffer.id] = self._buffer - self._buffer.editor.SetFocus() + self.notebook.AddPage(page=notebook, text=self.buffer.name, + select=True) + self.editor.setFocus() def bufferDestroy(self): """Destroy the current buffer.""" - if self._buffer: - del self._buffers[self._buffer.id] - self._buffer = None # Do this before DeletePage(). + if self.buffer: + self.editor = None + del self.buffers[self.buffer.id] + self.buffer = None # Do this before DeletePage(). if self._singlefile: - self._notebook.Destroy() - self._notebook = None + self.notebook.Destroy() + self.notebook = None else: - selection = self._notebook.GetSelection() - print "Destroy Selection:", selection - self._notebook.DeletePage(selection) + selection = self.notebook.GetSelection() +## print "Destroy Selection:", selection + self.notebook.DeletePage(selection) def bufferNew(self): """Create new buffer.""" @@ -437,8 +430,8 @@ class BufferEditorShellNotebookFrame(EditorFrame): if cancel: return cancel filedir = '' - if self._buffer and self._buffer.doc.filedir: - filedir = self._buffer.doc.filedir + if self.buffer and self.buffer.doc.filedir: + filedir = self.buffer.doc.filedir if self._singlefile: result = openSingle(directory=filedir) if result.path: @@ -451,94 +444,125 @@ class BufferEditorShellNotebookFrame(EditorFrame): return cancel -class BufferEditorShellNotebook(wx.wxNotebook): - """A notebook containing a page for each buffer.""" - - def __init__(self, parent): - """Create a BufferEditorShellNotebook instance.""" - wx.wxNotebook.__init__(self, parent, id=-1) - wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self.GetId(), self.OnPageChanged) - - def OnPageChanged(self, event): - """Page changed event handler.""" -## old = event.GetOldSelection() -## print "Changed from old:", old - new = event.GetSelection() -## print "Changed to new:", new - page = self.GetPage(new) - buffer = page.buffer - subselection = page.GetSelection() - page.focus(subselection) - dispatcher.send(signal='BufferChange', sender=self, buffer=buffer) - event.Skip() - - class EditorShellNotebook(wx.wxNotebook): """A notebook containing an editor page and a shell page.""" def __init__(self, parent, filename=None): - """Create an EditorShellNotebook instance.""" + """Create EditorShellNotebook instance.""" wx.wxNotebook.__init__(self, parent, id=-1) usePanels = True if usePanels: - shellparent = shellpanel = wx.wxPanel(self, -1) editorparent = editorpanel = wx.wxPanel(self, -1) + shellparent = shellpanel = wx.wxPanel(self, -1) else: - shellparent = self editorparent = self - self.shell = shell.Shell(parent=shellparent, - style=wx.wxCLIP_CHILDREN | wx.wxSUNKEN_BORDER) - self.editor = Editor(interp=self.shell.interp, parent=editorparent, - filename=filename) + shellparent = self + self.buffer = Buffer() + self.editor = Editor(parent=editorparent) + self.buffer.addEditor(self.editor) + self.buffer.open(filename) + self.shell = Shell(parent=shellparent, locals=self.buffer.interp.locals, + style=wx.wxCLIP_CHILDREN | wx.wxSUNKEN_BORDER) + self.buffer.interp.locals.clear() if usePanels: - self.AddPage(page=editorpanel, text='File', select=True) + self.AddPage(page=editorpanel, text='Editor', select=True) self.AddPage(page=shellpanel, text='Shell') # Setup sizers + editorsizer = wx.wxBoxSizer(wx.wxVERTICAL) + editorsizer.Add(self.editor.window, 1, wx.wxEXPAND) + editorpanel.SetSizer(editorsizer) + editorpanel.SetAutoLayout(True) shellsizer = wx.wxBoxSizer(wx.wxVERTICAL) shellsizer.Add(self.shell, 1, wx.wxEXPAND) shellpanel.SetSizer(shellsizer) shellpanel.SetAutoLayout(True) - editorsizer = wx.wxBoxSizer(wx.wxVERTICAL) - editorsizer.Add(self.editor, 1, wx.wxEXPAND) - editorpanel.SetSizer(editorsizer) - editorpanel.SetAutoLayout(True) else: - self.AddPage(page=self.editor, text='File', select=True) + self.AddPage(page=self.editor.window, text='Editor', select=True) self.AddPage(page=self.shell, text='Shell') - self.buffer = self.editor.buffer - self.editor.SetFocus() + self.editor.setFocus() wx.EVT_NOTEBOOK_PAGE_CHANGED(self, self.GetId(), self.OnPageChanged) def OnPageChanged(self, event): """Page changed event handler.""" selection = event.GetSelection() - self.focus(selection) + if selection == 0: + self.editor.setFocus() + else: + self.shell.SetFocus() event.Skip() - def focus(self, selection): + def SetFocus(self): + wx.wxNotebook.SetFocus(self) + selection = self.GetSelection() if selection == 0: - self.editor.SetFocus() + self.editor.setFocus() else: self.shell.SetFocus() -class Editor(base.Editor): - """Editor based on StyledTextCtrl.""" +class Editor: + """Editor having an EditWindow.""" - def __init__(self, interp, parent, id=-1, pos=wx.wxDefaultPosition, + def __init__(self, parent, id=-1, pos=wx.wxDefaultPosition, size=wx.wxDefaultSize, - style=wx.wxCLIP_CHILDREN | wx.wxSUNKEN_BORDER, - filename=None): - """Create a Editor instance.""" - base.Editor.__init__(self, parent, id, pos, size, style) - self.interp = interp - # Find out for which keycodes the interpreter will autocomplete. - self.autoCompleteKeys = self.interp.getAutoCompleteKeys() + style=wx.wxCLIP_CHILDREN | wx.wxSUNKEN_BORDER): + """Create Editor instance.""" + self.window = EditWindow(self, parent, id, pos, size, style) + self.id = self.window.GetId() + self.buffer = None # Assign handlers for keyboard events. - wx.EVT_CHAR(self, self.OnChar) - wx.EVT_KEY_DOWN(self, self.OnKeyDown) - self.buffer = buffer.Buffer(editor=self, interp=self.interp, - filename=filename) + wx.EVT_CHAR(self.window, self.OnChar) + wx.EVT_KEY_DOWN(self.window, self.OnKeyDown) + + def _setBuffer(self, buffer, text): + """Set the editor to a buffer. Private callback called by buffer.""" + self.buffer = buffer + self.autoCompleteKeys = buffer.interp.getAutoCompleteKeys() + self.clearAll() + self.setText(text) + self.emptyUndoBuffer() + self.setSavePoint() + + def destroy(self): + """Destroy all editor objects.""" + self.window.Destroy() + + def clearAll(self): + self.window.ClearAll() + + def emptyUndoBuffer(self): + self.window.EmptyUndoBuffer() + + def getStatus(self): + """Return (filepath, line, column) status tuple.""" + pos = self.window.GetCurrentPos() + line = self.window.LineFromPosition(pos) + 1 + col = self.window.GetColumn(pos) + if self.buffer: + name = self.buffer.doc.filepath or self.buffer.name + else: + name = '' + status = (name, line, col) + return status + + def getText(self): + """Return contents of editor.""" + return self.window.GetText() + + def hasChanged(self): + """Return True if contents have changed.""" + return self.window.GetModify() + + def setFocus(self): + """Set the input focus to the editor window.""" + self.window.SetFocus() + + def setSavePoint(self): + self.window.SetSavePoint() + + def setText(self, text): + """Set contents of editor.""" + self.window.SetText(text) def OnChar(self, event): """Keypress event handler. @@ -549,22 +573,22 @@ class Editor(base.Editor): key = event.KeyCode() if key in self.autoCompleteKeys: # Usually the dot (period) key activates auto completion. - if self.AutoCompActive(): - self.AutoCompCancel() - self.ReplaceSelection('') - self.AddText(chr(key)) - text, pos = self.GetCurLine() + if self.window.AutoCompActive(): + self.window.AutoCompCancel() + self.window.ReplaceSelection('') + self.window.AddText(chr(key)) + text, pos = self.window.GetCurLine() text = text[:pos] - if self.autoComplete: + if self.window.autoComplete: self.autoCompleteShow(text) elif key == ord('('): # The left paren activates a call tip and cancels an # active auto completion. - if self.AutoCompActive(): - self.AutoCompCancel() - self.ReplaceSelection('') - self.AddText('(') - text, pos = self.GetCurLine() + if self.window.AutoCompActive(): + self.window.AutoCompCancel() + self.window.ReplaceSelection('') + self.window.AddText('(') + text, pos = self.window.GetCurLine() text = text[:pos] self.autoCallTipShow(text) else: @@ -576,7 +600,7 @@ class Editor(base.Editor): key = event.KeyCode() # If the auto-complete window is up let it do its thing. - if self.AutoCompActive(): + if self.window.AutoCompActive(): event.Skip() return controlDown = event.ControlDown() @@ -599,46 +623,57 @@ class Editor(base.Editor): def autoCompleteShow(self, command): """Display auto-completion popup list.""" - list = self.interp.getAutoCompleteList(command, - includeMagic=self.autoCompleteIncludeMagic, - includeSingle=self.autoCompleteIncludeSingle, - includeDouble=self.autoCompleteIncludeDouble) + list = self.buffer.interp.getAutoCompleteList(command, + includeMagic=self.window.autoCompleteIncludeMagic, + includeSingle=self.window.autoCompleteIncludeSingle, + includeDouble=self.window.autoCompleteIncludeDouble) if list and len(list) < 2000: options = ' '.join(list) offset = 0 - self.AutoCompShow(offset, options) + self.window.AutoCompShow(offset, options) def autoCallTipShow(self, command): """Display argument spec and docstring in a popup window.""" - if self.CallTipActive(): - self.CallTipCancel() - (name, argspec, tip) = self.interp.getCallTip(command) + if self.window.CallTipActive(): + self.window.CallTipCancel() + (name, argspec, tip) = self.buffer.interp.getCallTip(command) if tip: dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip) - if not self.autoCallTip: + if not self.window.autoCallTip: return if argspec: - startpos = self.GetCurrentPos() - self.AddText(argspec + ')') - endpos = self.GetCurrentPos() - self.SetSelection(endpos, startpos) + startpos = self.window.GetCurrentPos() + self.window.AddText(argspec + ')') + endpos = self.window.GetCurrentPos() + self.window.SetSelection(endpos, startpos) if tip: - curpos = self.GetCurrentPos() + curpos = self.window.GetCurrentPos() size = len(name) tippos = curpos - (size + 1) - fallback = curpos - self.GetColumn(curpos) + fallback = curpos - self.window.GetColumn(curpos) # In case there isn't enough room, only go back to the # fallback. tippos = max(tippos, fallback) - self.CallTipShow(tippos, tip) - self.CallTipSetHighlight(0, size) + self.window.CallTipShow(tippos, tip) + self.window.CallTipSetHighlight(0, size) + + +class EditWindow(editwindow.EditWindow): + """EditWindow based on StyledTextCtrl.""" + + def __init__(self, editor, parent, id=-1, pos=wx.wxDefaultPosition, + size=wx.wxDefaultSize, + style=wx.wxCLIP_CHILDREN | wx.wxSUNKEN_BORDER): + """Create EditWindow instance.""" + editwindow.EditWindow.__init__(self, parent, id, pos, size, style) + self.editor = editor class DialogResults: """DialogResults class.""" def __init__(self, returned): - """Create a wrapper for the results returned by a dialog.""" + """Create wrapper for results returned by dialog.""" self.returned = returned self.positive = returned in (wx.wxID_OK, wx.wxID_YES) self.text = self._asString() diff --git a/wxPython/wxPython/py/editwindow.py b/wxPython/wxPython/py/editwindow.py new file mode 100644 index 0000000000..1427ec00d7 --- /dev/null +++ b/wxPython/wxPython/py/editwindow.py @@ -0,0 +1,195 @@ +"""EditWindow class.""" + +__author__ = "Patrick K. O'Brien " +__cvsid__ = "$Id$" +__revision__ = "$Revision$"[11:-2] + +from wxPython import wx +from wxPython import stc + +import keyword +import os +import sys +import time + +import dispatcher +from version import VERSION + +try: + True +except NameError: + True = 1==1 + False = 1==0 + +if wx.wxPlatform == '__WXMSW__': + FACES = { 'times' : 'Times New Roman', + 'mono' : 'Courier New', + 'helv' : 'Lucida Console', + 'lucida' : 'Lucida Console', + 'other' : 'Comic Sans MS', + 'size' : 10, + 'lnsize' : 9, + 'backcol': '#FFFFFF', + } +else: # GTK + FACES = { 'times' : 'Times', + 'mono' : 'Courier', + 'helv' : 'Helvetica', + 'other' : 'new century schoolbook', + 'size' : 12, + 'lnsize' : 10, + 'backcol': '#FFFFFF', + } + + +class EditWindow(stc.wxStyledTextCtrl): + """EditWindow based on StyledTextCtrl.""" + + revision = __revision__ + + def __init__(self, parent, id=-1, pos=wx.wxDefaultPosition, + size=wx.wxDefaultSize, style=wx.wxCLIP_CHILDREN | wx.wxSUNKEN_BORDER): + """Create EditWindow instance.""" + stc.wxStyledTextCtrl.__init__(self, parent, id, pos, size, style) + self.__config() + stc.EVT_STC_UPDATEUI(self, id, self.OnUpdateUI) + dispatcher.connect(receiver=self._fontsizer, signal='FontIncrease') + dispatcher.connect(receiver=self._fontsizer, signal='FontDecrease') + dispatcher.connect(receiver=self._fontsizer, signal='FontDefault') + + def _fontsizer(self, signal): + """Receiver for Font* signals.""" + size = self.GetZoom() + if signal == 'FontIncrease': + size += 1 + elif signal == 'FontDecrease': + size -= 1 + elif signal == 'FontDefault': + size = 0 + self.SetZoom(size) + + def __config(self): + """Configure shell based on user preferences.""" + self.SetMarginType(1, stc.wxSTC_MARGIN_NUMBER) + self.SetMarginWidth(1, 40) + + self.SetLexer(stc.wxSTC_LEX_PYTHON) + self.SetKeyWords(0, ' '.join(keyword.kwlist)) + + self.setStyles(FACES) + self.SetViewWhiteSpace(False) + self.SetTabWidth(4) + self.SetUseTabs(False) + # Do we want to automatically pop up command completion options? + self.autoComplete = True + self.autoCompleteIncludeMagic = True + self.autoCompleteIncludeSingle = True + self.autoCompleteIncludeDouble = True + self.autoCompleteCaseInsensitive = True + self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive) + self.AutoCompSetAutoHide(False) + self.AutoCompStops(' .,;:([)]}\'"\\<>%^&+-=*/|`') + # Do we want to automatically pop up command argument help? + self.autoCallTip = True + self.CallTipSetBackground(wx.wxColour(255, 255, 232)) + self.SetWrapMode(False) + try: + self.SetEndAtLastLine(False) + except AttributeError: + pass + + def setStyles(self, faces): + """Configure font size, typeface and color for lexer.""" + + # Default style + self.StyleSetSpec(stc.wxSTC_STYLE_DEFAULT, + "face:%(mono)s,size:%(size)d,back:%(backcol)s" % \ + faces) + + self.StyleClearAll() + + # Built in styles + self.StyleSetSpec(stc.wxSTC_STYLE_LINENUMBER, + "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces) + self.StyleSetSpec(stc.wxSTC_STYLE_CONTROLCHAR, + "face:%(mono)s" % faces) + self.StyleSetSpec(stc.wxSTC_STYLE_BRACELIGHT, + "fore:#0000FF,back:#FFFF88") + self.StyleSetSpec(stc.wxSTC_STYLE_BRACEBAD, + "fore:#FF0000,back:#FFFF88") + + # Python styles + self.StyleSetSpec(stc.wxSTC_P_DEFAULT, + "face:%(mono)s" % faces) + self.StyleSetSpec(stc.wxSTC_P_COMMENTLINE, + "fore:#007F00,face:%(mono)s" % faces) + self.StyleSetSpec(stc.wxSTC_P_NUMBER, + "") + self.StyleSetSpec(stc.wxSTC_P_STRING, + "fore:#7F007F,face:%(mono)s" % faces) + self.StyleSetSpec(stc.wxSTC_P_CHARACTER, + "fore:#7F007F,face:%(mono)s" % faces) + self.StyleSetSpec(stc.wxSTC_P_WORD, + "fore:#00007F,bold") + self.StyleSetSpec(stc.wxSTC_P_TRIPLE, + "fore:#7F0000") + self.StyleSetSpec(stc.wxSTC_P_TRIPLEDOUBLE, + "fore:#000033,back:#FFFFE8") + self.StyleSetSpec(stc.wxSTC_P_CLASSNAME, + "fore:#0000FF,bold") + self.StyleSetSpec(stc.wxSTC_P_DEFNAME, + "fore:#007F7F,bold") + self.StyleSetSpec(stc.wxSTC_P_OPERATOR, + "") + self.StyleSetSpec(stc.wxSTC_P_IDENTIFIER, + "") + self.StyleSetSpec(stc.wxSTC_P_COMMENTBLOCK, + "fore:#7F7F7F") + self.StyleSetSpec(stc.wxSTC_P_STRINGEOL, + "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces) + + def OnUpdateUI(self, event): + """Check for matching braces.""" + # If the auto-complete window is up let it do its thing. + if self.AutoCompActive() or self.CallTipActive(): + return + braceAtCaret = -1 + braceOpposite = -1 + charBefore = None + caretPos = self.GetCurrentPos() + if caretPos > 0: + charBefore = self.GetCharAt(caretPos - 1) + styleBefore = self.GetStyleAt(caretPos - 1) + + # Check before. + if charBefore and chr(charBefore) in '[]{}()' \ + and styleBefore == stc.wxSTC_P_OPERATOR: + braceAtCaret = caretPos - 1 + + # Check after. + if braceAtCaret < 0: + charAfter = self.GetCharAt(caretPos) + styleAfter = self.GetStyleAt(caretPos) + if charAfter and chr(charAfter) in '[]{}()' \ + and styleAfter == stc.wxSTC_P_OPERATOR: + braceAtCaret = caretPos + + if braceAtCaret >= 0: + braceOpposite = self.BraceMatch(braceAtCaret) + + if braceAtCaret != -1 and braceOpposite == -1: + self.BraceBadLight(braceAtCaret) + else: + self.BraceHighlight(braceAtCaret, braceOpposite) + + def CanCut(self): + """Return true if text is selected and can be cut.""" + return self.CanCopy() + + def CanCopy(self): + """Return true if text is selected and can be copied.""" + return self.GetSelectionStart() != self.GetSelectionEnd() + + def CanEdit(self): + """Return true if editing should succeed.""" + return True diff --git a/wxPython/wxPython/py/filling.py b/wxPython/wxPython/py/filling.py index 99ce5358b6..5eb7949ca9 100644 --- a/wxPython/wxPython/py/filling.py +++ b/wxPython/wxPython/py/filling.py @@ -7,8 +7,8 @@ __revision__ = "$Revision$"[11:-2] from wxPython import wx -import base import dispatcher +import editwindow import inspect import introspect import keyword @@ -245,7 +245,7 @@ class FillingTree(wx.wxTreeCtrl): print text -class FillingText(base.Editor): +class FillingText(editwindow.EditWindow): """FillingText based on StyledTextCtrl.""" name = 'PyFilling Text' @@ -255,7 +255,7 @@ class FillingText(base.Editor): size=wx.wxDefaultSize, style=wx.wxCLIP_CHILDREN, static=False): """Create a FillingText instance.""" - base.Editor.__init__(self, parent, id, pos, size, style) + editwindow.EditWindow.__init__(self, parent, id, pos, size, style) # Configure various defaults and user preferences. self.SetReadOnly(True) self.SetWrapMode(True) @@ -269,7 +269,7 @@ class FillingText(base.Editor): def SetText(self, *args, **kwds): self.SetReadOnly(False) - base.Editor.SetText(self, *args, **kwds) + editwindow.EditWindow.SetText(self, *args, **kwds) self.SetReadOnly(True) diff --git a/wxPython/wxPython/py/shell.py b/wxPython/wxPython/py/shell.py index 028cf29061..89c3fc4bda 100644 --- a/wxPython/wxPython/py/shell.py +++ b/wxPython/wxPython/py/shell.py @@ -18,9 +18,9 @@ import os import sys import time -import base -import buffer +from buffer import Buffer import dispatcher +import editwindow import frame from pseudo import PseudoFileIn from pseudo import PseudoFileOut @@ -71,6 +71,23 @@ class ShellFrame(frame.Frame): self.shell.destroy() self.Destroy() + def OnAbout(self, event): + """Display an About window.""" + title = 'About PyShell' + text = 'PyShell %s\n\n' % VERSION + \ + 'Yet another Python shell, only flakier.\n\n' + \ + 'Half-baked by Patrick K. O\'Brien,\n' + \ + 'the other half is still in the oven.\n\n' + \ + 'Shell Revision: %s\n' % self.shell.revision + \ + 'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \ + 'Python Version: %s\n' % sys.version.split()[0] + \ + 'wxPython Version: %s\n' % wx.__version__ + \ + 'Platform: %s\n' % sys.platform + dialog = wx.wxMessageDialog(self, text, title, + wx.wxOK | wx.wxICON_INFORMATION) + dialog.ShowModal() + dialog.Destroy() + class ShellFacade: """Simplified interface to all shell-related functionality. @@ -156,7 +173,7 @@ Ctrl+= Default font size. return list -class Shell(base.Editor): +class Shell(editwindow.EditWindow): """PyCrust Shell based on StyledTextCtrl.""" name = 'PyCrust Shell' @@ -166,7 +183,7 @@ class Shell(base.Editor): size=wx.wxDefaultSize, style=wx.wxCLIP_CHILDREN, introText='', locals=None, InterpClass=None, *args, **kwds): """Create a PyCrust Shell instance.""" - base.Editor.__init__(self, parent, id, pos, size, style) + editwindow.EditWindow.__init__(self, parent, id, pos, size, style) self.wrap() if locals is None: locals = {} @@ -193,8 +210,7 @@ class Shell(base.Editor): stderr=PseudoFileErr(self.writeErr), *args, **kwds) # Set up the buffer. - self.buffer = buffer.Buffer(editor=self, interp=self.interp, - filename=None) + self.buffer = Buffer() # Find out for which keycodes the interpreter will autocomplete. self.autoCompleteKeys = self.interp.getAutoCompleteKeys() # Keep track of the last non-continuation prompt positions. @@ -231,6 +247,10 @@ class Shell(base.Editor): def destroy(self): del self.interp + def setFocus(self): + """Set focus to the shell.""" + self.SetFocus() + def OnIdle(self, event): """Free the CPU to do other things.""" if self.waiting: @@ -878,7 +898,7 @@ Platform: %s""" % \ def CanPaste(self): """Return true if a paste should succeed.""" - if self.CanEdit() and base.Editor.CanPaste(self): + if self.CanEdit() and editwindow.EditWindow.CanPaste(self): return True else: return False