]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/demo/Main.py
A little cleanup
[wxWidgets.git] / wxPython / demo / Main.py
index a9c4c3bdd1b1b8f14eaa6425f428dd8f3f1f4f1d..c951870a3b3752d80b42d9e53c471cb620041a61 100644 (file)
 # Licence:      wxWindows license
 #----------------------------------------------------------------------------
 
-import sys, os, time
+# FIXME List:
+# * Problems with flickering related to ERASE_BACKGROUND
+#     and the splitters. Might be a problem with this 2.5 beta...?
+#     UPDATE: can't see on 2.5.2 GTK - maybe just a faster machine :)
+# * Demo Code menu?
+# * Annoying switching between tabs and resulting flicker
+#     how to replace a page in the notebook without deleting/adding?
+#     Where is SetPage!? tried freeze...tried reparent of dummy panel....
+
+# TODO List:
+# * UI design more prefessional
+# * save file positions (new field in demoModules) (@ LoadDemoSource)
+# * Update main overview
+
+# * Why don't we move _treeList into a separate module
+
+import sys, os, time, traceback, types
 
 import wx                  # This module uses the new wx namespace
 import wx.html
@@ -20,7 +36,9 @@ import images
 
 # For debugging
 ##wx.Trap();
-##print os.getpid(); raw_input("Press a key...")
+##print "wx.VERSION_STRING = ", wx.VERSION_STRING
+##print os.getpid();
+##raw_input("Press Enter...")
 
 
 #---------------------------------------------------------------------------
@@ -28,19 +46,14 @@ import images
 
 _treeList = [
     # new stuff
-    ('Recent Additions', [
-        'VListBox',
-        'Listbook',
-        'MaskedNumCtrl',
-        'FloatCanvas',
-        'XmlResourceSubclass',
-        'GridBagSizer',
-        'Cursor',
-        'PyPlot',
+    ('Recent Additions/Updates', [
+        'FoldPanelBar',
+        'GIFAnimationCtrl',
+        'HyperLinkCtrl',
         ]),
 
     # managed windows == things with a (optional) caption you can close
-    ('Base Frames and Dialogs', [
+    ('Frames and Dialogs', [
         'Dialog',
         'Frame',
         'MDIWindows',
@@ -53,10 +66,10 @@ _treeList = [
         'ColourDialog',
         'DirDialog',
         'FileDialog',
-        'FileDialog_Save',
         'FindReplaceDialog',
         'FontDialog',
         'MessageDialog',
+        'MultiChoiceDialog',
         'PageSetupDialog',
         'PrintDialog',
         'ProgressDialog',
@@ -66,7 +79,6 @@ _treeList = [
 
     # dialogs from libraries
     ('More Dialogs', [
-        'ErrorDialogs',
         'ImageBrowser',
         'MultipleChoiceDialog',
         'ScrolledMessageDialog',
@@ -74,10 +86,12 @@ _treeList = [
 
     # core controls
     ('Core Windows/Controls', [
+        'BitmapButton',
         'Button',
         'CheckBox',
         'CheckListBox',
         'Choice',
+        'Choicebook',
         'ComboBox',
         'Gauge',
         'Grid',
@@ -85,6 +99,7 @@ _treeList = [
         'ListBox',
         'ListCtrl',
         'ListCtrl_virtual',
+        'ListCtrl_edit',
         'Listbook',
         'Menu',
         'Notebook',
@@ -99,8 +114,10 @@ _treeList = [
         'SpinCtrl',
         'SplitterWindow',
         'StaticBitmap',
+        'StaticBox',
         'StaticText',
         'StatusBar',
+        'StockButtons',
         'TextCtrl',
         'ToggleButton',
         'ToolBar',
@@ -123,19 +140,26 @@ _treeList = [
     
     # 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',
+        'DatePickerCtrl',
         'DynamicSashWindow',
         'EditableListBox',
         'FancyText',
         'FileBrowseButton',
         'FloatBar',  
         'FloatCanvas',
+        'FoldPanelBar',
+        'GIFAnimationCtrl',
         'HtmlWindow',
-        'IEHtmlWin',
+        'HyperLinkCtrl',
         'IntCtrl',
+        'MediaCtrl',
         'MVCTree',   
         'MaskedEditControls',
         'MaskedNumCtrl',
@@ -149,6 +173,7 @@ _treeList = [
         'StyledTextCtrl_2',
         'TablePrint',
         'Throbber',
+        'Ticker',
         'TimeCtrl',
         'VListBox',
         ]),
@@ -171,12 +196,11 @@ _treeList = [
     ('Process and Events', [
         'EventManager',
         'KeyEvents',
-        'OOR',
         'Process',
         'PythonEvents',
         'Threads',
         'Timer',
-        'infoframe',
+        ##'infoframe',    # needs better explanation and some fixing
         ]),
 
     # Clipboard and DnD
@@ -191,7 +215,9 @@ _treeList = [
         'ArtProvider',
         'Cursor',
         'DragImage',
+        'GIFAnimationCtrl',
         'Image',
+        'ImageAlpha',
         'ImageFromStream',
         'Mask',
         'Throbber',
@@ -200,25 +226,23 @@ _treeList = [
     # Other stuff
     ('Miscellaneous', [
         'ColourDB',
-        'DialogUnits',
+        ##'DialogUnits',   # needs more explanations
         'DrawXXXList',
         'FileHistory',
         'FontEnumerator',
         'Joystick',
-        'NewNamespace',
+        'MouseGestures',
         'OGL',
         'PrintFramework',
         'ShapedWindow',
         'Sound',
+        'StandardPaths',
         'Unicode',
         ]),
 
     # need libs not coming with the demo
-    ('Objects using an external library', [
-        'ActiveXWrapper_Acrobat',
-        'ActiveXWrapper_IE',
+    ('Samples using an external library', [
         'GLCanvas',
-        #'PlotCanvas', # deprecated, use PyPlot
         ]),
 
 
@@ -239,9 +263,10 @@ class MyLog(wx.PyLog):
         self.logTime = logTime
 
     def DoLogString(self, message, timeStamp):
-        if self.logTime:
-            message = time.strftime("%X", time.localtime(timeStamp)) + \
-                      ": " + message
+        #print message, timeStamp
+        #if self.logTime:
+        #    message = time.strftime("%X", time.localtime(timeStamp)) + \
+        #              ": " + message
         if self.tc:
             self.tc.AppendText(message + '\n')
 
@@ -250,6 +275,61 @@ 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)
+        self.Fit()
+        
+
 #---------------------------------------------------------------------------
 # 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
@@ -257,32 +337,44 @@ class MyTP(wx.PyTipProvider):
 #
 
 try:
-    ##raise ImportError
+    ##raise ImportError     # for testing the alternate implementation
     from wx import stc
     from StyledTextCtrl_2 import PythonSTC
-    class DemoCodeViewer(PythonSTC):
-        def __init__(self, parent, ID):
-            PythonSTC.__init__(self, parent, ID)
+
+    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):
-            self.SetReadOnly(False)
+            if wx.USE_UNICODE:
+                value = value.decode('iso8859_1')
             self.SetText(value)
-            self.SetReadOnly(True)
+            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):
-            self.GotoPos(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)
 
@@ -293,6 +385,11 @@ try:
             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.            
@@ -328,9 +425,12 @@ try:
             # White space
             self.SetViewWhiteSpace(False)   # Don't view white space
     
-            # EOL
-            #self.SetEOLMode(wx.stc.STC_EOL_CRLF)  # Just leave it at the default (autosense)
-            self.SetViewEOL(False)    
+            # 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)
     
@@ -364,8 +464,7 @@ try:
             # The rest remains unchanged.
 
             # Line numbers in margin
-            self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2')
-    
+            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
@@ -406,48 +505,620 @@ try:
             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 DemoCodeViewer(wx.TextCtrl):
-        def __init__(self, parent, ID):
-            wx.TextCtrl.__init__(self, parent, ID, style =
-                                 wx.TE_MULTILINE | wx.TE_READONLY |
-                                 wx.HSCROLL | wx.TE_RICH2 | wx.TE_NOHIDESEL)
+    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 exist")
+                    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"""
-    return apply(os.path.join, tuple(path.split('/')))
+    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 occurred 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()
+        
+
+#---------------------------------------------------------------------------
+
+class DemoTaskBarIcon(wx.TaskBarIcon):
+    TBMENU_RESTORE = wx.NewId()
+    TBMENU_CLOSE   = wx.NewId()
+    TBMENU_CHANGE  = wx.NewId()
+    TBMENU_REMOVE  = wx.NewId()
+    
+    def __init__(self, frame):
+        wx.TaskBarIcon.__init__(self)
+        self.frame = frame
+
+        # Set the image
+        icon = self.MakeIcon(images.getWXPdemoImage())
+        self.SetIcon(icon, "wxPython Demo")
+        self.imgidx = 1
+        
+        # bind some events
+        self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate)
+        self.Bind(wx.EVT_MENU, self.OnTaskBarActivate, id=self.TBMENU_RESTORE)
+        self.Bind(wx.EVT_MENU, self.OnTaskBarClose, id=self.TBMENU_CLOSE)
+        self.Bind(wx.EVT_MENU, self.OnTaskBarChange, id=self.TBMENU_CHANGE)
+        self.Bind(wx.EVT_MENU, self.OnTaskBarRemove, id=self.TBMENU_REMOVE)
+
+
+    def CreatePopupMenu(self):
+        """
+        This method is called by the base class when it needs to popup
+        the menu for the default EVT_RIGHT_DOWN event.  Just create
+        the menu how you want it and return it from this function,
+        the base class takes care of the rest.
+        """
+        menu = wx.Menu()
+        menu.Append(self.TBMENU_RESTORE, "Restore wxPython Demo")
+        menu.Append(self.TBMENU_CLOSE,   "Close wxPython Demo")
+        menu.AppendSeparator()
+        menu.Append(self.TBMENU_CHANGE, "Change the TB Icon")
+        menu.Append(self.TBMENU_REMOVE, "Remove the TB Icon")
+        return menu
+
+
+    def MakeIcon(self, img):
+        """
+        The various platforms have different requirements for the
+        icon size...
+        """
+        if "wxMSW" in wx.PlatformInfo:
+            img = img.Scale(16, 16)
+        elif "wxGTK" in wx.PlatformInfo:
+            img = img.Scale(22, 22)
+        # wxMac can be any size upto 128x128, so leave the source img alone....
+        icon = wx.IconFromBitmap(img.ConvertToBitmap() )
+        return icon
+    
+
+    def OnTaskBarActivate(self, evt):
+        if self.frame.IsIconized():
+            self.frame.Iconize(False)
+        if not self.frame.IsShown():
+            self.frame.Show(True)
+        self.frame.Raise()
+
+
+    def OnTaskBarClose(self, evt):
+        self.frame.Close()
+
+
+    def OnTaskBarChange(self, evt):
+        names = [ "WXPdemo", "Mondrian", "Pencil", "Carrot" ]                  
+        name = names[self.imgidx]
+        
+        getFunc = getattr(images, "get%sImage" % name)
+        self.imgidx += 1
+        if self.imgidx >= len(names):
+            self.imgidx = 0
+            
+        icon = self.MakeIcon(getFunc())
+        self.SetIcon(icon, "This is a new icon: " + name)
+
+
+    def OnTaskBarRemove(self, evt):
+        self.RemoveIcon()
+
+
+#---------------------------------------------------------------------------
 class wxPythonDemo(wx.Frame):
     overviewText = "wxPython Overview"
 
-    def __init__(self, parent, id, title):
-        wx.Frame.__init__(self, parent, -1, title, size = (800, 600),
-                          style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
+    def __init__(self, parent, title):
+        wx.Frame.__init__(self, parent, -1, title, size = (950, 720),
+                          style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
+
+        self.SetMinSize((640,480))
 
+        self.loaded = False
         self.cwd = os.getcwd()
         self.curOverview = ""
-        self.window = None
+        self.demoPage = None
+        self.codePage = None
+        self.shell = None
+        self.firstTime = True
+        self.finddlg = None
 
-        icon = images.getMondrianIcon()
+        icon = images.getWXPdemoIcon()
         self.SetIcon(icon)
 
-        if wx.Platform != '__WXMAC__':
-            # setup a taskbar icon, and catch some events from it
-            icon = wx.IconFromBitmap(
-                images.getMondrianImage().Scale(16,16).ConvertToBitmap() )            
-            self.tbicon = wx.TaskBarIcon()
-            self.tbicon.SetIcon(icon, "wxPython Demo")
-            self.tbicon.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate)
-            self.tbicon.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTaskBarMenu)
-            self.tbicon.Bind(wx.EVT_MENU, self.OnTaskBarActivate, id=self.TBMENU_RESTORE)
-            self.tbicon.Bind(wx.EVT_MENU, self.OnTaskBarClose, id=self.TBMENU_CLOSE)
+        self.tbicon = DemoTaskBarIcon(self)
 
         wx.CallAfter(self.ShowTip)
 
@@ -460,24 +1131,8 @@ class wxPythonDemo(wx.Frame):
         self.Centre(wx.BOTH)
         self.CreateStatusBar(1, wx.ST_SIZEGRIP)
 
-        splitter = wx.SplitterWindow(self, -1)
-        splitter2 = wx.SplitterWindow(splitter, -1) ##, size=(20,20))
-
-        # Set up a log on the View Log Notebook page
-        self.log = wx.TextCtrl(splitter2, -1,
-                              style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
-
-        # Set the wxWindows log target to be this textctrl
-        #wx.Log_SetActiveTarget(wx.LogTextCtrl(self.log))
-
-        # But instead of the above we want to show how to use our own wx.Log class
-        wx.Log_SetActiveTarget(MyLog(self.log))
-
-        # for serious debugging
-        #wx.Log_SetActiveTarget(wx.LogStderr())
-        #wx.Log_SetTraceMask(wx.TraceMessages)
-
-
+        splitter = wx.SplitterWindow(self, -1, style=wx.CLIP_CHILDREN | wx.SP_LIVE_UPDATE | wx.SP_3D)
+        splitter2 = wx.SplitterWindow(splitter, -1, style=wx.CLIP_CHILDREN | wx.SP_LIVE_UPDATE | wx.SP_3D)
 
         def EmptyHandler(evt): pass
         #splitter.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler)
@@ -486,12 +1141,20 @@ class wxPythonDemo(wx.Frame):
         # Prevent TreeCtrl from displaying all items after destruction when True
         self.dying = False
 
+        # Create a Notebook
+        self.nb = wx.Notebook(splitter2, -1, style=wx.CLIP_CHILDREN)
+
         # Make a File menu
         self.mainmenu = wx.MenuBar()
         menu = wx.Menu()
+        item = menu.Append(-1, '&Redirect Output',
+                           'Redirect print statements to a window',
+                           wx.ITEM_CHECK)
+        self.Bind(wx.EVT_MENU, self.OnToggleRedirect, item)
         item = menu.Append(-1, 'E&xit\tAlt-X', 'Get the heck outta here!')
         self.Bind(wx.EVT_MENU, self.OnFileExit, item)
-        wx.App_SetMacExitMenuItemId(item.GetId())
+        wx.App.SetMacExitMenuItemId(item.GetId())
         self.mainmenu.Append(menu, '&File')
 
         # Make a Demo menu
@@ -504,23 +1167,44 @@ class wxPythonDemo(wx.Frame):
             menu.AppendMenu(wx.NewId(), item[0], submenu)
         self.mainmenu.Append(menu, '&Demo')
 
+        # Make a Demo Code menu
+        #TODO: Add new menu items
+        #       Like the option-enabled entries to select the
+        #       active module
+        #TODO: should we bother?
+
+        #menu = wx.Menu()
+        #saveID = wx.NewId()
+        #restoreID = wx.NewId()
+       # 
+        #menu.Append(saveID, '&Save\tCtrl-S', 'Save edited demo')
+        #menu.Append(restoreID, '&Delete Modified\tCtrl-R', 'Delete modified copy')
+        #self.Bind(wx.EVT_MENU, self.codePage.OnSave, id=saveID)
+        #self.Bind(wx.EVT_MENU, self.codePage.OnRestore, id=restoreID)
+        #self.mainmenu.Append(menu, 'Demo &Code')
+       # 
 
         # Make a Help menu
-        helpID = wx.NewId()
-        findID = wx.NewId()
-        findnextID = wx.NewId()
         menu = wx.Menu()
         findItem = menu.Append(-1, '&Find\tCtrl-F', 'Find in the Demo Code')
         findnextItem = menu.Append(-1, 'Find &Next\tF3', 'Find Next')
         menu.AppendSeparator()
+
+        shellItem = menu.Append(-1, 'Open Py&Shell Window\tF5',
+                                'An interactive interpreter window with the demo app and frame objects in the namesapce')
+        menu.AppendSeparator()
         helpItem = menu.Append(-1, '&About\tCtrl-H', 'wxPython RULES!!!')
-        wx.App_SetMacAboutMenuItemId(helpItem.GetId())
+        wx.App.SetMacAboutMenuItemId(helpItem.GetId())
+
+        self.Bind(wx.EVT_MENU, self.OnOpenShellWindow, shellItem)
         self.Bind(wx.EVT_MENU, self.OnHelpAbout, helpItem)
         self.Bind(wx.EVT_MENU, self.OnHelpFind,  findItem)
         self.Bind(wx.EVT_MENU, self.OnFindNext,  findnextItem)
-        self.Bind(wx.EVT_COMMAND_FIND, self.OnFind)
-        self.Bind(wx.EVT_COMMAND_FIND_NEXT, self.OnFind)
-        self.Bind(wx.EVT_COMMAND_FIND_CLOSE, self.OnFindClose)
+        self.Bind(wx.EVT_FIND, self.OnFind)
+        self.Bind(wx.EVT_FIND_NEXT, self.OnFind)
+        self.Bind(wx.EVT_FIND_CLOSE, self.OnFindClose)
+        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateFindItems, findItem)
+        self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateFindItems, findnextItem)
         self.mainmenu.Append(menu, '&Help')
         self.SetMenuBar(self.mainmenu)
 
@@ -560,9 +1244,6 @@ class wxPythonDemo(wx.Frame):
         self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, id=tID)
         self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnTreeLeftDown)
 
-        # Create a Notebook
-        self.nb = wx.Notebook(splitter2, -1, style=wx.CLIP_CHILDREN)
-
         # Set up a wx.html.HtmlWindow on the Overview Notebook page
         # we put it in a panel first because there seems to be a
         # refresh bug of some sort (wxGTK) when it is directly in
@@ -578,62 +1259,73 @@ class wxPythonDemo(wx.Frame):
 
             def OnOvrSize(evt, ovr=self.ovr):
                 ovr.SetSize(evt.GetSize())
-
             panel.Bind(wx.EVT_SIZE, OnOvrSize)
             panel.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler)
 
+        if "gtk2" in wx.PlatformInfo:
+            self.ovr.SetStandardFonts()
+        self.SetOverview(self.overviewText, mainOverview)
 
-        self.SetOverview(self.overviewText, overview)
 
+        # Set up a log window
+        self.log = wx.TextCtrl(splitter2, -1,
+                              style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
 
-        # Set up a notebook page for viewing the source code of each sample
-        self.txt = DemoCodeViewer(self.nb, -1)
-        self.nb.AddPage(self.txt, "Demo Code")
-        self.LoadDemoSource('Main.py')
+        # Set the wxWindows log target to be this textctrl
+        #wx.Log_SetActiveTarget(wx.LogTextCtrl(self.log))
 
+        # But instead of the above we want to show how to use our own wx.Log class
+        wx.Log_SetActiveTarget(MyLog(self.log))
+        
+        # for serious debugging
+        #wx.Log_SetActiveTarget(wx.LogStderr())
+        #wx.Log_SetTraceMask(wx.TraceMessages)
 
-        # add the windows to the splitter and split it.
-        splitter2.SplitHorizontally(self.nb, self.log, -120)
-        splitter.SplitVertically(self.tree, splitter2, 180)
 
-        splitter.SetMinimumPaneSize(20)
-        splitter2.SetMinimumPaneSize(20)
+        self.Bind(wx.EVT_ACTIVATE, self.OnActivate)
+        wx.GetApp().Bind(wx.EVT_ACTIVATE_APP, self.OnAppActivate)
+
+        # add the windows to the splitter and split it.
+        splitter2.SplitHorizontally(self.nb, self.log, -160)
+        splitter.SplitVertically(self.tree, splitter2, 200)
 
+        splitter.SetMinimumPaneSize(120)
+        splitter2.SetMinimumPaneSize(60)
 
         # Make the splitter on the right expand the top window when resized
         def SplitterOnSize(evt):
             splitter = evt.GetEventObject()
             sz = splitter.GetSize()
-            splitter.SetSashPosition(sz.height - 120, False)
+            splitter.SetSashPosition(sz.height - 160, False)
             evt.Skip()
 
         splitter2.Bind(wx.EVT_SIZE, SplitterOnSize)
 
-
         # select initial items
         self.nb.SetSelection(0)
         self.tree.SelectItem(root)
 
-        if len(sys.argv) == 2:
-            try:
-                selectedDemo = self.treeMap[sys.argv[1]]
-            except:
-                selectedDemo = None
+        # Load 'Main' module
+        self.LoadDemo(self.overviewText)
+        self.loaded = True
+
+        # select some other initial module?
+        if len(sys.argv) > 1:
+            arg = sys.argv[1]
+            if arg.endswith('.py'):
+                arg = arg[:-3]
+            selectedDemo = self.treeMap.get(arg, None)
             if selectedDemo:
                 self.tree.SelectItem(selectedDemo)
                 self.tree.EnsureVisible(selectedDemo)
 
 
-        wx.LogMessage('window handle: %s' % self.GetHandle())
-
-
     #---------------------------------------------
     def WriteText(self, text):
         if text[-1:] == '\n':
             text = text[:-1]
         wx.LogMessage(text)
 
-
     def write(self, txt):
         self.WriteText(txt)
 
@@ -651,6 +1343,7 @@ class wxPythonDemo(wx.Frame):
 
     #---------------------------------------------
     def OnTreeLeftDown(self, event):
+        # reset the overview text if the tree item is clicked on again
         pt = event.GetPosition();
         item, flags = self.tree.HitTest(pt)
         if item == self.tree.GetSelection():
@@ -659,84 +1352,151 @@ class wxPythonDemo(wx.Frame):
 
     #---------------------------------------------
     def OnSelChanged(self, event):
-        if self.dying:
+        if self.dying or  not self.loaded:
             return
 
         item = event.GetItem()
         itemText = self.tree.GetItemText(item)
-        self.RunDemo(itemText)
-
+        self.LoadDemo(itemText)
 
     #---------------------------------------------
-    def RunDemo(self, itemText):
-        os.chdir(self.cwd)
-        if self.nb.GetPageCount() == 3:
-            if self.nb.GetSelection() == 2:
-                self.nb.SetSelection(0)
-            # inform the window that it's time to quit if it cares
-            if self.window is not None:
-                if hasattr(self.window, "ShutdownDemo"):
-                    self.window.ShutdownDemo()
-            wx.SafeYield() # in case the page has pending events
-            self.nb.DeletePage(2)
-
-        if itemText == self.overviewText:
-            self.LoadDemoSource('Main.py')
-            self.SetOverview(self.overviewText, overview)
-            self.nb.Refresh();
-            self.window = None
+    def LoadDemo(self, demoName):
+        try:
+            wx.BeginBusyCursor()
+            
+            os.chdir(self.cwd)
+            self.ShutdownDemoModule()
+
+            if demoName == self.overviewText:
+                # User selected the "wxPython Overview" node
+                # ie: _this_ module
+                # Changing the main window at runtime not yet supported...
+                self.demoModules = DemoModules(__name__)
+                self.SetOverview(self.overviewText, mainOverview)
+                self.LoadDemoSource()
+                self.UpdateNotebook(0)
+            else:
+                if os.path.exists(GetOriginalFilename(demoName)):
+                    wx.LogMessage("Loading demo %s.py..." % demoName)
+                    self.demoModules = DemoModules(demoName)
+                    self.LoadDemoSource()
+                    self.tree.Refresh()
+                else:
+                    self.SetOverview("wxPython", mainOverview)
+                    self.codePage = None
+                    self.UpdateNotebook(0)
+        finally:
+            wx.EndBusyCursor()
 
-        else:
-            if os.path.exists(itemText + '.py'):
-                wx.BeginBusyCursor()
-                wx.LogMessage("Running demo %s.py..." % itemText)
-                try:
-                    self.LoadDemoSource(itemText + '.py')
-
-                    if (sys.modules.has_key(itemText)):
-                       reload(sys.modules[itemText])
-                    
-                    module = __import__(itemText, globals())
-                    self.SetOverview(itemText + " Overview", module.overview)
-                finally:
-                    wx.EndBusyCursor()
-                self.tree.Refresh()
-
-                # in case runTest is modal, make sure things look right...
-                self.nb.Refresh();
-                wx.SafeYield()
-
-                self.window = module.runTest(self, self.nb, self) ###
-                if self.window is not None:
-                    self.nb.AddPage(self.window, 'Demo')
-                    self.nb.SetSelection(2)
-                    self.nb.Refresh()  # without this wxMac has troubles showing the just added page
+    #---------------------------------------------
+    def LoadDemoSource(self):
+        self.codePage = None
+        self.codePage = DemoCodePanel(self.nb, self)
+        self.codePage.LoadDemo(self.demoModules)
+        
+    #---------------------------------------------
+    def RunModule(self):
+        """Runs the active module"""
+
+        module = self.demoModules.GetActive()
+        self.ShutdownDemoModule()
+        overviewText = ""
+        
+        # o The RunTest() for all samples must now return a window that can
+        #   be palced in a tab in the main notebook.
+        # o If an error occurs (or has occurred before) an error tab is created.
+        
+        if module is not None:
+            wx.LogMessage("Running demo module...")
+            if hasattr(module, "overview"):
+                overviewText = module.overview
 
-            else:
-                self.ovr.SetPage("")
-                self.txt.Clear()
-                self.window = None
+            try:
+                self.demoPage = module.runTest(self, self.nb, self)
+            except:
+                self.demoPage = DemoErrorPanel(self.nb, self.codePage,
+                                               DemoError(sys.exc_info()), self)
 
+            assert self.demoPage is not None, "runTest must return a window!"
+            
+        else:
+            # There was a previous error in compiling or exec-ing
+            self.demoPage = DemoErrorPanel(self.nb, self.codePage,
+                                           self.demoModules.GetErrorInfo(), self)
+            
+        self.SetOverview(self.demoModules.name + " Overview", overviewText)
 
+        if self.firstTime:
+            # cahnge to the demo page the first time a module is run
+            self.UpdateNotebook(2)
+            self.firstTime = False
+        else:
+            # otherwise just stay on the same tab in case the user has changed to another one
+            self.UpdateNotebook()
 
     #---------------------------------------------
-    # Get the Demo files
-    def LoadDemoSource(self, filename):
-        self.txt.Clear()
-        try:
-            self.txt.SetValue(open(filename).read())
-        except IOError:
-            self.txt.SetValue("Cannot open %s file." % filename)
+    def ShutdownDemoModule(self):
+        if self.demoPage:
+            # inform the window that it's time to quit if it cares
+            if hasattr(self.demoPage, "ShutdownDemo"):
+                self.demoPage.ShutdownDemo()
+            wx.YieldIfNeeded() # in case the page has pending events
+            self.demoPage = None
+            
+    #---------------------------------------------
+    def UpdateNotebook(self, select = -1):
+        nb = self.nb
+        debug = False
+        
+        def UpdatePage(page, pageText):
+            pageExists = False
+            pagePos = -1
+            for i in range(nb.GetPageCount()):
+                if nb.GetPageText(i) == pageText:
+                    pageExists = True
+                    pagePos = i
+                    break
+                
+            if page:
+                if not pageExists:
+                    # Add a new page
+                    nb.AddPage(page, pageText)
+                    if debug: wx.LogMessage("DBG: ADDED %s" % pageText)
+                else:
+                    if nb.GetPage(pagePos) != page:
+                        # Reload an existing page
+                        nb.Freeze()
+                        nb.DeletePage(pagePos)
+                        nb.InsertPage(pagePos, page, pageText)
+                        nb.Thaw()
+                        if debug: wx.LogMessage("DBG: RELOADED %s" % pageText)
+                    else:
+                        # Excellent! No redraw/flicker
+                        if debug: wx.LogMessage("DBG: SAVED from reloading %s" % pageText)
+            elif pageExists:
+                # Delete a page
+                nb.DeletePage(pagePos)
+                if debug: wx.LogMessage("DBG: DELETED %s" % pageText)
+            else:
+                if debug: wx.LogMessage("DBG: STILL GONE - %s" % pageText)
+                
+        if select == -1:
+            select = nb.GetSelection()
 
-        self.txt.SetInsertionPoint(0)
-        self.txt.ShowPosition(0)
+        UpdatePage(self.codePage, "Demo Code")
+        UpdatePage(self.demoPage, "Demo")
 
+        if select >= 0 and select < nb.GetPageCount():
+            nb.SetSelection(select)
+            
     #---------------------------------------------
     def SetOverview(self, name, text):
         self.curOverview = text
         lead = text[:6]
         if lead != '<html>' and lead != '<HTML>':
             text = '<br>'.join(text.split('\n'))
+        if wx.USE_UNICODE:
+            text = text.decode('iso8859_1')  
         self.ovr.SetPage(text)
         self.nb.SetPageText(0, name)
 
@@ -745,6 +1505,15 @@ class wxPythonDemo(wx.Frame):
     def OnFileExit(self, *event):
         self.Close()
 
+    def OnToggleRedirect(self, event):
+        app = wx.GetApp()
+        if event.Checked():
+            app.RedirectStdio()
+            print "Print statements and other standard output will now be directed to this window."
+        else:
+            app.RestoreStdio()
+            print "Print statements and other standard output will now be sent to the usual location."
     def OnHelpAbout(self, event):
         from About import MyAboutBox
         about = MyAboutBox(self)
@@ -752,6 +1521,9 @@ class wxPythonDemo(wx.Frame):
         about.Destroy()
 
     def OnHelpFind(self, event):
+        if self.finddlg != None:
+            return
+        
         self.nb.SetSelection(1)
         self.finddlg = wx.FindReplaceDialog(self, self.finddata, "Find",
                         wx.FR_NOUPDOWN |
@@ -759,11 +1531,17 @@ class wxPythonDemo(wx.Frame):
                         wx.FR_NOWHOLEWORD)
         self.finddlg.Show(True)
 
+
+    def OnUpdateFindItems(self, evt):
+        evt.Enable(self.finddlg == None)
+
+
     def OnFind(self, event):
+        editor = self.codePage.editor
         self.nb.SetSelection(1)
-        end = self.txt.GetLastPosition()
-        textstring = self.txt.GetRange(0, end).lower()
-        start = self.txt.GetSelection()[1]
+        end = editor.GetLastPosition()
+        textstring = editor.GetRange(0, end).lower()
+        start = editor.GetSelection()[1]
         findstring = self.finddata.GetFindString().lower()
         loc = textstring.find(findstring, start)
         if loc == -1 and start != 0:
@@ -782,8 +1560,9 @@ class wxPythonDemo(wx.Frame):
                 return
             else:
                 self.finddlg.Destroy()
-        self.txt.ShowPosition(loc)
-        self.txt.SetSelection(loc, loc + len(findstring))
+                self.finddlg = None
+        editor.ShowPosition(loc)
+        editor.SetSelection(loc, loc + len(findstring))
 
 
 
@@ -795,15 +1574,43 @@ class wxPythonDemo(wx.Frame):
 
     def OnFindClose(self, event):
         event.GetDialog().Destroy()
+        self.finddlg = None
 
 
+    def OnOpenShellWindow(self, evt):
+        if self.shell:
+            # if it already exists then just make sure it's visible
+            s = self.shell
+            if s.IsIconized():
+                s.Iconize(False)
+            s.Raise()
+        else:
+            # Make a PyShell window
+            from wx import py
+            namespace = { 'wx'    : wx,
+                          'app'   : wx.GetApp(),
+                          'frame' : self,
+                          }
+            self.shell = py.shell.ShellFrame(None, locals=namespace)
+            self.shell.SetSize((640,480))
+            self.shell.Show()
+
+            # Hook the close event of the main frame window so that we
+            # close the shell at the same time if it still exists            
+            def CloseShell(evt):
+                if self.shell:
+                    self.shell.Close()
+                evt.Skip()
+            self.Bind(wx.EVT_CLOSE, CloseShell)
+
+        
     #---------------------------------------------
     def OnCloseWindow(self, event):
         self.dying = True
-        self.window = None
+        self.demoPage = None
+        self.codePage = None
         self.mainmenu = None
-        if hasattr(self, "tbicon"):
-            del self.tbicon
+        self.tbicon.Destroy()
         self.Destroy()
 
 
@@ -811,7 +1618,7 @@ class wxPythonDemo(wx.Frame):
     def OnIdle(self, event):
         if self.otherWin:
             self.otherWin.Raise()
-            self.window = self.otherWin
+            self.demoPage = self.otherWin
             self.otherWin = None
 
 
@@ -841,38 +1648,10 @@ class wxPythonDemo(wx.Frame):
             self.tree.EnsureVisible(selectedDemo)
 
 
-    #---------------------------------------------
-    def OnTaskBarActivate(self, evt):
-        if self.IsIconized():
-            self.Iconize(False)
-        if not self.IsShown():
-            self.Show(True)
-        self.Raise()
-
-    #---------------------------------------------
-
-    TBMENU_RESTORE = 1000
-    TBMENU_CLOSE   = 1001
-
-    def OnTaskBarMenu(self, evt):
-        menu = wx.Menu()
-        menu.Append(self.TBMENU_RESTORE, "Restore wxPython Demo")
-        menu.Append(self.TBMENU_CLOSE,   "Close")
-        self.tbicon.PopupMenu(menu)
-        menu.Destroy()
-
-    #---------------------------------------------
-    def OnTaskBarClose(self, evt):
-        self.Close()
-
-        # because of the way wx.TaskBarIcon.PopupMenu is implemented we have to
-        # prod the main idle handler a bit to get the window to actually close
-        wx.GetApp().ProcessIdle()
-
 
     #---------------------------------------------
     def OnIconfiy(self, evt):
-        wx.LogMessage("OnIconfiy")
+        wx.LogMessage("OnIconfiy: %s" % evt.Iconized())
         evt.Skip()
 
     #---------------------------------------------
@@ -880,27 +1659,49 @@ class wxPythonDemo(wx.Frame):
         wx.LogMessage("OnMaximize")
         evt.Skip()
 
+    #---------------------------------------------
+    def OnActivate(self, evt):
+        wx.LogMessage("OnActivate: %s" % evt.GetActive())
+        evt.Skip()
 
-
+    #---------------------------------------------
+    def OnAppActivate(self, evt):
+        wx.LogMessage("OnAppActivate: %s" % evt.GetActive())
+        evt.Skip()
 
 #---------------------------------------------------------------------------
 #---------------------------------------------------------------------------
 
 class MySplashScreen(wx.SplashScreen):
     def __init__(self):
-        bmp = wx.Image(opj("bitmaps/splash.gif")).ConvertToBitmap()
+        bmp = wx.Image(opj("bitmaps/splash.png")).ConvertToBitmap()
         wx.SplashScreen.__init__(self, bmp,
                                  wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT,
-                                 3000, None, -1)
+                                 5000, None, -1)
         self.Bind(wx.EVT_CLOSE, self.OnClose)
+        self.fc = wx.FutureCall(2000, self.ShowMain)
+
 
     def OnClose(self, evt):
+        # Make sure the default handler runs too so this window gets
+        # destroyed
+        evt.Skip()
         self.Hide()
-        frame = wxPythonDemo(None, -1, "wxPython: (A Demonstration)")
-        frame.Show()
-        evt.Skip()  # Make sure the default handler runs too...
+        
+        # if the timer is still running then go ahead and show the
+        # main frame now
+        if self.fc.IsRunning():
+            self.fc.Stop()
+            self.ShowMain()
 
 
+    def ShowMain(self):
+        frame = wxPythonDemo(None, "wxPython: (A Demonstration)")
+        frame.Show()
+        if self.fc.IsRunning():
+            self.Raise()
+        
+
 class MyApp(wx.App):
     def OnInit(self):
         """
@@ -908,14 +1709,17 @@ class MyApp(wx.App):
         the main frame when it is time to do so.
         """
 
-        wx.InitAllImageHandlers()
+        wx.SystemOptions.SetOptionInt("mac.window-plain-transition", 1)
+
+        # For debugging
+        #self.SetAssertMode(wx.PYAPP_ASSERT_DIALOG)
 
         # Normally when using a SplashScreen you would create it, show
         # it and then continue on with the applicaiton's
         # initialization, finally creating and showing the main
         # application window(s).  In this case we have nothing else to
         # do so we'll delay showing the main frame until later (see
-        # OnClose above) so the users can see the SplashScrren effect.        
+        # ShowMain above) so the users can see the SplashScreen effect.        
         splash = MySplashScreen()
         splash.Show()
 
@@ -931,24 +1735,21 @@ def main():
         os.chdir(demoPath)
     except:
         pass
-    app = MyApp(wx.Platform == "__WXMAC__")
+    app = MyApp(False)
     app.MainLoop()
 
-
 #---------------------------------------------------------------------------
 
 
-
-overview = """<html><body>
+mainOverview = """<html><body>
 <h2>wxPython</h2>
 
-<p> wxPython is a <b>GUI toolkit</b> for the <a
-href="http://www.python.org/">Python</a> programming language.  It
-allows Python programmers to create programs with a robust, highly
-functional graphical user interface, simply and easily.  It is
-implemented as a Python extension module (native code) that wraps the
-popular <a href="http://wxwindows.org/front.htm">wxWindows</a> cross
-platform GUI library, which is written in C++.
+<p> wxPython is a <b>GUI toolkit</b> for the Python programming
+language.  It allows Python programmers to create programs with a
+robust, highly functional graphical user interface, simply and easily.
+It is implemented as a Python extension module (native code) that
+wraps the popular wxWindows cross platform GUI library, which is
+written in C++.
 
 <p> Like Python and wxWindows, wxPython is <b>Open Source</b> which
 means that it is free for anyone to use and the source code is
@@ -976,6 +1777,7 @@ is loaded in another tab for you to browse and learn from.
 #----------------------------------------------------------------------------
 
 if __name__ == '__main__':
+    __name__ = 'Main'
     main()
 
 #----------------------------------------------------------------------------