+    # new stuff
+    ('Recent Additions/Updates', [
+        'StandardPaths',
+        ]),
+
+    # managed windows == things with a (optional) caption you can close
+    ('Frames and Dialogs', [
+        'Dialog',
+        'Frame',
+        'MDIWindows',
+        'MiniFrame',
+        'Wizard',
+        ]),
+
+    # the common dialogs
+    ('Common Dialogs', [
+        'ColourDialog',
+        'DirDialog',
+        'FileDialog',
+        'FindReplaceDialog',
+        'FontDialog',
+        'MessageDialog',
+        'PageSetupDialog',
+        'PrintDialog',
+        'ProgressDialog',
+        'SingleChoiceDialog',
+        'TextEntryDialog',
+        ]),
+
+    # dialogs from libraries
+    ('More Dialogs', [
+        'ImageBrowser',
+        'MultipleChoiceDialog',
+        'ScrolledMessageDialog',
+        ]),
+
+    # core controls
+    ('Core Windows/Controls', [
+        'BitmapButton',
+        'Button',
+        'CheckBox',
+        'CheckListBox',
+        'Choice',
+        'Choicebook',
+        'ComboBox',
+        'Gauge',
+        'Grid',
+        'Grid_MegaExample',
+        'ListBox',
+        'ListCtrl',
+        'ListCtrl_virtual',
+        'ListCtrl_edit',
+        'Listbook',
+        'Menu',
+        'Notebook',
+        'PopupMenu',
+        'PopupWindow',
+        'RadioBox',
+        'RadioButton',
+        'SashWindow',
+        'ScrolledWindow',
+        'Slider',
+        'SpinButton',
+        'SpinCtrl',
+        'SplitterWindow',
+        'StaticBitmap',
+        'StaticText',
+        'StatusBar',
+        'StockButtons',
+        'TextCtrl',
+        'ToggleButton',
+        'ToolBar',
+        'TreeCtrl',
+        'Validator',
+        ]),
+
+    ('Custom Controls', [
+        'AnalogClockWindow',
+        'ColourSelect',
+        'Editor',
+        'GenericButtons',
+        'GenericDirCtrl',
+        'LEDNumberCtrl',
+        'MultiSash',
+        'PopupControl',
+        'PyColourChooser',
+        'TreeListCtrl',
+    ]),
+    
+    # controls coming from other libraries
+    ('More Windows/Controls', [
+        'ActiveX_FlashWindow',
+        'ActiveX_IEHtmlWindow',
+        'ActiveX_PDFWindow',
+        #'RightTextCtrl',     deprecated as we have wxTE_RIGHT now.
+        'Calendar',
+        'CalendarCtrl',
+        'ContextHelp',
+        'DynamicSashWindow',
+        'EditableListBox',
+        'FancyText',
+        'FileBrowseButton',
+        'FloatBar',  
+        'FloatCanvas',
+        'HtmlWindow',
+        'IntCtrl',
+        'MVCTree',   
+        'MaskedEditControls',
+        'MaskedNumCtrl',
+        'MimeTypesManager',
+        'PyCrust',
+        'PyPlot',
+        'PyShell',
+        'ScrolledPanel',
+        'SplitTree',
+        'StyledTextCtrl_1',
+        'StyledTextCtrl_2',
+        'TablePrint',
+        'Throbber',
+        'Ticker',
+        'TimeCtrl',
+        'VListBox',
+        ]),
+
+    # How to lay out the controls in a frame/dialog
+    ('Window Layout', [
+        'GridBagSizer',
+        'LayoutAnchors',
+        'LayoutConstraints',
+        'Layoutf',
+        'RowColSizer',
+        'ScrolledPanel',
+        'Sizers',
+        'XmlResource',
+        'XmlResourceHandler',
+        'XmlResourceSubclass',
+        ]),
+
+    # ditto
+    ('Process and Events', [
+        'EventManager',
+        'KeyEvents',
+        'Process',
+        'PythonEvents',
+        'Threads',
+        'Timer',
+        ##'infoframe',    # needs better explaination and some fixing
+        ]),
+
+    # Clipboard and DnD
+    ('Clipboard and DnD', [
+        'CustomDragAndDrop',
+        'DragAndDrop',
+        'URLDragAndDrop',
+        ]),
+
+    # Images
+    ('Using Images', [
+        'ArtProvider',
+        'Cursor',
+        'DragImage',
+        'Image',
+        'ImageAlpha',
+        'ImageFromStream',
+        'Mask',
+        'Throbber',
+        ]),
+
+    # Other stuff
+    ('Miscellaneous', [
+        'ColourDB',
+        ##'DialogUnits',   # needs more explainations
+        'DrawXXXList',
+        'FileHistory',
+        'FontEnumerator',
+        'Joystick',
+        'OGL',
+        'PrintFramework',
+        'ShapedWindow',
+        'Sound',
+        'StandardPaths',
+        'Unicode',
+        ]),
+
+    # need libs not coming with the demo
+    ('Samples using an external library', [
+        'GLCanvas',
+        ]),
+
+
+    ('Check out the samples dir too', [
+        ]),
+
+]
+
+
+
+#---------------------------------------------------------------------------
+# Show how to derive a custom wxLog class
+
+class MyLog(wx.PyLog):
+    def __init__(self, textCtrl, logTime=0):
+        wx.PyLog.__init__(self)
+        self.tc = textCtrl
+        self.logTime = logTime
+
+    def DoLogString(self, message, timeStamp):
+        #print message, timeStamp
+        #if self.logTime:
+        #    message = time.strftime("%X", time.localtime(timeStamp)) + \
+        #              ": " + message
+        if self.tc:
+            self.tc.AppendText(message + '\n')
+
+
+class MyTP(wx.PyTipProvider):
+    def GetTip(self):
+        return "This is my tip"
+
+#---------------------------------------------------------------------------
+# A class to be used to simply display a message in the demo pane
+# rather than running the sample itself.
+
+class MessagePanel(wx.Panel):
+    def __init__(self, parent, message, caption='', flags=0):
+        wx.Panel.__init__(self, parent)
+
+        # Make widgets
+        if flags:
+            artid = None
+            if flags & wx.ICON_EXCLAMATION:
+                artid = wx.ART_WARNING            
+            elif flags & wx.ICON_ERROR:
+                artid = wx.ART_ERROR
+            elif flags & wx.ICON_QUESTION:
+                artid = wx.ART_QUESTION
+            elif flags & wx.ICON_INFORMATION:
+                artid = wx.ART_INFORMATION
+
+            if artid is not None:
+                bmp = wx.ArtProvider.GetBitmap(artid, wx.ART_MESSAGE_BOX, (32,32))
+                icon = wx.StaticBitmap(self, -1, bmp)
+            else:
+                icon = (32,32) # make a spacer instead
+
+        if caption:
+            caption = wx.StaticText(self, -1, caption)
+            caption.SetFont(wx.Font(28, wx.SWISS, wx.NORMAL, wx.BOLD))
+
+        message = wx.StaticText(self, -1, message)
+
+        # add to sizers for layout
+        tbox = wx.BoxSizer(wx.VERTICAL)
+        if caption:
+            tbox.Add(caption)
+            tbox.Add((10,10))
+        tbox.Add(message)
+        
+        hbox = wx.BoxSizer(wx.HORIZONTAL)
+        hbox.Add((10,10), 1)
+        hbox.Add(icon)
+        hbox.Add((10,10))
+        hbox.Add(tbox)
+        hbox.Add((10,10), 1)
+
+        box = wx.BoxSizer(wx.VERTICAL)
+        box.Add((10,10), 1)
+        box.Add(hbox, 0, wx.EXPAND)
+        box.Add((10,10), 2)
+
+        self.SetSizer(box)
+        
+        
+
+#---------------------------------------------------------------------------
+# A class to be used to display source code in the demo.  Try using the
+# wxSTC in the StyledTextCtrl_2 sample first, fall back to wxTextCtrl
+# if there is an error, such as the stc module not being present.
+#
+
+try:
+    ##raise ImportError     # for testing the alternate implementation
+    from wx import stc
+    from StyledTextCtrl_2 import PythonSTC
+
+    class DemoCodeEditor(PythonSTC):
+        def __init__(self, parent):
+            PythonSTC.__init__(self, parent, -1, style=wx.BORDER_NONE)
+            self.SetUpEditor()
+
+        # Some methods to make it compatible with how the wxTextCtrl is used
+        def SetValue(self, value):
+            if wx.USE_UNICODE:
+                value = value.decode('iso8859_1')
+            self.SetText(value)
+            self.EmptyUndoBuffer()
+            self.SetSavePoint()
+
+        def IsModified(self):
+            return self.GetModify()
+
+        def Clear(self):
+            self.ClearAll()
+
+        def SetInsertionPoint(self, pos):
+            self.SetCurrentPos(pos)
+            self.SetAnchor(pos)
+
+        def ShowPosition(self, pos):
+            line = self.LineFromPosition(pos)
+            #self.EnsureVisible(line)
+            self.GotoLine(line)
+
+        def GetLastPosition(self):
+            return self.GetLength()
+
+        def GetPositionFromLine(self, line):
+            return self.PositionFromLine(line)
+
+        def GetRange(self, start, end):
+            return self.GetTextRange(start, end)
+
+        def GetSelection(self):
+            return self.GetAnchor(), self.GetCurrentPos()
+
+        def SetSelection(self, start, end):
+            self.SetSelectionStart(start)
+            self.SetSelectionEnd(end)
+
+        def SelectLine(self, line):
+            start = self.PositionFromLine(line)
+            end = self.GetLineEndPosition(line)
+            self.SetSelection(start, end)
+            
+        def SetUpEditor(self):
+            """
+            This method carries out the work of setting up the demo editor.            
+            It's seperate so as not to clutter up the init code.
+            """
+            import keyword
+            
+            self.SetLexer(stc.STC_LEX_PYTHON)
+            self.SetKeyWords(0, " ".join(keyword.kwlist))
+    
+            # Enable folding
+            self.SetProperty("fold", "1" ) 
+
+            # Highlight tab/space mixing (shouldn't be any)
+            self.SetProperty("tab.timmy.whinge.level", "1")
+
+            # Set left and right margins
+            self.SetMargins(2,2)
+    
+            # Set up the numbers in the margin for margin #1
+            self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
+            # Reasonable value for, say, 4-5 digits using a mono font (40 pix)
+            self.SetMarginWidth(1, 40)
+    
+            # Indentation and tab stuff
+            self.SetIndent(4)               # Proscribed indent size for wx
+            self.SetIndentationGuides(True) # Show indent guides
+            self.SetBackSpaceUnIndents(True)# Backspace unindents rather than delete 1 space
+            self.SetTabIndents(True)        # Tab key indents
+            self.SetTabWidth(4)             # Proscribed tab size for wx
+            self.SetUseTabs(False)          # Use spaces rather than tabs, or
+                                            # TabTimmy will complain!    
+            # White space
+            self.SetViewWhiteSpace(False)   # Don't view white space
+    
+            # EOL: Since we are loading/saving ourselves, and the
+            # strings will always have \n's in them, set the STC to
+            # edit them that way.            
+            self.SetEOLMode(wx.stc.STC_EOL_LF)
+            self.SetViewEOL(False)
+            
+            # No right-edge mode indicator
+            self.SetEdgeMode(stc.STC_EDGE_NONE)
+    
+            # Setup a margin to hold fold markers
+            self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
+            self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
+            self.SetMarginSensitive(2, True)
+            self.SetMarginWidth(2, 12)
+    
+            # and now set up the fold markers
+            self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_BOXPLUSCONNECTED,  "white", "black")
+            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "black")
+            self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,  "white", "black")
+            self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNER,  "white", "black")
+            self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,    "white", "black")
+            self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_BOXPLUS,  "white", "black")
+            self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_BOXMINUS, "white", "black")
+    
+            # Global default style
+            if wx.Platform == '__WXMSW__':
+                self.StyleSetSpec(stc.STC_STYLE_DEFAULT, 
+                                  'fore:#000000,back:#FFFFFF,face:Courier New,size:9')
+            else:
+                self.StyleSetSpec(stc.STC_STYLE_DEFAULT, 
+                                  'fore:#000000,back:#FFFFFF,face:Courier,size:12')
+    
+            # Clear styles and revert to default.
+            self.StyleClearAll()
+
+            # Following style specs only indicate differences from default.
+            # The rest remains unchanged.
+
+            # Line numbers in margin
+            self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2')    
+            # Highlighted brace
+            self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00')
+            # Unmatched brace
+            self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000')
+            # Indentation guide
+            self.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD")
+    
+            # Python styles
+            self.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000')
+            # Comments
+            self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE,  'fore:#008000,back:#F0FFF0')
+            self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0')
+            # Numbers
+            self.StyleSetSpec(wx.stc.STC_P_NUMBER, 'fore:#008080')
+            # Strings and characters
+            self.StyleSetSpec(wx.stc.STC_P_STRING, 'fore:#800080')
+            self.StyleSetSpec(wx.stc.STC_P_CHARACTER, 'fore:#800080')
+            # Keywords
+            self.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold')
+            # Triple quotes
+            self.StyleSetSpec(wx.stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA')
+            self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA')
+            # Class names
+            self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, 'fore:#0000FF,bold')
+            # Function names
+            self.StyleSetSpec(wx.stc.STC_P_DEFNAME, 'fore:#008080,bold')
+            # Operators
+            self.StyleSetSpec(wx.stc.STC_P_OPERATOR, 'fore:#800000,bold')
+            # Identifiers. I leave this as not bold because everything seems
+            # to be an identifier if it doesn't match the above criterae
+            self.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, 'fore:#000000')
+
+            # Caret color
+            self.SetCaretForeground("BLUE")
+            # Selection background
+            self.SetSelBackground(1, '#66CCFF')
+
+            self.SetSelBackground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT))
+            self.SetSelForeground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
+
+        def RegisterModifiedEvent(self, eventHandler):
+            self.Bind(wx.stc.EVT_STC_CHANGE, eventHandler)
+
+
+except ImportError:
+    class DemoCodeEditor(wx.TextCtrl):
+        def __init__(self, parent):
+            wx.TextCtrl.__init__(self, parent, -1, style =
+                                 wx.TE_MULTILINE | wx.HSCROLL | wx.TE_RICH2 | wx.TE_NOHIDESEL)
+
+        def RegisterModifiedEvent(self, eventHandler):
+            self.Bind(wx.EVT_TEXT, eventHandler)
+
+        def SetReadOnly(self, flag):
+            self.SetEditable(not flag)
+            # NOTE: STC already has this method
+    
+        def GetText(self):
+            return self.GetValue()
+
+        def GetPositionFromLine(self, line):
+            return self.XYToPosition(0,line)
+
+        def GotoLine(self, line):
+            pos = self.GetPositionFromLine(line)
+            self.SetInsertionPoint(pos)
+            self.ShowPosition(pos)
+
+        def SelectLine(self, line):
+            start = self.GetPositionFromLine(line)
+            end = start + self.GetLineLength(line)
+            self.SetSelection(start, end)
+
+
+#---------------------------------------------------------------------------
+# Constants for module versions
+
+modOriginal = 0
+modModified = 1
+modDefault = modOriginal
+
+#---------------------------------------------------------------------------
+
+class DemoCodePanel(wx.Panel):
+    """Panel for the 'Demo Code' tab"""
+    def __init__(self, parent, mainFrame):
+        wx.Panel.__init__(self, parent, size=(1,1))
+        if 'wxMSW' in wx.PlatformInfo:
+            self.Hide()
+        self.mainFrame = mainFrame
+        self.editor = DemoCodeEditor(self)
+        self.editor.RegisterModifiedEvent(self.OnCodeModified)
+
+        self.btnSave = wx.Button(self, -1, "Save Changes")
+        self.btnRestore = wx.Button(self, -1, "Delete Modified")
+        self.btnSave.Enable(False)
+        self.btnSave.Bind(wx.EVT_BUTTON, self.OnSave)
+        self.btnRestore.Bind(wx.EVT_BUTTON, self.OnRestore)
+
+        self.radioButtons = { modOriginal: wx.RadioButton(self, -1, "Original", style = wx.RB_GROUP),
+                              modModified: wx.RadioButton(self, -1, "Modified") }
+
+        self.controlBox = wx.BoxSizer(wx.HORIZONTAL)
+        self.controlBox.Add(wx.StaticText(self, -1, "Active Version:"), 0,
+                            wx.RIGHT | wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5)
+        for modID, radioButton in self.radioButtons.items():
+            self.controlBox.Add(radioButton, 0, wx.EXPAND | wx.RIGHT, 5)
+            radioButton.modID = modID # makes it easier for the event handler
+            radioButton.Bind(wx.EVT_RADIOBUTTON, self.OnRadioButton)
+            
+        self.controlBox.Add(self.btnSave, 0, wx.RIGHT, 5)
+        self.controlBox.Add(self.btnRestore, 0)
+
+        self.box = wx.BoxSizer(wx.VERTICAL)
+        self.box.Add(self.controlBox, 0, wx.EXPAND)
+        self.box.Add(wx.StaticLine(self), 0, wx.EXPAND)
+        self.box.Add(self.editor, 1, wx.EXPAND)
+        
+        self.box.Fit(self)
+        self.SetSizer(self.box)
+
+
+    # Loads a demo from a DemoModules object
+    def LoadDemo(self, demoModules):
+        self.demoModules = demoModules
+        if (modDefault == modModified) and demoModules.Exists(modModified):
+            demoModules.SetActive(modModified)
+        else:
+            demoModules.SetActive(modOriginal)
+        self.radioButtons[demoModules.GetActiveID()].Enable(True)
+        self.ActiveModuleChanged()
+
+
+    def ActiveModuleChanged(self):
+        self.LoadDemoSource(self.demoModules.GetSource())
+        self.UpdateControlState()
+        self.ReloadDemo()
+
+        
+    def LoadDemoSource(self, source):
+        self.editor.Clear()
+        self.editor.SetValue(source)
+        self.JumpToLine(0)
+        self.btnSave.Enable(False)
+
+
+    def JumpToLine(self, line, highlight=False):
+        self.editor.GotoLine(line)
+        self.editor.SetFocus()
+        if highlight:
+            self.editor.SelectLine(line)
+        
+       
+    def UpdateControlState(self):
+        active = self.demoModules.GetActiveID()
+        # Update the radio/restore buttons
+        for moduleID in self.radioButtons:
+            btn = self.radioButtons[moduleID]
+            if moduleID == active:
+                btn.SetValue(True)
+            else:
+                btn.SetValue(False)
+
+            if self.demoModules.Exists(moduleID):
+                btn.Enable(True)
+                if moduleID == modModified:
+                    self.btnRestore.Enable(True)
+            else:
+                btn.Enable(False)
+                if moduleID == modModified:
+                    self.btnRestore.Enable(False)
+
+                    
+    def OnRadioButton(self, event):
+        radioSelected = event.GetEventObject()
+        modSelected = radioSelected.modID
+        if modSelected != self.demoModules.GetActiveID():
+            busy = wx.BusyInfo("Reloading demo module...")
+            self.demoModules.SetActive(modSelected)
+            self.ActiveModuleChanged()
+
+
+    def ReloadDemo(self):
+        if self.demoModules.name != __name__:
+            self.mainFrame.RunModule()
+
+                
+    def OnCodeModified(self, event):
+        self.btnSave.Enable(self.editor.IsModified())
+
+        
+    def OnSave(self, event):
+        if self.demoModules.Exists(modModified):
+            if self.demoModules.GetActiveID() == modOriginal:
+                overwriteMsg = "You are about to overwrite an already existing modified copy\n" + \
+                               "Do you want to continue?"
+                dlg = wx.MessageDialog(self, overwriteMsg, "wxPython Demo",
+                                       wx.YES_NO | wx.NO_DEFAULT| wx.ICON_EXCLAMATION)
+                result = dlg.ShowModal()
+                if result == wx.ID_NO:
+                    return
+                dlg.Destroy()
+            
+        self.demoModules.SetActive(modModified)
+        modifiedFilename = GetModifiedFilename(self.demoModules.name)
+
+        # Create the demo directory if one doesn't already exist
+        if not os.path.exists(GetModifiedDirectory()):
+            try:
+                os.makedirs(GetModifiedDirectory())
+                if not os.path.exists(GetModifiedDirectory()):
+                    wx.LogMessage("BUG: Created demo directory but it still doesn't exit")
+                    raise AssetionError
+            except:
+                wx.LogMessage("Error creating demo directory: %s" % GetModifiedDirectory())
+                return
+            else:
+                wx.LogMessage("Created directory for modified demos: %s" % GetModifiedDirectory())
+            
+        # Save
+        f = open(modifiedFilename, "wt")
+        source = self.editor.GetText()
+        try:
+            f.write(source)
+        finally:
+            f.close()
+            
+        busy = wx.BusyInfo("Reloading demo module...")
+        self.demoModules.LoadFromFile(modModified, modifiedFilename)
+        self.ActiveModuleChanged()
+
+
+    def OnRestore(self, event): # Handles the "Delete Modified" button
+        modifiedFilename = GetModifiedFilename(self.demoModules.name)
+        self.demoModules.Delete(modModified)
+        os.unlink(modifiedFilename) # Delete the modified copy
+        busy = wx.BusyInfo("Reloading demo module...")
+        self.ActiveModuleChanged()
+
+
+#---------------------------------------------------------------------------
+
+def opj(path):
+    """Convert paths to the platform-specific separator"""
+    str = apply(os.path.join, tuple(path.split('/')))
+    # HACK: on Linux, a leading / gets lost...
+    if path.startswith('/'):
+        str = '/' + str
+    return str
+
+
+def GetModifiedDirectory():
+    """
+    Returns the directory where modified versions of the demo files
+    are stored
+    """
+    return opj(wx.GetHomeDir() + "/.wxPyDemo/modified/")
+
+
+def GetModifiedFilename(name):
+    """
+    Returns the filename of the modified version of the specified demo
+    """
+    if not name.endswith(".py"):
+        name = name + ".py"
+    return GetModifiedDirectory() + name
+
+
+def GetOriginalFilename(name):
+    """
+    Returns the filename of the original version of the specified demo
+    """
+    if not name.endswith(".py"):
+        name = name + ".py"
+    return name
+
+
+def DoesModifiedExist(name):
+    """Returns whether the specified demo has a modified copy"""
+    if os.path.exists(GetModifiedFilename(name)):
+        return True
+    else:
+        return False
+
+
+#---------------------------------------------------------------------------
+
+class ModuleDictWrapper:
+    """Emulates a module with a dynamically compiled __dict__"""
+    def __init__(self, dict):
+        self.dict = dict
+        
+    def __getattr__(self, name):
+        if name in self.dict:
+            return self.dict[name]
+        else:
+            raise AttributeError
+
+class DemoModules:
+    """
+    Dynamically manages the original/modified versions of a demo
+    module
+    """
+    def __init__(self, name):
+        self.modActive = -1
+        self.name = name
+        
+        #              (dict , source ,  filename , description   , error information )        
+        #              (  0  ,   1    ,     2     ,      3        ,          4        )        
+        self.modules = [[None,  ""    ,    ""     , "<original>"  ,        None],
+                        [None,  ""    ,    ""     , "<modified>"  ,        None]]
+        
+        # load original module
+        self.LoadFromFile(modOriginal, GetOriginalFilename(name))
+        self.SetActive(modOriginal)
+
+        # load modified module (if one exists)
+        if DoesModifiedExist(name):
+           self.LoadFromFile(modModified, GetModifiedFilename(name))
+
+
+    def LoadFromFile(self, modID, filename):
+        self.modules[modID][2] = filename
+        file = open(filename, "rt")
+        self.LoadFromSource(modID, file.read())
+        file.close()
+
+
+    def LoadFromSource(self, modID, source):
+        self.modules[modID][1] = source
+        self.LoadDict(modID)
+
+
+    def LoadDict(self, modID):
+        if self.name != __name__:
+            source = self.modules[modID][1]
+            description = self.modules[modID][3]
+
+            try:
+                self.modules[modID][0] = {}
+                code = compile(source, description, "exec")        
+                exec code in self.modules[modID][0]
+            except:
+                self.modules[modID][4] = DemoError(sys.exc_info())
+                self.modules[modID][0] = None
+            else:
+                self.modules[modID][4] = None
+
+
+    def SetActive(self, modID):
+        if modID != modOriginal and modID != modModified:
+            raise LookupError
+        else:
+            self.modActive = modID
+
+
+    def GetActive(self):
+        dict = self.modules[self.modActive][0]
+        if dict is None:
+            return None
+        else:
+            return ModuleDictWrapper(dict)
+
+
+    def GetActiveID(self):
+        return self.modActive
+
+    
+    def GetSource(self, modID = None):
+        if modID is None:
+            modID = self.modActive
+        return self.modules[modID][1]
+
+
+    def GetFilename(self, modID = None):
+        if modID is None:
+            modID = self.modActive
+        return self.modules[self.modActive][2]
+
+
+    def GetErrorInfo(self, modID = None):
+        if modID is None:
+            modID = self.modActive
+        return self.modules[self.modActive][4]
+
+
+    def Exists(self, modID):
+        return self.modules[modID][1] != ""
+
+
+    def UpdateFile(self, modID = None):
+        """Updates the file from which a module was loaded
+        with (possibly updated) source"""
+        if modID is None:
+            modID = self.modActive
+
+        source = self.modules[modID][1]
+        filename = self.modules[modID][2]
+
+        try:        
+            file = open(filename, "wt")
+            file.write(source)
+        finally:
+            file.close()
+
+
+    def Delete(self, modID):
+        if self.modActive == modID:
+            self.SetActive(0)
+
+        self.modules[modID][0] = None
+        self.modules[modID][1] = ""
+        self.modules[modID][2] = ""
+
+
+#---------------------------------------------------------------------------
+
+class DemoError:
+    """Wraps and stores information about the current exception"""
+    def __init__(self, exc_info):
+        import copy
+        
+        excType, excValue = exc_info[:2]
+        # traceback list entries: (filename, line number, function name, text)
+        self.traceback = traceback.extract_tb(exc_info[2])
+
+        # --Based on traceback.py::format_exception_only()--
+        if type(excType) == types.ClassType:
+            self.exception_type = excType.__name__
+        else:
+            self.exception_type = excType
+
+        # If it's a syntax error, extra information needs
+        # to be added to the traceback
+        if excType is SyntaxError:
+            try:
+                msg, (filename, lineno, self.offset, line) = excValue
+            except:
+                pass
+            else:
+                if not filename:
+                    filename = "<string>"
+                line = line.strip()
+                self.traceback.append( (filename, lineno, "", line) )
+                excValue = msg
+        try:
+            self.exception_details = str(excValue)
+        except:
+            self.exception_details = "<unprintable %s object>" & type(excValue).__name__
+
+        del exc_info
+        
+    def __str__(self):
+        ret = "Type %s \n \
+        Traceback: %s \n \
+        Details  : %s" % ( str(self.exception_type), str(self.traceback), self.exception_details )
+        return ret
+
+#---------------------------------------------------------------------------
+
+class DemoErrorPanel(wx.Panel):
+    """Panel put into the demo tab when the demo fails to run due  to errors"""
+
+    def __init__(self, parent, codePanel, demoError, log):
+        wx.Panel.__init__(self, parent, -1)#, style=wx.NO_FULL_REPAINT_ON_RESIZE)
+        self.codePanel = codePanel
+        self.nb = parent
+        self.log = log
+
+        self.box = wx.BoxSizer(wx.VERTICAL)
+
+        # Main Label
+        self.box.Add(wx.StaticText(self, -1, "An error has occured while trying to run the demo")
+                     , 0, wx.ALIGN_CENTER | wx.TOP, 10)
+
+        # Exception Information
+        boxInfo      = wx.StaticBox(self, -1, "Exception Info" )
+        boxInfoSizer = wx.StaticBoxSizer(boxInfo, wx.VERTICAL ) # Used to center the grid within the box
+        boxInfoGrid  = wx.FlexGridSizer(0, 2, 0, 0)
+        textFlags    = wx.ALIGN_RIGHT | wx.LEFT | wx.RIGHT | wx.TOP
+        boxInfoGrid.Add(wx.StaticText(self, -1, "Type: "), 0, textFlags, 5 )
+        boxInfoGrid.Add(wx.StaticText(self, -1, demoError.exception_type) , 0, textFlags, 5 )
+        boxInfoGrid.Add(wx.StaticText(self, -1, "Details: ") , 0, textFlags, 5 )
+        boxInfoGrid.Add(wx.StaticText(self, -1, demoError.exception_details) , 0, textFlags, 5 )
+        boxInfoSizer.Add(boxInfoGrid, 0, wx.ALIGN_CENTRE | wx.ALL, 5 )
+        self.box.Add(boxInfoSizer, 0, wx.ALIGN_CENTER | wx.ALL, 5)
+       
+        # Set up the traceback list
+        # This one automatically resizes last column to take up remaining space
+        from ListCtrl import TestListCtrl
+        self.list = TestListCtrl(self, -1, style=wx.LC_REPORT  | wx.SUNKEN_BORDER)
+        self.list.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
+        self.list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
+        self.list.InsertColumn(0, "Filename")
+        self.list.InsertColumn(1, "Line", wx.LIST_FORMAT_RIGHT)
+        self.list.InsertColumn(2, "Function")
+        self.list.InsertColumn(3, "Code")
+        self.InsertTraceback(self.list, demoError.traceback)
+        self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
+        self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE)
+        self.box.Add(wx.StaticText(self, -1, "Traceback:")
+                     , 0, wx.ALIGN_CENTER | wx.TOP, 5)
+        self.box.Add(self.list, 1, wx.GROW | wx.ALIGN_CENTER | wx.ALL, 5)
+        self.box.Add(wx.StaticText(self, -1, "Entries from the demo module are shown in blue\n"
+                                           + "Double-click on them to go to the offending line")
+                     , 0, wx.ALIGN_CENTER | wx.BOTTOM, 5)
+
+        self.box.Fit(self)
+        self.SetSizer(self.box)
+
+
+    def InsertTraceback(self, list, traceback):
+        #Add the traceback data
+        for x in range(len(traceback)):
+            data = traceback[x]
+            list.InsertStringItem(x, os.path.basename(data[0])) # Filename
+            list.SetStringItem(x, 1, str(data[1]))              # Line
+            list.SetStringItem(x, 2, str(data[2]))              # Function
+            list.SetStringItem(x, 3, str(data[3]))              # Code
+            
+            # Check whether this entry is from the demo module
+            if data[0] == "<original>" or data[0] == "<modified>": # FIXME: make more generalised
+                self.list.SetItemData(x, int(data[1]))   # Store line number for easy access
+                # Give it a blue colour
+                item = self.list.GetItem(x)
+                item.SetTextColour(wx.BLUE)
+                self.list.SetItem(item)
+            else:
+                self.list.SetItemData(x, -1)        # Editor can't jump into this one's code
+       
+
+    def OnItemSelected(self, event):
+        # This occurs before OnDoubleClick and can be used to set the
+        # currentItem. OnDoubleClick doesn't get a wxListEvent....
+        self.currentItem = event.m_itemIndex
+        event.Skip()
+
+        
+    def OnDoubleClick(self, event):
+        # If double-clicking on a demo's entry, jump to the line number
+        line = self.list.GetItemData(self.currentItem)
+        if line != -1:
+            self.nb.SetSelection(1) # Switch to the code viewer tab
+            wx.CallAfter(self.codePanel.JumpToLine, line-1, True)
+        event.Skip()
+