+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()
+