#!/bin/env python #---------------------------------------------------------------------------- # Name: Main.py # Purpose: Testing lots of stuff, controls, window types, etc. # # Author: Robin Dunn # # Created: A long time ago, in a galaxy far, far away... # RCS-ID: $Id$ # Copyright: (c) 1999 by Total Control Software # Licence: wxWindows license #---------------------------------------------------------------------------- # 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 import images # For debugging ##wx.Trap(); ##print "wx.VERSION_STRING = ", wx.VERSION_STRING ##print os.getpid(); ##raw_input("Press Enter...") #--------------------------------------------------------------------------- _treeList = [ # new stuff ('Recent Additions/Updates', [ 'StockButtons', 'Ticker', ]), # 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', 'ComboBox', 'Gauge', 'Grid', 'Grid_MegaExample', 'ListBox', 'ListCtrl', 'ListCtrl_virtual', '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', '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): 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 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(line): return self.XYToPosition(0,line) def GotoLine(self, line): pos = self.editor.GetPositionFromLine(line) self.editor.SetInsertionPoint(pos) self.editor.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, "" , "" , "" , None], [None, "" , "" , "" , 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 = "" line = line.strip() self.traceback.append( (filename, lineno, "", line) ) excValue = msg try: self.exception_details = str(excValue) except: self.exception_details = "" & 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] == "" or data[0] == "": # 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 wxPythonDemo(wx.Frame): overviewText = "wxPython Overview" 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.loaded = False self.cwd = os.getcwd() self.curOverview = "" self.demoPage = None self.codePage = None self.shell = None self.firstTime = True icon = images.getMondrianIcon() self.SetIcon(icon) if wx.Platform != '__WXMAC__': # setup a taskbar icon, and catch some events from it dim = 16 # (may want to use 22 on wxGTK, but 16 looks okay too) icon = wx.IconFromBitmap( images.getMondrianImage().Scale(dim,dim).ConvertToBitmap() ) #icon = wx.Icon('bmp_source/mondrian.ico', wx.BITMAP_TYPE_ICO) #icon = images.getMondrianIcon() 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) wx.CallAfter(self.ShowTip) self.otherWin = None self.Bind(wx.EVT_IDLE, self.OnIdle) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) self.Bind(wx.EVT_ICONIZE, self.OnIconfiy) self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize) self.Centre(wx.BOTH) self.CreateStatusBar(1, wx.ST_SIZEGRIP) 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) #splitter2.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler) # 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()) self.mainmenu.Append(menu, '&File') # Make a Demo menu menu = wx.Menu() for item in _treeList: submenu = wx.Menu() for childItem in item[1]: mi = submenu.Append(-1, childItem) self.Bind(wx.EVT_MENU, self.OnDemoMenu, mi) 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 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()) 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.mainmenu.Append(menu, '&Help') self.SetMenuBar(self.mainmenu) self.finddata = wx.FindReplaceData() if 0: # This is another way to set Accelerators, in addition to # using the '\t' syntax in the menu items. aTable = wx.AcceleratorTable([(wx.ACCEL_ALT, ord('X'), exitID), (wx.ACCEL_CTRL, ord('H'), helpID), (wx.ACCEL_CTRL, ord('F'), findID), (wx.ACCEL_NORMAL, WXK_F3, findnextID) ]) self.SetAcceleratorTable(aTable) # Create a TreeCtrl tID = wx.NewId() self.treeMap = {} self.tree = wx.TreeCtrl(splitter, tID, style = wx.TR_DEFAULT_STYLE #| wx.TR_HAS_VARIABLE_ROW_HEIGHT ) root = self.tree.AddRoot("wxPython Overview") firstChild = None for item in _treeList: child = self.tree.AppendItem(root, item[0]) if not firstChild: firstChild = child for childItem in item[1]: theDemo = self.tree.AppendItem(child, childItem) self.treeMap[childItem] = theDemo self.tree.Expand(root) self.tree.Expand(firstChild) self.tree.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, id=tID) self.tree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, id=tID) self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, id=tID) self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnTreeLeftDown) # 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 # the notebook... if 0: # the old way self.ovr = wx.html.HtmlWindow(self.nb, -1, size=(400, 400)) self.nb.AddPage(self.ovr, self.overviewText) else: # hopefully I can remove this hacky code soon, see SF bug #216861 panel = wx.Panel(self.nb, -1, style=wx.CLIP_CHILDREN) self.ovr = wx.html.HtmlWindow(panel, -1, size=(400, 400)) self.nb.AddPage(panel, self.overviewText) 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.NormalizeFontSizes() self.SetOverview(self.overviewText, mainOverview) # Set up a log window 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) # add the windows to the splitter and split it. splitter2.SplitHorizontally(self.nb, self.log, -160) splitter.SplitVertically(self.tree, splitter2, 200) splitter.SetMinimumPaneSize(20) splitter2.SetMinimumPaneSize(20) # 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 - 160, False) evt.Skip() splitter2.Bind(wx.EVT_SIZE, SplitterOnSize) # select initial items self.nb.SetSelection(0) self.tree.SelectItem(root) # 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) #--------------------------------------------- def WriteText(self, text): if text[-1:] == '\n': text = text[:-1] wx.LogMessage(text) def write(self, txt): self.WriteText(txt) #--------------------------------------------- def OnItemExpanded(self, event): item = event.GetItem() wx.LogMessage("OnItemExpanded: %s" % self.tree.GetItemText(item)) event.Skip() #--------------------------------------------- def OnItemCollapsed(self, event): item = event.GetItem() wx.LogMessage("OnItemCollapsed: %s" % self.tree.GetItemText(item)) event.Skip() #--------------------------------------------- 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(): self.SetOverview(self.tree.GetItemText(item)+" Overview", self.curOverview) event.Skip() #--------------------------------------------- def OnSelChanged(self, event): if self.dying or not self.loaded: return item = event.GetItem() itemText = self.tree.GetItemText(item) self.LoadDemo(itemText) #--------------------------------------------- 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() #--------------------------------------------- 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 If the demo returns a window it is placed in a tab. # o Otherwise, a placeholder tab is created, informing the user that the # demo runs outside the main window, and allowing it to be reloaded. # o If an error occurs (or has occured before) an error tab is created. if module is not None: wx.LogMessage("Running demo module...") if hasattr(module, "overview"): overviewText = module.overview 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() #--------------------------------------------- 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() 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 != '' and lead != '': text = '
'.join(text.split('\n')) if wx.USE_UNICODE: text = text.decode('iso8859_1') self.ovr.SetPage(text) self.nb.SetPageText(0, name) #--------------------------------------------- # Menu methods 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) about.ShowModal() about.Destroy() def OnHelpFind(self, event): self.nb.SetSelection(1) self.finddlg = wx.FindReplaceDialog(self, self.finddata, "Find", wx.FR_NOUPDOWN | wx.FR_NOMATCHCASE | wx.FR_NOWHOLEWORD) self.finddlg.Show(True) def OnFind(self, event): editor = self.codePage.editor self.nb.SetSelection(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: # string not found, start at beginning start = 0 loc = textstring.find(findstring, start) if loc == -1: dlg = wx.MessageDialog(self, 'Find String Not Found', 'Find String Not Found in Demo File', wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() dlg.Destroy() if self.finddlg: if loc == -1: self.finddlg.SetFocus() return else: self.finddlg.Destroy() editor.ShowPosition(loc) editor.SetSelection(loc, loc + len(findstring)) def OnFindNext(self, event): if self.finddata.GetFindString(): self.OnFind(event) else: self.OnHelpFind(event) def OnFindClose(self, event): event.GetDialog().Destroy() 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.demoPage = None self.codePage = None self.mainmenu = None self.Destroy() #--------------------------------------------- def OnIdle(self, event): if self.otherWin: self.otherWin.Raise() self.demoPage = self.otherWin self.otherWin = None #--------------------------------------------- def ShowTip(self): try: showTipText = open(opj("data/showTips")).read() showTip, index = eval(showTipText) except IOError: showTip, index = (1, 0) if showTip: tp = wx.CreateFileTipProvider(opj("data/tips.txt"), index) ##tp = MyTP(0) showTip = wx.ShowTip(self, tp) index = tp.GetCurrentTip() open(opj("data/showTips"), "w").write(str( (showTip, index) )) #--------------------------------------------- def OnDemoMenu(self, event): try: selectedDemo = self.treeMap[self.mainmenu.GetLabel(event.GetId())] except: selectedDemo = None if selectedDemo: self.tree.SelectItem(selectedDemo) 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: %d" % evt.Iconized()) evt.Skip() #--------------------------------------------- def OnMaximize(self, evt): wx.LogMessage("OnMaximize") evt.Skip() #--------------------------------------------------------------------------- #--------------------------------------------------------------------------- class MySplashScreen(wx.SplashScreen): def __init__(self): bmp = wx.Image(opj("bitmaps/splash.gif")).ConvertToBitmap() wx.SplashScreen.__init__(self, bmp, wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT, 3000, None, -1) self.Bind(wx.EVT_CLOSE, self.OnClose) def OnClose(self, evt): self.Hide() frame = wxPythonDemo(None, "wxPython: (A Demonstration)") frame.Show() evt.Skip() # Make sure the default handler runs too... class MyApp(wx.App): def OnInit(self): """ Create and show the splash screen. It will then create and show the main frame when it is time to do so. """ # 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 SplashScreen effect. splash = MySplashScreen() splash.Show() return True #--------------------------------------------------------------------------- def main(): try: demoPath = os.path.dirname(__file__) os.chdir(demoPath) except: pass app = MyApp(False) app.MainLoop() #--------------------------------------------------------------------------- mainOverview = """

wxPython

wxPython is a GUI toolkit 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++.

Like Python and wxWindows, wxPython is Open Source which means that it is free for anyone to use and the source code is available for anyone to look at and modify. Or anyone can contribute fixes or enhancements to the project.

wxPython is a cross-platform toolkit. This means that the same program will run on multiple platforms without modification. Currently supported platforms are 32-bit Microsoft Windows, most Unix or unix-like systems, and Macintosh OS X. Since the language is Python, wxPython programs are simple, easy to write and easy to understand.

This demo is not only a collection of test cases for wxPython, but is also designed to help you learn about and how to use wxPython. Each sample is listed in the tree control on the left. When a sample is selected in the tree then a module is loaded and run (usually in a tab of this notebook,) and the source code of the module is loaded in another tab for you to browse and learn from. """ #---------------------------------------------------------------------------- #---------------------------------------------------------------------------- if __name__ == '__main__': __name__ = 'Main' main() #----------------------------------------------------------------------------