+
+    def ProcessEvent(self, event):
+        """
+        Processes an event, searching event tables and calling zero or more
+        suitable event handler function(s).  Note that the ProcessEvent
+        method is called from the wxPython docview framework directly since
+        wxPython does not have a virtual ProcessEvent function.
+        """
+        return False
+
+
+    def ProcessUpdateUIEvent(self, event):
+        """
+        Processes a UI event, searching event tables and calling zero or more
+        suitable event handler function(s).  Note that the ProcessEvent
+        method is called from the wxPython docview framework directly since
+        wxPython does not have a virtual ProcessEvent function.
+        """
+        return False
+
+
+    def OnCloseFrame(self, event):
+        """
+        Called when the a docview frame is being closed.  Override this method
+        so a service can either do cleanup or veto the frame being closed by
+        returning false.
+        """
+        return True
+
+
+    def OnExit(self):
+        """
+        Called when the the docview application is being closed.  Override this method
+        so a service can either do cleanup or veto the frame being closed by
+        returning false.
+        """
+        pass
+
+
+    def GetMenuItemPos(self, menu, id):
+        """
+        Utility method used to find the position of a menu item so that services can
+        easily find where to insert a menu item in InstallControls.
+        """
+        menuItems = menu.GetMenuItems()
+        for i, menuItem in enumerate(menuItems):
+            if menuItem.GetId() == id:
+                return i
+        return i
+
+
+    def GetView(self):
+        """
+        Called by WindowMenuService to get views for services that don't
+        have dedicated documents such as the Outline Service.
+        """
+        return None
+
+
+class DocOptionsService(DocService):
+    """
+    A service that implements an options menu item and an options dialog with
+    notebook tabs.  New tabs can be added by other services by calling the
+    "AddOptionsPanel" method.
+    """
+
+
+    def __init__(self, showGeneralOptions=True, supportedModes=wx.lib.docview.DOC_SDI & wx.lib.docview.DOC_MDI):
+        """
+        Initializes the options service with the option of suppressing the default
+        general options pane that is included with the options service by setting
+        showGeneralOptions to False.  It allowModeChanges is set to False, the
+        default general options pane will allow users to change the document
+        interface mode between SDI and MDI modes.
+        """
+        DocService.__init__(self)
+        self.ClearOptionsPanels()
+        self._supportedModes = supportedModes
+        self._toolOptionsID = wx.ID_PREFERENCES
+        if showGeneralOptions:
+            self.AddOptionsPanel(GeneralOptionsPanel)
+
+
+    def InstallControls(self, frame, menuBar=None, toolBar=None, statusBar=None, document=None):
+        """
+        Installs a "Tools" menu with an "Options" menu item.
+        """
+        toolsMenuIndex = menuBar.FindMenu(_("&Tools"))
+        if toolsMenuIndex > -1:
+            toolsMenu = menuBar.GetMenu(toolsMenuIndex)
+        else:
+            toolsMenu = wx.Menu()
+        if toolsMenuIndex == -1:
+            formatMenuIndex = menuBar.FindMenu(_("&Format"))
+            menuBar.Insert(formatMenuIndex + 1, toolsMenu, _("&Tools"))
+        if toolsMenu:
+            if toolsMenu.GetMenuItemCount():
+                toolsMenu.AppendSeparator()
+            toolsMenu.Append(self._toolOptionsID, _("&Options..."), _("Sets options"))
+            wx.EVT_MENU(frame, self._toolOptionsID, frame.ProcessEvent)
+            
+
+    def ProcessEvent(self, event):
+        """
+        Checks to see if the "Options" menu item has been selected.
+        """
+        id = event.GetId()
+        if id == self._toolOptionsID:
+            self.OnOptions(event)
+            return True
+        else:
+            return False
+
+
+    def GetSupportedModes(self):
+        """
+        Return the modes supported by the application.  Use docview.DOC_SDI and
+        docview.DOC_MDI flags to check if SDI and/or MDI modes are supported.
+        """
+        return self._supportedModes        
+
+
+    def SetSupportedModes(self, _supportedModessupportedModes):
+        """
+        Sets the modes supported by the application.  Use docview.DOC_SDI and
+        docview.DOC_MDI flags to set if SDI and/or MDI modes are supported.
+        """
+        self._supportedModes = supportedModes
+
+
+    def ClearOptionsPanels(self):
+        """
+        Clears all of the options panels that have been added into the
+        options dialog.
+        """
+        self._optionsPanels = []
+
+
+    def AddOptionsPanel(self, optionsPanel):
+        """
+        Adds an options panel to the options dialog. 
+        """
+        self._optionsPanels.append(optionsPanel)
+
+
+    def OnOptions(self, event):
+        """
+        Shows the options dialog, called when the "Options" menu item is selected.
+        """
+        if len(self._optionsPanels) == 0:
+            return
+        optionsDialog = OptionsDialog(wx.GetApp().GetTopWindow(), self._optionsPanels, self._docManager)
+        optionsDialog.CenterOnParent()
+        if optionsDialog.ShowModal() == wx.ID_OK:
+            optionsDialog.OnOK(optionsDialog)  # wxBug: wxDialog should be calling this automatically but doesn't
+        optionsDialog.Destroy()
+
+
+class OptionsDialog(wx.Dialog):
+    """
+    A default options dialog used by the OptionsService that hosts a notebook
+    tab of options panels.
+    """
+
+
+    def __init__(self, parent, optionsPanelClasses, docManager):
+        """
+        Initializes the options dialog with a notebook page that contains new
+        instances of the passed optionsPanelClasses.
+        """        
+        wx.Dialog.__init__(self, parent, -1, _("Options"))
+
+        self._optionsPanels = []
+        self._docManager = docManager
+
+        HALF_SPACE = 5
+        SPACE = 10
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        if wx.Platform == "__WXMAC__":
+            optionsNotebook = wx.Listbook(self, wx.NewId(), style=wx.LB_DEFAULT)
+        else:
+            optionsNotebook = wx.Notebook(self, wx.NewId(), style=wx.NB_MULTILINE)  # NB_MULTILINE is windows platform only
+        sizer.Add(optionsNotebook, 0, wx.ALL | wx.EXPAND, SPACE)
+        
+        if wx.Platform == "__WXMAC__":
+            iconList = wx.ImageList(16, 16, initialCount = len(optionsPanelClasses))
+            self._iconIndexLookup = []
+               
+            for optionsPanelClass in optionsPanelClasses:
+                optionsPanel = optionsPanelClass(optionsNotebook, -1)
+                self._optionsPanels.append(optionsPanel)
+             
+                # We need to populate the image list before setting notebook images
+                if hasattr(optionsPanel, "GetIcon"):
+                    icon = optionsPanel.GetIcon()
+                else:
+                    icon = None
+                if icon:
+                    if icon.GetHeight() != 16 or icon.GetWidth() != 16:
+                        icon.SetHeight(16)
+                        icon.SetWidth(16)
+                        if wx.GetApp().GetDebug():
+                            print "Warning: icon for '%s' isn't 16x16, not crossplatform" % template._docTypeName
+                    iconIndex = iconList.AddIcon(icon)
+                    self._iconIndexLookup.append((optionsPanel, iconIndex))
+            
+                else:
+                    # use -1 to represent that this panel has no icon
+                    self._iconIndexLookup.append((optionsPanel, -1))
+                
+            optionsNotebook.AssignImageList(iconList)
+        
+            # Add icons to notebook
+            for index in range(0, len(optionsPanelClasses)-1):
+                iconIndex = self._iconIndexLookup[index][1]
+                if iconIndex >= 0:
+                    optionsNotebook.SetPageImage(index, iconIndex)        
+        else:
+            for optionsPanelClass in optionsPanelClasses:
+                optionsPanel = optionsPanelClass(optionsNotebook, -1)
+                self._optionsPanels.append(optionsPanel)
+
+        sizer.Add(self.CreateButtonSizer(wx.OK | wx.CANCEL), 0, wx.ALIGN_RIGHT | wx.RIGHT | wx.BOTTOM, HALF_SPACE)
+        self.SetSizer(sizer)
+        self.Layout()
+        self.Fit()
+        wx.CallAfter(self.DoRefresh)
+
+
+    def DoRefresh(self):
+        """
+        wxBug: On Windows XP when using a multiline notebook the default page doesn't get
+        drawn, but it works when using a single line notebook.
+        """
+        self.Refresh()
+
+
+    def GetDocManager(self):
+        """
+        Returns the document manager passed to the OptionsDialog constructor.
+        """
+        return self._docManager
+
+
+    def OnOK(self, event):
+        """
+        Calls the OnOK method of all of the OptionDialog's embedded panels
+        """
+        for optionsPanel in self._optionsPanels:
+            optionsPanel.OnOK(event)
+
+
+class GeneralOptionsPanel(wx.Panel):
+    """
+    A general options panel that is used in the OptionDialog to configure the
+    generic properties of a pydocview application, such as "show tips at startup"
+    and whether to use SDI or MDI for the application.
+    """
+
+
+    def __init__(self, parent, id):
+        """
+        Initializes the panel by adding an "Options" folder tab to the parent notebook and
+        populating the panel with the generic properties of a pydocview application. 
+        """
+        wx.Panel.__init__(self, parent, id)
+        SPACE = 10
+        HALF_SPACE = 5
+        config = wx.ConfigBase_Get()
+        self._showTipsCheckBox = wx.CheckBox(self, -1, _("Show tips at start up"))
+        self._showTipsCheckBox.SetValue(config.ReadInt("ShowTipAtStartup", True))
+        if self._AllowModeChanges():
+            supportedModes = wx.GetApp().GetService(DocOptionsService).GetSupportedModes()
+            choices = []
+            self._sdiChoice = _("Show each document in its own window")
+            self._mdiChoice = _("Show all documents in a single window with tabs")
+            self._winMdiChoice = _("Show all documents in a single window with child windows")
+            if supportedModes & wx.lib.docview.DOC_SDI:
+                choices.append(self._sdiChoice)
+            choices.append(self._mdiChoice)
+            if wx.Platform == "__WXMSW__":
+                choices.append(self._winMdiChoice)
+            self._documentRadioBox = wx.RadioBox(self, -1, _("Document Interface"),
+                                          choices = choices,
+                                          majorDimension=1,
+                                          )
+            if config.ReadInt("UseWinMDI", False):
+                self._documentRadioBox.SetStringSelection(self._winMdiChoice)
+            elif config.ReadInt("UseMDI", True):
+                self._documentRadioBox.SetStringSelection(self._mdiChoice)
+            else:
+                self._documentRadioBox.SetStringSelection(self._sdiChoice)
+            def OnDocumentInterfaceSelect(event):
+                if not self._documentInterfaceMessageShown:
+                    msgTitle = wx.GetApp().GetAppName()
+                    if not msgTitle:
+                        msgTitle = _("Document Options")
+                    wx.MessageBox(_("Document interface changes will not appear until the application is restarted."),
+                                  msgTitle,
+                                  wx.OK | wx.ICON_INFORMATION,
+                                  self.GetParent())
+                    self._documentInterfaceMessageShown = True
+            wx.EVT_RADIOBOX(self, self._documentRadioBox.GetId(), OnDocumentInterfaceSelect)
+        optionsBorderSizer = wx.BoxSizer(wx.VERTICAL)
+        optionsSizer = wx.BoxSizer(wx.VERTICAL)
+        if self._AllowModeChanges():
+            optionsSizer.Add(self._documentRadioBox, 0, wx.ALL, HALF_SPACE)
+        optionsSizer.Add(self._showTipsCheckBox, 0, wx.ALL, HALF_SPACE)
+        optionsBorderSizer.Add(optionsSizer, 0, wx.ALL, SPACE)
+        self.SetSizer(optionsBorderSizer)
+        self.Layout()
+        self._documentInterfaceMessageShown = False
+        parent.AddPage(self, _("Options"))
+
+
+    def _AllowModeChanges(self):
+        supportedModes = wx.GetApp().GetService(DocOptionsService).GetSupportedModes()
+        return supportedModes & wx.lib.docview.DOC_SDI and supportedModes & wx.lib.docview.DOC_MDI or wx.Platform == "__WXMSW__" and supportedModes & wx.lib.docview.DOC_MDI  # More than one mode is supported, allow selection
+
+
+    def OnOK(self, optionsDialog):
+        """
+        Updates the config based on the selections in the options panel.
+        """
+        config = wx.ConfigBase_Get()
+        config.WriteInt("ShowTipAtStartup", self._showTipsCheckBox.GetValue())
+        if self._AllowModeChanges():
+            config.WriteInt("UseMDI", (self._documentRadioBox.GetStringSelection() == self._mdiChoice))
+            config.WriteInt("UseWinMDI", (self._documentRadioBox.GetStringSelection() == self._winMdiChoice))
+
+
+    def GetIcon(self):
+        """ Return icon for options panel on the Mac. """
+        return wx.GetApp().GetDefaultIcon()
+
+
+class DocApp(wx.PySimpleApp):
+    """
+    The DocApp class serves as the base class for pydocview applications and offers
+    functionality such as services, creation of SDI and MDI frames, show tips,
+    and a splash screen.
+    """
+
+
+    def OnInit(self):
+        """
+        Initializes the DocApp.
+        """
+        self._services = []
+        self._defaultIcon = None
+        self._registeredCloseEvent = False
+        self._useTabbedMDI = True
+
+        if not hasattr(self, "_debug"):  # only set if not already initialized
+            self._debug = False
+        if not hasattr(self, "_singleInstance"):  # only set if not already initialized
+            self._singleInstance = True
+            
+        # if _singleInstance is TRUE only allow one single instance of app to run.
+        # When user tries to run a second instance of the app, abort startup,
+        # But if user also specifies files to open in command line, send message to running app to open those files
+        if self._singleInstance:
+            # create shared memory temporary file
+            if wx.Platform == '__WXMSW__':
+                tfile = tempfile.TemporaryFile(prefix="ag", suffix="tmp")
+                fno = tfile.fileno()
+                self._sharedMemory = mmap.mmap(fno, 1024, "shared_memory")
+            else:
+                tfile = file(os.path.join(tempfile.gettempdir(), tempfile.gettempprefix() + self.GetAppName() + '-' + wx.GetUserId() + "AGSharedMemory"), 'w+b')
+                tfile.write("*")
+                tfile.seek(1024)
+                tfile.write(" ")
+                tfile.flush()
+                fno = tfile.fileno()
+                self._sharedMemory = mmap.mmap(fno, 1024)
+
+            self._singleInstanceChecker = wx.SingleInstanceChecker(self.GetAppName() + '-' + wx.GetUserId(), tempfile.gettempdir())
+            if self._singleInstanceChecker.IsAnotherRunning():
+                # have running single instance open file arguments
+                data = pickle.dumps(sys.argv[1:])
+                while 1:
+                    self._sharedMemory.seek(0)
+                    marker = self._sharedMemory.read_byte()
+                    if marker == '\0' or marker == '*':        # available buffer
+                        self._sharedMemory.seek(0)
+                        self._sharedMemory.write_byte('-')     # set writing marker
+                        self._sharedMemory.write(data)  # write files we tried to open to shared memory
+                        self._sharedMemory.seek(0)
+                        self._sharedMemory.write_byte('+')     # set finished writing marker
+                        self._sharedMemory.flush()
+                        break
+                    else:
+                        time.sleep(1)  # give enough time for buffer to be available
+                        
+                return False
+            else:
+                self._timer = wx.PyTimer(self.DoBackgroundListenAndLoad)
+                self._timer.Start(250)
+
+        return True
+        
+
+    def OpenMainFrame(self):
+        docManager = self.GetDocumentManager()
+        if docManager.GetFlags() & wx.lib.docview.DOC_MDI:
+            if self.GetUseTabbedMDI():
+                frame = wx.lib.pydocview.DocTabbedParentFrame(docManager, None, -1, self.GetAppName())
+            else:
+                frame = wx.lib.pydocview.DocMDIParentFrame(docManager, None, -1, self.GetAppName())                
+            frame.Show(True)
+
+    def MacOpenFile(self, filename):
+        self.GetDocumentManager().CreateDocument(os.path.normpath(filename), wx.lib.docview.DOC_SILENT)
+            
+        # force display of running app
+        topWindow = wx.GetApp().GetTopWindow()
+        if topWindow.IsIconized():
+            topWindow.Iconize(False)
+        else:
+            topWindow.Raise()
+                
+    def DoBackgroundListenAndLoad(self):
+        """
+        Open any files specified in the given command line argument passed in via shared memory
+        """
+        self._timer.Stop()
+
+        self._sharedMemory.seek(0)
+        if self._sharedMemory.read_byte() == '+':  # available data
+            data = self._sharedMemory.read(1024-1)
+            self._sharedMemory.seek(0)
+            self._sharedMemory.write_byte("*")     # finished reading, set buffer free marker
+            self._sharedMemory.flush()
+            args = pickle.loads(data)
+            for arg in args:
+                if (wx.Platform != "__WXMSW__" or arg[0] != "/") and arg[0] != '-' and os.path.exists(arg):
+                    self.GetDocumentManager().CreateDocument(os.path.normpath(arg), wx.lib.docview.DOC_SILENT)
+            
+            # force display of running app
+            topWindow = wx.GetApp().GetTopWindow()
+            if topWindow.IsIconized():
+                topWindow.Iconize(False)
+            else:
+                topWindow.Raise()
+            
+        
+        self._timer.Start(1000) # 1 second interval
+
+
+    def OpenCommandLineArgs(self):
+        """
+        Called to open files that have been passed to the application from the
+        command line.
+        """
+        args = sys.argv[1:]
+        for arg in args:
+            if (wx.Platform != "__WXMSW__" or arg[0] != "/") and arg[0] != '-' and os.path.exists(arg):
+                self.GetDocumentManager().CreateDocument(os.path.normpath(arg), wx.lib.docview.DOC_SILENT)
+
+
+    def GetDocumentManager(self):
+        """
+        Returns the document manager associated to the DocApp.
+        """
+        return self._docManager
+
+
+    def SetDocumentManager(self, docManager):
+        """
+        Sets the document manager associated with the DocApp and loads the
+        DocApp's file history into the document manager.
+        """
+        self._docManager = docManager
+        config = wx.ConfigBase_Get()
+        self.GetDocumentManager().FileHistoryLoad(config)
+
+
+    def ProcessEventBeforeWindows(self, event):
+        """
+        Enables services to process an event before the main window has a chance to
+        process the window. 
+        """
+        for service in self._services:
+            if service.ProcessEventBeforeWindows(event):
+                return True
+        return False
+
+
+    def ProcessUpdateUIEventBeforeWindows(self, event):
+        """
+        Enables services to process a UI event before the main window has a chance
+        to process the window.
+        """
+        for service in self._services:
+            if service.ProcessUpdateUIEventBeforeWindows(event):
+                return True
+        return False
+
+
+    def ProcessEvent(self, event):
+        """
+        Processes an event, searching event tables and calling zero or more
+        suitable event handler function(s).  Note that the ProcessEvent
+        method is called from the wxPython docview framework directly since
+        wxPython does not have a virtual ProcessEvent function.
+        """
+        for service in self._services:
+            if service.ProcessEvent(event):
+                return True
+        return False
+
+
+    def ProcessUpdateUIEvent(self, event):
+        """
+        Processes a UI event, searching event tables and calling zero or more
+        suitable event handler function(s).  Note that the ProcessEvent
+        method is called from the wxPython docview framework directly since
+        wxPython does not have a virtual ProcessEvent function.
+        """
+        for service in self._services:
+            if service.ProcessUpdateUIEvent(event):
+                return True
+        return False
+
+
+    def InstallService(self, service):
+        """
+        Installs an instance of a DocService into the DocApp.
+        """
+        service.SetDocumentManager(self._docManager)
+        self._services.append(service)
+        return service
+
+
+    def GetServices(self):
+        """
+        Returns the DocService instances that have been installed into the DocApp.
+        """
+        return self._services
+
+
+    def GetService(self, type):
+        """
+        Returns the instance of a particular type of service that has been installed
+        into the DocApp.  For example, "wx.GetApp().GetService(pydocview.OptionsService)"
+        returns the isntance of the OptionsService that is running within the DocApp.
+        """
+        for service in self._services:
+            if isinstance(service, type):
+                return service
+        return None
+
+
+    def OnExit(self):
+        """
+        Called when the DocApp is exited, enables the installed DocServices to exit
+        and saves the DocManager's file history.
+        """
+        for service in self._services:
+            service.OnExit()
+        config = wx.ConfigBase_Get()
+        self._docManager.FileHistorySave(config)
+        
+        if hasattr(self, "_singleInstanceChecker"):
+            del self._singleInstanceChecker
+
+    
+    def GetDefaultDocManagerFlags(self):
+        """
+        Returns the default flags to use when creating the DocManager.
+        """
+        config = wx.ConfigBase_Get()
+        if config.ReadInt("UseMDI", True) or config.ReadInt("UseWinMDI", False):
+            flags = wx.lib.docview.DOC_MDI | wx.lib.docview.DOC_OPEN_ONCE
+            if config.ReadInt("UseWinMDI", False):
+                self.SetUseTabbedMDI(False)