]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/py/editor.py
Factory some of wxFilterClassFactory into a base class.
[wxWidgets.git] / wxPython / wx / py / editor.py
index f7862bb7c5ffbdf83f6b263071ba47a985d9dcc7..b21c691193c21238b43c37e2d5d31132fdb5b917 100644 (file)
@@ -1,8 +1,841 @@
+"""PyAlaCarte and PyAlaMode editors."""
 
-"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
+__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
+__cvsid__ = "$Id$"
+__revision__ = "$Revision$"[11:-2]
 
-from wx import _rename
-from wxPython.py import editor
-_rename(globals(), editor.__dict__, modulename='py.editor')
-del editor
-del _rename
+import wx
+
+from buffer import Buffer
+import crust
+import dispatcher
+import editwindow
+import frame
+from shell import Shell
+import version
+
+
+class EditorFrame(frame.Frame):
+    """Frame containing one editor."""
+
+    def __init__(self, parent=None, id=-1, title='PyAlaCarte',
+                 pos=wx.DefaultPosition, size=(800, 600), 
+                 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE,
+                 filename=None):
+        """Create EditorFrame instance."""
+        frame.Frame.__init__(self, parent, id, title, pos, size, style)
+        self.buffers = {}
+        self.buffer = None  # Current buffer.
+        self.editor = None
+        self._defaultText = title + ' - the tastiest Python editor.'
+        self._statusText = self._defaultText
+        self.SetStatusText(self._statusText)
+        self.Bind(wx.EVT_IDLE, self.OnIdle)
+        self._setup()
+        if filename:
+            self.bufferCreate(filename)
+
+    def _setup(self):
+        """Setup prior to first buffer creation.
+
+        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'
+        text = 'Another fine, flaky program.'
+        dialog = wx.MessageDialog(self, text, title,
+                                  wx.OK | wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+    def OnClose(self, event):
+        """Event handler for closing."""
+        for buffer in self.buffers.values():
+            self.buffer = buffer
+            if buffer.hasChanged():
+                cancel = self.bufferSuggestSave()
+                if cancel and event.CanVeto():
+                    event.Veto()
+                    return
+        self.Destroy()
+
+    def OnIdle(self, event):
+        """Event handler for idle time."""
+        self._updateStatus()
+        if hasattr(self, 'notebook'):
+            self._updateTabText()
+        self._updateTitle()
+        event.Skip()
+
+    def _updateStatus(self):
+        """Show current status information."""
+        if self.editor and hasattr(self.editor, 'getStatus'):
+            status = self.editor.getStatus()
+            text = 'File: %s  |  Line: %d  |  Column: %d' % status
+        else:
+            text = self._defaultText
+        if text != self._statusText:
+            self.SetStatusText(text)
+            self._statusText = text
+
+    def _updateTabText(self):
+        """Show current buffer information on notebook tab."""
+##         suffix = ' **'
+##         notebook = self.notebook
+##         selection = notebook.GetSelection()
+##         if selection == -1:
+##             return
+##         text = notebook.GetPageText(selection)
+##         window = notebook.GetPage(selection)
+##         if window.editor and window.editor.buffer.hasChanged():
+##             if text.endswith(suffix):
+##                 pass
+##             else:
+##                 notebook.SetPageText(selection, text + suffix)
+##         else:
+##             if text.endswith(suffix):
+##                 notebook.SetPageText(selection, text[:len(suffix)])
+
+    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 hasBuffer(self):
+        """Return True if there is a current buffer."""
+        if self.buffer:
+            return True
+        else:
+            return False
+
+    def bufferClose(self):
+        """Close buffer."""
+        if self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        self.bufferDestroy()
+        cancel = False
+        return cancel
+
+    def bufferCreate(self, filename=None):
+        """Create new buffer."""
+        self.bufferDestroy()
+        buffer = Buffer()
+        self.panel = panel = wx.Panel(parent=self, id=-1)
+        panel.Bind (wx.EVT_ERASE_BACKGROUND, lambda x: x)        
+        editor = Editor(parent=panel)
+        panel.editor = editor
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(editor.window, 1, wx.EXPAND)
+        panel.SetSizer(sizer)
+        panel.SetAutoLayout(True)
+        sizer.Layout()
+        buffer.addEditor(editor)
+        buffer.open(filename)
+        self.setEditor(editor)
+        self.editor.setFocus()
+        self.SendSizeEvent()
+        
+
+    def bufferDestroy(self):
+        """Destroy the current buffer."""
+        if self.buffer:
+            for editor in self.buffer.editors.values():
+                editor.destroy()
+            self.editor = None
+            del self.buffers[self.buffer.id]
+            self.buffer = None
+            self.panel.Destroy()
+
+
+    def bufferHasChanged(self):
+        """Return True if buffer has changed since last save."""
+        if self.buffer:
+            return self.buffer.hasChanged()
+        else:
+            return False
+
+    def bufferNew(self):
+        """Create new buffer."""
+        if self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        self.bufferCreate()
+        cancel = False
+        return cancel
+
+    def bufferOpen(self):
+        """Open file in buffer."""
+        if self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        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)
+        cancel = False
+        return cancel
+
+##     def bufferPrint(self):
+##         """Print buffer."""
+##         pass
+
+##     def bufferRevert(self):
+##         """Revert buffer to version of file on disk."""
+##         pass
+
+    def bufferSave(self):
+        """Save buffer to its file."""
+        if self.buffer.doc.filepath:
+            self.buffer.save()
+            cancel = False
+        else:
+            cancel = self.bufferSaveAs()
+        return cancel
+
+    def bufferSaveAs(self):
+        """Save buffer to a new filename."""
+        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
+        result = saveSingle(directory=filedir)
+        if result.path:
+            self.buffer.saveAs(result.path)
+            cancel = False
+        else:
+            cancel = True
+        return cancel
+
+    def bufferSuggestSave(self):
+        """Suggest saving changes.  Return True if user selected Cancel."""
+        result = messageDialog(parent=None,
+                               message='%s has changed.\n'
+                                       'Would you like to save it first'
+                                       '?' % self.buffer.name,
+                               title='Save current file?')
+        if result.positive:
+            cancel = self.bufferSave()
+        else:
+            cancel = result.text == 'Cancel'
+        return cancel
+
+    def updateNamespace(self):
+        """Update the buffer namespace for autocompletion and calltips."""
+        if self.buffer.updateNamespace():
+            self.SetStatusText('Namespace updated')
+        else:
+            self.SetStatusText('Error executing, unable to update namespace')
+
+
+class EditorNotebookFrame(EditorFrame):
+    """Frame containing one or more editors in a notebook."""
+
+    def __init__(self, parent=None, id=-1, title='PyAlaMode',
+                 pos=wx.DefaultPosition, size=(800, 600), 
+                 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE,
+                 filename=None):
+        """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.
+
+        Called automatically by base class during init."""
+        self.notebook = EditorNotebook(parent=self)
+        intro = 'Py %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.shell = self.crust.shell
+        # Override the filling so that status messages go to the status bar.
+        self.crust.filling.tree.setStatusText = self.SetStatusText
+        # Override the shell so that status messages go to the status bar.
+        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='*Shell*', select=True)
+        self.setEditor(self.crust.editor)
+        self.crust.editor.SetFocus()
+
+    def _editorChange(self, editor):
+        """Editor change signal receiver."""
+        self.setEditor(editor)
+
+    def OnAbout(self, event):
+        """Display an About window."""
+        title = 'About PyAlaMode'
+        text = 'Another fine, flaky program.'
+        dialog = wx.MessageDialog(self, text, title,
+                                  wx.OK | wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+    def _updateTitle(self):
+        """Show current title information."""
+        pass
+##         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."""
+        buffer = Buffer()
+        panel = wx.Panel(parent=self.notebook, id=-1)
+        panel.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: x)        
+        editor = Editor(parent=panel)
+        panel.editor = editor
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(editor.window, 1, wx.EXPAND)
+        panel.SetSizer(sizer)
+        panel.SetAutoLayout(True)
+        sizer.Layout()
+        buffer.addEditor(editor)
+        buffer.open(filename)
+        self.setEditor(editor)
+        self.notebook.AddPage(page=panel, text=self.buffer.name, select=True)
+        self.editor.setFocus()
+
+    def bufferDestroy(self):
+        """Destroy the current buffer."""
+        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)
+
+    def bufferNew(self):
+        """Create new buffer."""
+        self.bufferCreate()
+        cancel = False
+        return cancel
+
+    def bufferOpen(self):
+        """Open file in buffer."""
+        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)
+        cancel = False
+        return cancel
+
+
+class EditorNotebook(wx.Notebook):
+    """A notebook containing a page for each editor."""
+
+    def __init__(self, parent):
+        """Create EditorNotebook instance."""
+        wx.Notebook.__init__(self, parent, id=-1, style=wx.CLIP_CHILDREN)
+        self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging, id=self.GetId())
+        self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=self.GetId())
+        self.Bind(wx.EVT_IDLE, self.OnIdle)
+
+    def OnIdle(self, event):
+        """Event handler for idle time."""
+        self._updateTabText()
+        event.Skip()
+
+    def _updateTabText(self):
+        """Show current buffer display name on all but first tab."""
+        size = 3
+        changed = ' **'
+        unchanged = ' --'
+        selection = self.GetSelection()
+        if selection < 1:
+            return
+        text = self.GetPageText(selection)
+        window = self.GetPage(selection)
+        if not window.editor:
+            return
+        if text.endswith(changed) or text.endswith(unchanged):
+            name = text[:-size]
+        else:
+            name = text
+        if name != window.editor.buffer.name:
+            text = window.editor.buffer.name
+        if window.editor.buffer.hasChanged():
+            if text.endswith(changed):
+                text = None
+            elif text.endswith(unchanged):
+                text = text[:-size] + changed
+            else:
+                text += changed
+        else:
+            if text.endswith(changed):
+                text = text[:-size] + unchanged
+            elif text.endswith(unchanged):
+                text = None
+            else:
+                text += unchanged
+        if text is not None:
+            self.SetPageText(selection, text)
+            self.Refresh()  # Needed on Win98.
+
+    def OnPageChanging(self, event):
+        """Page changing event handler."""
+        event.Skip()
+
+    def OnPageChanged(self, event):
+        """Page changed event handler."""
+        new = event.GetSelection()
+        window = self.GetPage(new)
+        dispatcher.send(signal='EditorChange', sender=self,
+                        editor=window.editor)
+        window.SetFocus()
+        event.Skip()
+
+
+class EditorShellNotebookFrame(EditorNotebookFrame):
+    """Frame containing a notebook containing EditorShellNotebooks."""
+
+    def __init__(self, parent=None, id=-1, title='PyAlaModeTest',
+                 pos=wx.DefaultPosition, size=(600, 400), 
+                 style=wx.DEFAULT_FRAME_STYLE,
+                 filename=None, singlefile=False):
+        """Create EditorShellNotebookFrame instance."""
+        self._singlefile = singlefile
+        EditorNotebookFrame.__init__(self, parent, id, title, pos,
+                                     size, style, filename)
+
+    def _setup(self):
+        """Setup prior to first buffer creation.
+
+        Called automatically by base class during init."""
+        if not self._singlefile:
+            self.notebook = EditorNotebook(parent=self)
+
+    def OnAbout(self, event):
+        """Display an About window."""
+        title = 'About PyAlaModePlus'
+        text = 'Another fine, flaky program.'
+        dialog = wx.MessageDialog(self, text, title,
+                                  wx.OK | wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+    def bufferCreate(self, filename=None):
+        """Create new buffer."""
+        if self._singlefile:
+            self.bufferDestroy()
+            notebook = EditorShellNotebook(parent=self,
+                                           filename=filename)
+            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.editor.setFocus()
+
+    def bufferDestroy(self):
+        """Destroy the current buffer."""
+        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
+        else:
+            selection = self.notebook.GetSelection()
+##             print "Destroy Selection:", selection
+            self.notebook.DeletePage(selection)
+
+    def bufferNew(self):
+        """Create new buffer."""
+        if self._singlefile and self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        self.bufferCreate()
+        cancel = False
+        return cancel
+
+    def bufferOpen(self):
+        """Open file in buffer."""
+        if self._singlefile and self.bufferHasChanged():
+            cancel = self.bufferSuggestSave()
+            if cancel:
+                return cancel
+        filedir = ''
+        if self.buffer and self.buffer.doc.filedir:
+            filedir = self.buffer.doc.filedir
+        if self._singlefile:
+            result = openSingle(directory=filedir)
+            if result.path:
+                self.bufferCreate(result.path)
+        else:
+            result = openMultiple(directory=filedir)
+            for path in result.paths:
+                self.bufferCreate(path)
+        cancel = False
+        return cancel
+
+
+class EditorShellNotebook(wx.Notebook):
+    """A notebook containing an editor page and a shell page."""
+
+    def __init__(self, parent, filename=None):
+        """Create EditorShellNotebook instance."""
+        wx.Notebook.__init__(self, parent, id=-1)
+        usePanels = True
+        if usePanels:
+            editorparent = editorpanel = wx.Panel(self, -1)
+            shellparent = shellpanel = wx.Panel(self, -1)
+        else:
+            editorparent = self
+            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.CLIP_CHILDREN | wx.SUNKEN_BORDER)
+        self.buffer.interp.locals.clear()
+        if usePanels:
+            self.AddPage(page=editorpanel, text='Editor', select=True)
+            self.AddPage(page=shellpanel, text='Shell')
+            # Setup sizers
+            editorsizer = wx.BoxSizer(wx.VERTICAL)
+            editorsizer.Add(self.editor.window, 1, wx.EXPAND)
+            editorpanel.SetSizer(editorsizer)
+            editorpanel.SetAutoLayout(True)
+            shellsizer = wx.BoxSizer(wx.VERTICAL)
+            shellsizer.Add(self.shell, 1, wx.EXPAND)
+            shellpanel.SetSizer(shellsizer)
+            shellpanel.SetAutoLayout(True)
+        else:
+            self.AddPage(page=self.editor.window, text='Editor', select=True)
+            self.AddPage(page=self.shell, text='Shell')
+        self.editor.setFocus()
+        self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged, id=self.GetId())
+
+    def OnPageChanged(self, event):
+        """Page changed event handler."""
+        selection = event.GetSelection()
+        if selection == 0:
+            self.editor.setFocus()
+        else:
+            self.shell.SetFocus()
+        event.Skip()
+
+    def SetFocus(self):
+        wx.Notebook.SetFocus(self)
+        selection = self.GetSelection()
+        if selection == 0:
+            self.editor.setFocus()
+        else:
+            self.shell.SetFocus()
+
+
+class Editor:
+    """Editor having an EditWindow."""
+
+    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
+                 size=wx.DefaultSize,
+                 style=wx.CLIP_CHILDREN | wx.SUNKEN_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.
+        self.window.Bind(wx.EVT_CHAR, self.OnChar)
+        self.window.Bind(wx.EVT_KEY_DOWN, 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."""
+        if self.window:
+            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
+        else:
+            return ('', 0, 0)
+
+    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.
+        
+        Only receives an event if OnKeyDown calls event.Skip() for the
+        corresponding event."""
+
+        key = event.GetKeyCode()
+        if key in self.autoCompleteKeys:
+            # Usually the dot (period) key activates auto completion.
+            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.window.autoComplete: 
+                self.autoCompleteShow(text)
+        elif key == ord('('):
+            # The left paren activates a call tip and cancels an
+            # active auto completion.
+            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:
+            # Allow the normal event handling to take place.
+            event.Skip()
+
+    def OnKeyDown(self, event):
+        """Key down event handler."""
+
+        key = event.GetKeyCode()
+        # If the auto-complete window is up let it do its thing.
+        if self.window.AutoCompActive():
+            event.Skip()
+            return
+        controlDown = event.ControlDown()
+        altDown = event.AltDown()
+        shiftDown = event.ShiftDown()
+        # Let Ctrl-Alt-* get handled normally.
+        if controlDown and altDown:
+            event.Skip()
+        # Increase font size.
+        elif controlDown and key in (ord(']'),):
+            dispatcher.send(signal='FontIncrease')
+        # Decrease font size.
+        elif controlDown and key in (ord('['),):
+            dispatcher.send(signal='FontDecrease')
+        # Default font size.
+        elif controlDown and key in (ord('='),):
+            dispatcher.send(signal='FontDefault')
+        else:
+            event.Skip()
+
+    def autoCompleteShow(self, command):
+        """Display auto-completion popup list."""
+        list = self.buffer.interp.getAutoCompleteList(command, 
+                    includeMagic=self.window.autoCompleteIncludeMagic, 
+                    includeSingle=self.window.autoCompleteIncludeSingle, 
+                    includeDouble=self.window.autoCompleteIncludeDouble)
+        if list:
+            options = ' '.join(list)
+            offset = 0
+            self.window.AutoCompShow(offset, options)
+
+    def autoCallTipShow(self, command):
+        """Display argument spec and docstring in a popup window."""
+        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.window.autoCallTip:
+            return
+        if argspec:
+            startpos = self.window.GetCurrentPos()
+            self.window.AddText(argspec + ')')
+            endpos = self.window.GetCurrentPos()
+            self.window.SetSelection(endpos, startpos)
+        if tip:
+            curpos = self.window.GetCurrentPos()
+            size = len(name)
+            tippos = curpos - (size + 1)
+            fallback = curpos - self.window.GetColumn(curpos)
+            # In case there isn't enough room, only go back to the
+            # fallback.
+            tippos = max(tippos, fallback)
+            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.DefaultPosition,
+                 size=wx.DefaultSize,
+                 style=wx.CLIP_CHILDREN | wx.SUNKEN_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 wrapper for results returned by dialog."""
+        self.returned = returned
+        self.positive = returned in (wx.ID_OK, wx.ID_YES)
+        self.text = self._asString()
+        
+
+    def __repr__(self):
+        return str(self.__dict__)
+
+    def _asString(self):
+        returned = self.returned
+        if returned == wx.ID_OK:
+            return "Ok"
+        elif returned == wx.ID_CANCEL:
+            return "Cancel"
+        elif returned == wx.ID_YES:
+            return "Yes"
+        elif returned == wx.ID_NO:
+            return "No"
+
+
+def fileDialog(parent=None, title='Open', directory='', filename='',
+               wildcard='All Files (*.*)|*.*',
+               style=wx.OPEN | wx.MULTIPLE):
+    """File dialog wrapper function."""
+    dialog = wx.FileDialog(parent, title, directory, filename,
+                           wildcard, style)
+    result = DialogResults(dialog.ShowModal())
+    if result.positive:
+        result.paths = dialog.GetPaths()
+    else:
+        result.paths = []
+    dialog.Destroy()
+    return result
+
+
+def openSingle(parent=None, title='Open', directory='', filename='',
+               wildcard='All Files (*.*)|*.*', style=wx.OPEN):
+    """File dialog wrapper function."""
+    dialog = wx.FileDialog(parent, title, directory, filename,
+                           wildcard, style)
+    result = DialogResults(dialog.ShowModal())
+    if result.positive:
+        result.path = dialog.GetPath()
+    else:
+        result.path = None
+    dialog.Destroy()
+    return result
+
+
+def openMultiple(parent=None, title='Open', directory='', filename='',
+                 wildcard='All Files (*.*)|*.*',
+                 style=wx.OPEN | wx.MULTIPLE):
+    """File dialog wrapper function."""
+    return fileDialog(parent, title, directory, filename, wildcard, style)
+
+
+def saveSingle(parent=None, title='Save', directory='', filename='',
+               wildcard='All Files (*.*)|*.*',
+               style=wx.SAVE | wx.OVERWRITE_PROMPT):
+    """File dialog wrapper function."""
+    dialog = wx.FileDialog(parent, title, directory, filename,
+                           wildcard, style)
+    result = DialogResults(dialog.ShowModal())
+    if result.positive:
+        result.path = dialog.GetPath()
+    else:
+        result.path = None
+    dialog.Destroy()
+    return result
+
+
+def directory(parent=None, message='Choose a directory', path='', style=0,
+              pos=wx.DefaultPosition, size=wx.DefaultSize):
+    """Dir dialog wrapper function."""
+    dialog = wx.DirDialog(parent, message, path, style, pos, size)
+    result = DialogResults(dialog.ShowModal())
+    if result.positive:
+        result.path = dialog.GetPath()
+    else:
+        result.path = None
+    dialog.Destroy()
+    return result
+
+
+def messageDialog(parent=None, message='', title='Message box',
+                  style=wx.YES_NO | wx.CANCEL | wx.CENTRE | wx.ICON_QUESTION,
+                  pos=wx.DefaultPosition):
+    """Message dialog wrapper function."""
+    dialog = wx.MessageDialog(parent, message, title, style, pos)
+    result = DialogResults(dialog.ShowModal())
+    dialog.Destroy()
+    return result