+ 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"""
+ 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]
+ description = self.modules[modID][2]
+
+ 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, str(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)