X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/a253aa20446ade206fa399be2acde4149411f678..c1dda21b64f300d89cbc084802082d37c694e971:/wxPython/demo/Main.py diff --git a/wxPython/demo/Main.py b/wxPython/demo/Main.py index 438245d456..c5ec3fa129 100644 --- a/wxPython/demo/Main.py +++ b/wxPython/demo/Main.py @@ -11,7 +11,23 @@ # 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 = %s (%s)" % (wx.VERSION_STRING, wx.USE_UNICODE and 'unicode' or 'ansi') +##print "pid:", os.getpid() +##raw_input("Press Enter...") #--------------------------------------------------------------------------- @@ -28,155 +46,202 @@ import images _treeList = [ # new stuff - ('Recent Additions', [ - 'wxVListBox', - 'wxListbook', - 'wxMaskedNumCtrl', - 'FloatCanvas', - 'wxXmlResourceSubclass', - 'wxGridBagSizer', - 'Cursor', - 'PyPlot', + ('Recent Additions/Updates', [ + 'RichTextCtrl', + 'Treebook', + 'Toolbook', + 'BitmapFromBuffer', + 'RawBitmapAccess', + 'DragScroller', + 'DelayedResult', + 'ExpandoTextCtrl', + 'ButtonPanel', + 'FlatNotebook', + 'CustomTreeCtrl', + 'AboutBox', + 'AlphaDrawing', + 'GraphicsContext', + 'CollapsiblePane', + 'ComboCtrl', + 'OwnerDrawnComboBox', + 'BitmapComboBox', + 'I18N', + 'Img2PyArtProvider', + 'SearchCtrl', + 'SizedControls', + 'AUI_MDI', ]), # managed windows == things with a (optional) caption you can close - ('Base Frames and Dialogs', [ - 'wxDialog', - 'wxFrame', - 'wxMDIWindows', - 'wxMiniFrame', - 'wxWizard', + ('Frames and Dialogs', [ + 'AUI_DockingWindowMgr', + 'AUI_MDI', + 'Dialog', + 'Frame', + 'MDIWindows', + 'MiniFrame', + 'Wizard', ]), # the common dialogs ('Common Dialogs', [ - 'wxColourDialog', - 'wxDirDialog', - 'wxFileDialog', - 'wxFileDialog_Save', - 'wxFindReplaceDialog', - 'wxFontDialog', - 'wxMessageDialog', - 'wxPageSetupDialog', - 'wxPrintDialog', - 'wxProgressDialog', - 'wxSingleChoiceDialog', - 'wxTextEntryDialog', + 'AboutBox', + 'ColourDialog', + 'DirDialog', + 'FileDialog', + 'FindReplaceDialog', + 'FontDialog', + 'MessageDialog', + 'MultiChoiceDialog', + 'PageSetupDialog', + 'PrintDialog', + 'ProgressDialog', + 'SingleChoiceDialog', + 'TextEntryDialog', ]), # dialogs from libraries ('More Dialogs', [ - 'ErrorDialogs', 'ImageBrowser', - 'wxMultipleChoiceDialog', - 'wxScrolledMessageDialog', + 'ScrolledMessageDialog', ]), # core controls ('Core Windows/Controls', [ + 'BitmapButton', + 'Button', + 'CheckBox', + 'CheckListBox', + 'Choice', + 'ComboBox', + 'Gauge', + 'Grid', + 'Grid_MegaExample', + 'ListBox', + 'ListCtrl', + 'ListCtrl_virtual', + 'ListCtrl_edit', + 'Menu', 'PopupMenu', - 'wxButton', - 'wxCheckBox', - 'wxCheckListBox', - 'wxChoice', - 'wxComboBox', - 'wxGauge', - 'wxGrid', - 'wxGrid_MegaExample', - 'wxListbook', - 'wxListBox', - 'wxListCtrl', - 'wxListCtrl_virtual', - 'wxMenu', - 'wxNotebook', - 'wxPopupWindow', - 'wxRadioBox', - 'wxRadioButton', - 'wxSashWindow', - 'wxScrolledWindow', - 'wxSlider', - 'wxSpinButton', - 'wxSpinCtrl', - 'wxSplitterWindow', - 'wxStaticBitmap', - 'wxStaticText', - 'wxStatusBar', - 'wxTextCtrl', - 'wxToggleButton', - 'wxToolBar', - 'wxTreeCtrl', - 'wxValidator', + 'PopupWindow', + 'RadioBox', + 'RadioButton', + 'SashWindow', + 'ScrolledWindow', + 'SearchCtrl', + 'Slider', + 'SpinButton', + 'SpinCtrl', + 'SplitterWindow', + 'StaticBitmap', + 'StaticBox', + 'StaticText', + 'StatusBar', + 'StockButtons', + 'TextCtrl', + 'ToggleButton', + 'ToolBar', + 'TreeCtrl', + 'Validator', + ]), + + ('"Book" Controls', [ + 'AUI_Notebook', + 'Choicebook', + 'Listbook', + 'Notebook', + 'Toolbook', + 'Treebook', ]), ('Custom Controls', [ - 'AnalogClockWindow', + 'AnalogClock', + 'ButtonPanel', 'ColourSelect', + 'ComboTreeBox', + 'CustomTreeCtrl', + 'Editor', + 'FlatNotebook', 'GenericButtons', - 'wxEditor', - 'wxGenericDirCtrl', - 'wxLEDNumberCtrl', - 'wxMultiSash', - 'wxPopupControl', - 'wxPyColourChooser', - 'wxTreeListCtrl', + 'GenericDirCtrl', + 'LEDNumberCtrl', + 'MultiSash', + 'PopupControl', + 'PyColourChooser', + 'TreeListCtrl', ]), # controls coming from other libraries ('More Windows/Controls', [ - #'wxFloatBar', deprecated - #'wxMVCTree', deprecated - #'wxRightTextCtrl', deprecated as we have wxTE_RIGHT now. + 'ActiveX_FlashWindow', + 'ActiveX_IEHtmlWindow', + 'ActiveX_PDFWindow', + 'BitmapComboBox', + 'Calendar', + 'CalendarCtrl', + 'CheckListCtrlMixin', + 'CollapsiblePane', + 'ComboCtrl', 'ContextHelp', + 'DatePickerCtrl', + 'DynamicSashWindow', + 'EditableListBox', + 'ExpandoTextCtrl', 'FancyText', - 'FloatCanvas', 'FileBrowseButton', + 'FloatBar', + 'FloatCanvas', + 'FoldPanelBar', + 'HtmlWindow', + 'HyperLinkCtrl', + 'IntCtrl', + 'MVCTree', 'MaskedEditControls', - 'PyShell', + 'MaskedNumCtrl', + 'MediaCtrl', + 'MultiSplitterWindow', + 'OwnerDrawnComboBox', + 'Pickers', 'PyCrust', 'PyPlot', + 'PyShell', + 'RichTextCtrl', + 'ScrolledPanel', 'SplitTree', + 'StyledTextCtrl_1', + 'StyledTextCtrl_2', 'TablePrint', 'Throbber', - 'wxCalendar', - 'wxCalendarCtrl', - 'wxDynamicSashWindow', - 'wxEditableListBox', - 'wxHtmlWindow', - 'wxIEHtmlWin', - 'wxIntCtrl', - 'wxMimeTypesManager', - 'wxMaskedNumCtrl', - 'wxScrolledPanel', - 'wxStyledTextCtrl_1', - 'wxStyledTextCtrl_2', - 'wxTimeCtrl', - 'wxVListBox', + 'Ticker', + 'TimeCtrl', + 'VListBox', ]), # How to lay out the controls in a frame/dialog ('Window Layout', [ + 'GridBagSizer', 'LayoutAnchors', + 'LayoutConstraints', 'Layoutf', 'RowColSizer', + 'ScrolledPanel', + 'SizedControls', 'Sizers', - 'wxGridBagSizer', - 'wxLayoutConstraints', - 'wxScrolledPanel', - 'wxXmlResource', - 'wxXmlResourceHandler', - 'wxXmlResourceSubclass', + 'XmlResource', + 'XmlResourceHandler', + 'XmlResourceSubclass', ]), # ditto ('Process and Events', [ + 'DelayedResult', 'EventManager', - 'infoframe', - 'OOR', + 'KeyEvents', + 'Process', 'PythonEvents', 'Threads', - 'wxKeyEvents', - 'wxProcess', - 'wxTimer', + 'Timer', + ##'infoframe', # needs better explanation and some fixing ]), # Clipboard and DnD @@ -188,38 +253,43 @@ _treeList = [ # Images ('Using Images', [ + 'AlphaDrawing', + 'AnimateCtrl', + 'ArtProvider', + 'BitmapFromBuffer', 'Cursor', + 'DragImage', + 'Image', + 'ImageAlpha', + 'ImageFromStream', + 'Img2PyArtProvider', + 'Mask', + 'RawBitmapAccess', 'Throbber', - 'wxArtProvider', - 'wxDragImage', - 'wxImage', - 'wxImageFromStream', - 'wxMask', ]), # Other stuff ('Miscellaneous', [ + 'AlphaDrawing', 'ColourDB', - 'DialogUnits', + ##'DialogUnits', # needs more explanations + 'DragScroller', 'DrawXXXList', + 'FileHistory', 'FontEnumerator', - 'NewNamespace', + 'GraphicsContext', + 'GLCanvas', + 'I18N', + 'Joystick', + 'MimeTypesManager', + 'MouseGestures', + 'OGL', 'PrintFramework', + 'PseudoDC', 'ShapedWindow', - 'Throbber', + 'Sound', + 'StandardPaths', 'Unicode', - 'wxFileHistory', - 'wxJoystick', - 'wxOGL', - 'wxWave', - ]), - - # need libs not coming with the demo - ('Objects using an external library', [ - 'ActiveXWrapper_Acrobat', - 'ActiveXWrapper_IE', - 'wxGLCanvas', - #'wxPlotCanvas', # deprecated, use PyPlot ]), @@ -240,9 +310,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') @@ -251,39 +322,106 @@ 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 wxStyledTextCtrl_2 sample first, fall back to wxTextCtrl +# 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 + ##raise ImportError # for testing the alternate implementation from wx import stc - from wxStyledTextCtrl_2 import PythonSTC - class DemoCodeViewer(PythonSTC): - def __init__(self, parent, ID): - PythonSTC.__init__(self, parent, ID) + 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): - 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) @@ -294,6 +432,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. @@ -329,9 +472,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) @@ -354,9 +500,14 @@ try: if wx.Platform == '__WXMSW__': self.StyleSetSpec(stc.STC_STYLE_DEFAULT, 'fore:#000000,back:#FFFFFF,face:Courier New,size:9') + elif wx.Platform == '__WXMAC__': + # TODO: if this looks fine on Linux too, remove the Mac-specific case + # and use this whenever OS != MSW. + self.StyleSetSpec(stc.STC_STYLE_DEFAULT, + 'fore:#000000,back:#FFFFFF,face:Courier') else: self.StyleSetSpec(stc.STC_STYLE_DEFAULT, - 'fore:#000000,back:#FFFFFF,face:Courier,size:12') + 'fore:#000000,back:#FFFFFF,face:Courier,size:9') # Clear styles and revert to default. self.StyleClearAll() @@ -365,8 +516,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 @@ -407,47 +557,625 @@ 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, "" , "" , "" , 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] + 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 = "" + 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 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] == "" 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 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 == '__WXMSW__': - # setup a taskbar icon, and catch some events from it - 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) - + try: + self.tbicon = DemoTaskBarIcon(self) + except: + self.tbicon = None + wx.CallAfter(self.ShowTip) self.otherWin = None @@ -459,8 +1187,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) + 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) @@ -469,85 +1197,97 @@ 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() - exitID = wx.NewId() - menu.Append(exitID, 'E&xit\tAlt-X', 'Get the heck outta here!') - self.Bind(wx.EVT_MENU, self.OnFileExit, id=exitID) - wx.App_SetMacExitMenuItemId(exitID) + item = menu.Append(-1, '&Redirect Output', + 'Redirect print statements to a window', + wx.ITEM_CHECK) + self.Bind(wx.EVT_MENU, self.OnToggleRedirect, item) + + exitItem = menu.Append(-1, 'E&xit\tCtrl-Q', 'Get the heck outta here!') + self.Bind(wx.EVT_MENU, self.OnFileExit, exitItem) + wx.App.SetMacExitMenuItemId(exitItem.GetId()) self.mainmenu.Append(menu, '&File') # Make a Demo menu menu = wx.Menu() - for item in _treeList: + for item in _treeList[:-1]: submenu = wx.Menu() for childItem in item[1]: - mID = wx.NewId() - submenu.Append(mID, childItem) - self.Bind(wx.EVT_MENU, self.OnDemoMenu, id=mID) + 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 Help menu - helpID = wx.NewId() - findID = wx.NewId() - findnextID = wx.NewId() menu = wx.Menu() - menu.Append(findID, '&Find\tCtrl-F', 'Find in the Demo Code') - menu.Append(findnextID, 'Find &Next\tF3', 'Find Next') + 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') + inspToolItem = menu.Append(-1, 'Open &Widget Inspector\tF6', + 'A tool that lets you browse the live widgets and sizers in an application') menu.AppendSeparator() - menu.Append(helpID, '&About\tCtrl-H', 'wxPython RULES!!!') - wx.App_SetMacAboutMenuItemId(helpID) - self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=helpID) - self.Bind(wx.EVT_MENU, self.OnHelpFind, id=findID) - self.Bind(wx.EVT_MENU, self.OnFindNext, id=findnextID) - 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) + helpItem = menu.Append(-1, '&About wxPython Demo', 'wxPython RULES!!!') + wx.App.SetMacAboutMenuItemId(helpItem.GetId()) + + self.Bind(wx.EVT_MENU, self.OnOpenShellWindow, shellItem) + self.Bind(wx.EVT_MENU, self.OnOpenWidgetInspector, inspToolItem) + 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_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) self.finddata = wx.FindReplaceData() + self.finddata.SetFlags(wx.FR_DOWN) - if 0: + if False: # 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) + aTable = wx.AcceleratorTable([(wx.ACCEL_ALT, ord('X'), exitItem.GetId()), + (wx.ACCEL_CTRL, ord('H'), helpItem.GetId()), + (wx.ACCEL_CTRL, ord('F'), findItem.GetId()), + (wx.ACCEL_NORMAL, wx.WXK_F3, findnextItem.GetId()), + (wx.ACCEL_NORMAL, wx.WXK_F9, shellItem.GetId()), ]) self.SetAcceleratorTable(aTable) # Create a TreeCtrl tID = wx.NewId() + leftPanel = wx.Panel(splitter) + + self.filter = wx.SearchCtrl(leftPanel) + self.filter.ShowCancelButton(True) + self.filter.Bind(wx.EVT_TEXT, self.RecreateTree) + self.filter.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, + lambda e: self.filter.SetValue('')) + self.treeMap = {} - self.tree = wx.TreeCtrl(splitter, tID, style = + self.tree = wx.TreeCtrl(leftPanel, 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.root = self.tree.AddRoot("wxPython Overview") + self.RecreateTree() 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) - - # 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 @@ -563,77 +1303,104 @@ 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 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 up a log on the View Log Notebook page + # Set up a log window self.log = wx.TextCtrl(splitter2, -1, style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL) + if wx.Platform == "__WXMAC__": + self.log.MacCheckSpelling(False) # 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) - # Make the splitter on the right expand the top wind when resized + # add the windows to the splitter and split it. + splitter2.SplitHorizontally(self.nb, self.log, -160) + leftBox = wx.BoxSizer(wx.VERTICAL) + leftBox.Add(self.tree, 1, wx.EXPAND) + leftBox.Add(wx.StaticText(leftPanel, label = "Filter Demos:"), 0, wx.TOP|wx.LEFT, 5) + leftBox.Add(self.filter, 0, wx.EXPAND|wx.ALL, 5) + leftPanel.SetSizer(leftBox) + splitter.SplitVertically(leftPanel, splitter2, 220) + + 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 + self.tree.SelectItem(self.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) - wx.LogMessage('window handle: %s' % self.GetHandle()) - - #--------------------------------------------- + + def RecreateTree(self, evt=None): + self.tree.Freeze() + self.tree.DeleteAllItems() + self.root = self.tree.AddRoot("wxPython Overview") + firstChild = None + filter = self.filter.GetValue() + for category, items in _treeList: + if filter: + items = [item for item in items if filter.lower() in item.lower()] + if items: + child = self.tree.AppendItem(self.root, category) + if not firstChild: firstChild = child + for childItem in items: + theDemo = self.tree.AppendItem(child, childItem) + self.treeMap[childItem] = theDemo + + self.tree.Expand(self.root) + if firstChild: + self.tree.Expand(firstChild) + if filter: + self.tree.ExpandAll() + self.tree.Thaw() + def WriteText(self, text): if text[-1:] == '\n': text = text[:-1] wx.LogMessage(text) - def write(self, txt): self.WriteText(txt) @@ -651,6 +1418,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 +1427,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 != '' 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) @@ -745,6 +1580,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,24 +1596,40 @@ 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 | - wx.FR_NOMATCHCASE | - wx.FR_NOWHOLEWORD) + wx.FR_NOMATCHCASE | 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() findstring = self.finddata.GetFindString().lower() - loc = textstring.find(findstring, start) + backward = not (self.finddata.GetFlags() & wx.FR_DOWN) + if backward: + start = editor.GetSelection()[0] + loc = textstring.rfind(findstring, 0, start) + else: + start = editor.GetSelection()[1] + 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 backward: + start = end + loc = textstring.rfind(findstring, 0, start) + else: + 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', @@ -782,8 +1642,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 +1656,58 @@ 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 OnOpenWidgetInspector(self, evt): + # Activate the widget inspection tool + from wx.lib.inspect import InspectionTool + if not InspectionTool().initialized: + InspectionTool().Init() + + # Find a widget to be selected in the tree. Use either the + # one under the cursor, if any, or this frame. + wnd = wx.FindWindowAtPointer() + if not wnd: + wnd = self + InspectionTool().Show(wnd, True) + + #--------------------------------------------- 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 + if self.tbicon is not None: + self.tbicon.Destroy() self.Destroy() @@ -811,7 +1715,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 +1745,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,26 +1756,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): - frame = wxPythonDemo(None, -1, "wxPython: (A Demonstration)") - frame.Show() - evt.Skip() # Make sure the default handler runs too... + # Make sure the default handler runs too so this window gets + # destroyed + evt.Skip() + self.Hide() + + # 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): """ @@ -907,14 +1806,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() @@ -930,24 +1832,21 @@ def main(): os.chdir(demoPath) except: pass - app = MyApp(0) #wx.Platform == "__WXMAC__") + app = MyApp(False) app.MainLoop() - #--------------------------------------------------------------------------- - -overview = """ +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++. +

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 @@ -975,6 +1874,7 @@ is loaded in another tab for you to browse and learn from. #---------------------------------------------------------------------------- if __name__ == '__main__': + __name__ = 'Main' main() #----------------------------------------------------------------------------