a067618812
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@37566 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1800 lines
60 KiB
Python
1800 lines
60 KiB
Python
#!/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 = %s (%s)" % (wx.VERSION_STRING, wx.USE_UNICODE and 'unicode' or 'ansi')
|
|
##print "pid:", os.getpid()
|
|
##raw_input("Press Enter...")
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
|
_treeList = [
|
|
# new stuff
|
|
('Recent Additions/Updates', [
|
|
'Treebook',
|
|
'Toolbook',
|
|
]),
|
|
|
|
# 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',
|
|
'MultiChoiceDialog',
|
|
'PageSetupDialog',
|
|
'PrintDialog',
|
|
'ProgressDialog',
|
|
'SingleChoiceDialog',
|
|
'TextEntryDialog',
|
|
]),
|
|
|
|
# dialogs from libraries
|
|
('More Dialogs', [
|
|
'ImageBrowser',
|
|
'ScrolledMessageDialog',
|
|
]),
|
|
|
|
# core controls
|
|
('Core Windows/Controls', [
|
|
'BitmapButton',
|
|
'Button',
|
|
'CheckBox',
|
|
'CheckListBox',
|
|
'Choice',
|
|
'ComboBox',
|
|
'Gauge',
|
|
'Grid',
|
|
'Grid_MegaExample',
|
|
'ListBox',
|
|
'ListCtrl',
|
|
'ListCtrl_virtual',
|
|
'ListCtrl_edit',
|
|
'Menu',
|
|
'PopupMenu',
|
|
'PopupWindow',
|
|
'RadioBox',
|
|
'RadioButton',
|
|
'SashWindow',
|
|
'ScrolledWindow',
|
|
'Slider',
|
|
'SpinButton',
|
|
'SpinCtrl',
|
|
'SplitterWindow',
|
|
'StaticBitmap',
|
|
'StaticBox',
|
|
'StaticText',
|
|
'StatusBar',
|
|
'StockButtons',
|
|
'TextCtrl',
|
|
'ToggleButton',
|
|
'ToolBar',
|
|
'TreeCtrl',
|
|
'Validator',
|
|
]),
|
|
|
|
('"Book" Controls', [
|
|
'Choicebook',
|
|
'Listbook',
|
|
'Notebook',
|
|
'Toolbook',
|
|
'Treebook',
|
|
]),
|
|
|
|
('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',
|
|
'DatePickerCtrl',
|
|
'DynamicSashWindow',
|
|
'EditableListBox',
|
|
'FancyText',
|
|
'FileBrowseButton',
|
|
'FloatBar',
|
|
'FloatCanvas',
|
|
'FoldPanelBar',
|
|
'GIFAnimationCtrl',
|
|
'HtmlWindow',
|
|
'HyperLinkCtrl',
|
|
'IntCtrl',
|
|
'MVCTree',
|
|
'MaskedEditControls',
|
|
'MaskedNumCtrl',
|
|
'MediaCtrl',
|
|
'MultiSplitterWindow',
|
|
'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 explanation and some fixing
|
|
]),
|
|
|
|
# Clipboard and DnD
|
|
('Clipboard and DnD', [
|
|
'CustomDragAndDrop',
|
|
'DragAndDrop',
|
|
'URLDragAndDrop',
|
|
]),
|
|
|
|
# Images
|
|
('Using Images', [
|
|
'ArtProvider',
|
|
'Cursor',
|
|
'DragImage',
|
|
'GIFAnimationCtrl',
|
|
'Image',
|
|
'ImageAlpha',
|
|
'ImageFromStream',
|
|
'Mask',
|
|
'Throbber',
|
|
]),
|
|
|
|
# Other stuff
|
|
('Miscellaneous', [
|
|
'ColourDB',
|
|
##'DialogUnits', # needs more explanations
|
|
'DrawXXXList',
|
|
'FileHistory',
|
|
'FontEnumerator',
|
|
'GLCanvas',
|
|
'Joystick',
|
|
'MimeTypesManager',
|
|
'MouseGestures',
|
|
'OGL',
|
|
'PrintFramework',
|
|
'ShapedWindow',
|
|
'Sound',
|
|
'StandardPaths',
|
|
'Unicode',
|
|
]),
|
|
|
|
|
|
('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):
|
|
#print 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 simply display a message in the demo pane
|
|
# rather than running the sample itself.
|
|
|
|
class MessagePanel(wx.Panel):
|
|
def __init__(self, parent, message, caption='', flags=0):
|
|
wx.Panel.__init__(self, parent)
|
|
|
|
# Make widgets
|
|
if flags:
|
|
artid = None
|
|
if flags & wx.ICON_EXCLAMATION:
|
|
artid = wx.ART_WARNING
|
|
elif flags & wx.ICON_ERROR:
|
|
artid = wx.ART_ERROR
|
|
elif flags & wx.ICON_QUESTION:
|
|
artid = wx.ART_QUESTION
|
|
elif flags & wx.ICON_INFORMATION:
|
|
artid = wx.ART_INFORMATION
|
|
|
|
if artid is not None:
|
|
bmp = wx.ArtProvider.GetBitmap(artid, wx.ART_MESSAGE_BOX, (32,32))
|
|
icon = wx.StaticBitmap(self, -1, bmp)
|
|
else:
|
|
icon = (32,32) # make a spacer instead
|
|
|
|
if caption:
|
|
caption = wx.StaticText(self, -1, caption)
|
|
caption.SetFont(wx.Font(28, wx.SWISS, wx.NORMAL, wx.BOLD))
|
|
|
|
message = wx.StaticText(self, -1, message)
|
|
|
|
# add to sizers for layout
|
|
tbox = wx.BoxSizer(wx.VERTICAL)
|
|
if caption:
|
|
tbox.Add(caption)
|
|
tbox.Add((10,10))
|
|
tbox.Add(message)
|
|
|
|
hbox = wx.BoxSizer(wx.HORIZONTAL)
|
|
hbox.Add((10,10), 1)
|
|
hbox.Add(icon)
|
|
hbox.Add((10,10))
|
|
hbox.Add(tbox)
|
|
hbox.Add((10,10), 1)
|
|
|
|
box = wx.BoxSizer(wx.VERTICAL)
|
|
box.Add((10,10), 1)
|
|
box.Add(hbox, 0, wx.EXPAND)
|
|
box.Add((10,10), 2)
|
|
|
|
self.SetSizer(box)
|
|
self.Fit()
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
# A class to be used to display source code in the demo. Try using the
|
|
# wxSTC in the StyledTextCtrl_2 sample first, fall back to wxTextCtrl
|
|
# 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(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"""
|
|
str = apply(os.path.join, tuple(path.split('/')))
|
|
# HACK: on Linux, a leading / gets lost...
|
|
if path.startswith('/'):
|
|
str = '/' + str
|
|
return str
|
|
|
|
|
|
def GetModifiedDirectory():
|
|
"""
|
|
Returns the directory where modified versions of the demo files
|
|
are stored
|
|
"""
|
|
return opj(wx.GetHomeDir() + "/.wxPyDemo/modified/")
|
|
|
|
|
|
def GetModifiedFilename(name):
|
|
"""
|
|
Returns the filename of the modified version of the specified demo
|
|
"""
|
|
if not name.endswith(".py"):
|
|
name = name + ".py"
|
|
return GetModifiedDirectory() + name
|
|
|
|
|
|
def GetOriginalFilename(name):
|
|
"""
|
|
Returns the filename of the original version of the specified demo
|
|
"""
|
|
if not name.endswith(".py"):
|
|
name = name + ".py"
|
|
return name
|
|
|
|
|
|
def DoesModifiedExist(name):
|
|
"""Returns whether the specified demo has a modified copy"""
|
|
if os.path.exists(GetModifiedFilename(name)):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
class ModuleDictWrapper:
|
|
"""Emulates a module with a dynamically compiled __dict__"""
|
|
def __init__(self, dict):
|
|
self.dict = dict
|
|
|
|
def __getattr__(self, name):
|
|
if name in self.dict:
|
|
return self.dict[name]
|
|
else:
|
|
raise AttributeError
|
|
|
|
class DemoModules:
|
|
"""
|
|
Dynamically manages the original/modified versions of a demo
|
|
module
|
|
"""
|
|
def __init__(self, name):
|
|
self.modActive = -1
|
|
self.name = name
|
|
|
|
# (dict , source , filename , description , error information )
|
|
# ( 0 , 1 , 2 , 3 , 4 )
|
|
self.modules = [[None, "" , "" , "<original>" , None],
|
|
[None, "" , "" , "<modified>" , None]]
|
|
|
|
# load original module
|
|
self.LoadFromFile(modOriginal, GetOriginalFilename(name))
|
|
self.SetActive(modOriginal)
|
|
|
|
# load modified module (if one exists)
|
|
if DoesModifiedExist(name):
|
|
self.LoadFromFile(modModified, GetModifiedFilename(name))
|
|
|
|
|
|
def LoadFromFile(self, modID, filename):
|
|
self.modules[modID][2] = filename
|
|
file = open(filename, "rt")
|
|
self.LoadFromSource(modID, file.read())
|
|
file.close()
|
|
|
|
|
|
def LoadFromSource(self, modID, source):
|
|
self.modules[modID][1] = source
|
|
self.LoadDict(modID)
|
|
|
|
|
|
def LoadDict(self, modID):
|
|
if self.name != __name__:
|
|
source = self.modules[modID][1]
|
|
#description = self.modules[modID][3]
|
|
description = self.modules[modID][2]
|
|
|
|
try:
|
|
self.modules[modID][0] = {}
|
|
code = compile(source, description, "exec")
|
|
exec code in self.modules[modID][0]
|
|
except:
|
|
self.modules[modID][4] = DemoError(sys.exc_info())
|
|
self.modules[modID][0] = None
|
|
else:
|
|
self.modules[modID][4] = None
|
|
|
|
|
|
def SetActive(self, modID):
|
|
if modID != modOriginal and modID != modModified:
|
|
raise LookupError
|
|
else:
|
|
self.modActive = modID
|
|
|
|
|
|
def GetActive(self):
|
|
dict = self.modules[self.modActive][0]
|
|
if dict is None:
|
|
return None
|
|
else:
|
|
return ModuleDictWrapper(dict)
|
|
|
|
|
|
def GetActiveID(self):
|
|
return self.modActive
|
|
|
|
|
|
def GetSource(self, modID = None):
|
|
if modID is None:
|
|
modID = self.modActive
|
|
return self.modules[modID][1]
|
|
|
|
|
|
def GetFilename(self, modID = None):
|
|
if modID is None:
|
|
modID = self.modActive
|
|
return self.modules[self.modActive][2]
|
|
|
|
|
|
def GetErrorInfo(self, modID = None):
|
|
if modID is None:
|
|
modID = self.modActive
|
|
return self.modules[self.modActive][4]
|
|
|
|
|
|
def Exists(self, modID):
|
|
return self.modules[modID][1] != ""
|
|
|
|
|
|
def UpdateFile(self, modID = None):
|
|
"""Updates the file from which a module was loaded
|
|
with (possibly updated) source"""
|
|
if modID is None:
|
|
modID = self.modActive
|
|
|
|
source = self.modules[modID][1]
|
|
filename = self.modules[modID][2]
|
|
|
|
try:
|
|
file = open(filename, "wt")
|
|
file.write(source)
|
|
finally:
|
|
file.close()
|
|
|
|
|
|
def Delete(self, modID):
|
|
if self.modActive == modID:
|
|
self.SetActive(0)
|
|
|
|
self.modules[modID][0] = None
|
|
self.modules[modID][1] = ""
|
|
self.modules[modID][2] = ""
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
class DemoError:
|
|
"""Wraps and stores information about the current exception"""
|
|
def __init__(self, exc_info):
|
|
import copy
|
|
|
|
excType, excValue = exc_info[:2]
|
|
# traceback list entries: (filename, line number, function name, text)
|
|
self.traceback = traceback.extract_tb(exc_info[2])
|
|
|
|
# --Based on traceback.py::format_exception_only()--
|
|
if type(excType) == types.ClassType:
|
|
self.exception_type = excType.__name__
|
|
else:
|
|
self.exception_type = excType
|
|
|
|
# If it's a syntax error, extra information needs
|
|
# to be added to the traceback
|
|
if excType is SyntaxError:
|
|
try:
|
|
msg, (filename, lineno, self.offset, line) = excValue
|
|
except:
|
|
pass
|
|
else:
|
|
if not filename:
|
|
filename = "<string>"
|
|
line = line.strip()
|
|
self.traceback.append( (filename, lineno, "", line) )
|
|
excValue = msg
|
|
try:
|
|
self.exception_details = str(excValue)
|
|
except:
|
|
self.exception_details = "<unprintable %s object>" & type(excValue).__name__
|
|
|
|
del exc_info
|
|
|
|
def __str__(self):
|
|
ret = "Type %s \n \
|
|
Traceback: %s \n \
|
|
Details : %s" % ( str(self.exception_type), str(self.traceback), self.exception_details )
|
|
return ret
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
class DemoErrorPanel(wx.Panel):
|
|
"""Panel put into the demo tab when the demo fails to run due to errors"""
|
|
|
|
def __init__(self, parent, codePanel, demoError, log):
|
|
wx.Panel.__init__(self, parent, -1)#, style=wx.NO_FULL_REPAINT_ON_RESIZE)
|
|
self.codePanel = codePanel
|
|
self.nb = parent
|
|
self.log = log
|
|
|
|
self.box = wx.BoxSizer(wx.VERTICAL)
|
|
|
|
# Main Label
|
|
self.box.Add(wx.StaticText(self, -1, "An error has occurred while trying to run the demo")
|
|
, 0, wx.ALIGN_CENTER | wx.TOP, 10)
|
|
|
|
# Exception Information
|
|
boxInfo = wx.StaticBox(self, -1, "Exception Info" )
|
|
boxInfoSizer = wx.StaticBoxSizer(boxInfo, wx.VERTICAL ) # Used to center the grid within the box
|
|
boxInfoGrid = wx.FlexGridSizer(0, 2, 0, 0)
|
|
textFlags = wx.ALIGN_RIGHT | wx.LEFT | wx.RIGHT | wx.TOP
|
|
boxInfoGrid.Add(wx.StaticText(self, -1, "Type: "), 0, textFlags, 5 )
|
|
boxInfoGrid.Add(wx.StaticText(self, -1, demoError.exception_type) , 0, textFlags, 5 )
|
|
boxInfoGrid.Add(wx.StaticText(self, -1, "Details: ") , 0, textFlags, 5 )
|
|
boxInfoGrid.Add(wx.StaticText(self, -1, demoError.exception_details) , 0, textFlags, 5 )
|
|
boxInfoSizer.Add(boxInfoGrid, 0, wx.ALIGN_CENTRE | wx.ALL, 5 )
|
|
self.box.Add(boxInfoSizer, 0, wx.ALIGN_CENTER | wx.ALL, 5)
|
|
|
|
# Set up the traceback list
|
|
# This one automatically resizes last column to take up remaining space
|
|
from ListCtrl import TestListCtrl
|
|
self.list = TestListCtrl(self, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
|
|
self.list.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
|
|
self.list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
|
|
self.list.InsertColumn(0, "Filename")
|
|
self.list.InsertColumn(1, "Line", wx.LIST_FORMAT_RIGHT)
|
|
self.list.InsertColumn(2, "Function")
|
|
self.list.InsertColumn(3, "Code")
|
|
self.InsertTraceback(self.list, demoError.traceback)
|
|
self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
|
|
self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE)
|
|
self.box.Add(wx.StaticText(self, -1, "Traceback:")
|
|
, 0, wx.ALIGN_CENTER | wx.TOP, 5)
|
|
self.box.Add(self.list, 1, wx.GROW | wx.ALIGN_CENTER | wx.ALL, 5)
|
|
self.box.Add(wx.StaticText(self, -1, "Entries from the demo module are shown in blue\n"
|
|
+ "Double-click on them to go to the offending line")
|
|
, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5)
|
|
|
|
self.box.Fit(self)
|
|
self.SetSizer(self.box)
|
|
|
|
|
|
def InsertTraceback(self, list, traceback):
|
|
#Add the traceback data
|
|
for x in range(len(traceback)):
|
|
data = traceback[x]
|
|
list.InsertStringItem(x, os.path.basename(data[0])) # Filename
|
|
list.SetStringItem(x, 1, str(data[1])) # Line
|
|
list.SetStringItem(x, 2, str(data[2])) # Function
|
|
list.SetStringItem(x, 3, str(data[3])) # Code
|
|
|
|
# Check whether this entry is from the demo module
|
|
if data[0] == "<original>" or data[0] == "<modified>": # FIXME: make more generalised
|
|
self.list.SetItemData(x, int(data[1])) # Store line number for easy access
|
|
# Give it a blue colour
|
|
item = self.list.GetItem(x)
|
|
item.SetTextColour(wx.BLUE)
|
|
self.list.SetItem(item)
|
|
else:
|
|
self.list.SetItemData(x, -1) # Editor can't jump into this one's code
|
|
|
|
|
|
def OnItemSelected(self, event):
|
|
# This occurs before OnDoubleClick and can be used to set the
|
|
# currentItem. OnDoubleClick doesn't get a wxListEvent....
|
|
self.currentItem = event.m_itemIndex
|
|
event.Skip()
|
|
|
|
|
|
def OnDoubleClick(self, event):
|
|
# If double-clicking on a demo's entry, jump to the line number
|
|
line = self.list.GetItemData(self.currentItem)
|
|
if line != -1:
|
|
self.nb.SetSelection(1) # Switch to the code viewer tab
|
|
wx.CallAfter(self.codePanel.JumpToLine, line-1, True)
|
|
event.Skip()
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
class DemoTaskBarIcon(wx.TaskBarIcon):
|
|
TBMENU_RESTORE = wx.NewId()
|
|
TBMENU_CLOSE = wx.NewId()
|
|
TBMENU_CHANGE = wx.NewId()
|
|
TBMENU_REMOVE = wx.NewId()
|
|
|
|
def __init__(self, frame):
|
|
wx.TaskBarIcon.__init__(self)
|
|
self.frame = frame
|
|
|
|
# Set the image
|
|
icon = self.MakeIcon(images.getWXPdemoImage())
|
|
self.SetIcon(icon, "wxPython Demo")
|
|
self.imgidx = 1
|
|
|
|
# bind some events
|
|
self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate)
|
|
self.Bind(wx.EVT_MENU, self.OnTaskBarActivate, id=self.TBMENU_RESTORE)
|
|
self.Bind(wx.EVT_MENU, self.OnTaskBarClose, id=self.TBMENU_CLOSE)
|
|
self.Bind(wx.EVT_MENU, self.OnTaskBarChange, id=self.TBMENU_CHANGE)
|
|
self.Bind(wx.EVT_MENU, self.OnTaskBarRemove, id=self.TBMENU_REMOVE)
|
|
|
|
|
|
def CreatePopupMenu(self):
|
|
"""
|
|
This method is called by the base class when it needs to popup
|
|
the menu for the default EVT_RIGHT_DOWN event. Just create
|
|
the menu how you want it and return it from this function,
|
|
the base class takes care of the rest.
|
|
"""
|
|
menu = wx.Menu()
|
|
menu.Append(self.TBMENU_RESTORE, "Restore wxPython Demo")
|
|
menu.Append(self.TBMENU_CLOSE, "Close wxPython Demo")
|
|
menu.AppendSeparator()
|
|
menu.Append(self.TBMENU_CHANGE, "Change the TB Icon")
|
|
menu.Append(self.TBMENU_REMOVE, "Remove the TB Icon")
|
|
return menu
|
|
|
|
|
|
def MakeIcon(self, img):
|
|
"""
|
|
The various platforms have different requirements for the
|
|
icon size...
|
|
"""
|
|
if "wxMSW" in wx.PlatformInfo:
|
|
img = img.Scale(16, 16)
|
|
elif "wxGTK" in wx.PlatformInfo:
|
|
img = img.Scale(22, 22)
|
|
# wxMac can be any size upto 128x128, so leave the source img alone....
|
|
icon = wx.IconFromBitmap(img.ConvertToBitmap() )
|
|
return icon
|
|
|
|
|
|
def OnTaskBarActivate(self, evt):
|
|
if self.frame.IsIconized():
|
|
self.frame.Iconize(False)
|
|
if not self.frame.IsShown():
|
|
self.frame.Show(True)
|
|
self.frame.Raise()
|
|
|
|
|
|
def OnTaskBarClose(self, evt):
|
|
self.frame.Close()
|
|
|
|
|
|
def OnTaskBarChange(self, evt):
|
|
names = [ "WXPdemo", "Mondrian", "Pencil", "Carrot" ]
|
|
name = names[self.imgidx]
|
|
|
|
getFunc = getattr(images, "get%sImage" % name)
|
|
self.imgidx += 1
|
|
if self.imgidx >= len(names):
|
|
self.imgidx = 0
|
|
|
|
icon = self.MakeIcon(getFunc())
|
|
self.SetIcon(icon, "This is a new icon: " + name)
|
|
|
|
|
|
def OnTaskBarRemove(self, evt):
|
|
self.RemoveIcon()
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
class wxPythonDemo(wx.Frame):
|
|
overviewText = "wxPython Overview"
|
|
|
|
def __init__(self, parent, 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.demoPage = None
|
|
self.codePage = None
|
|
self.shell = None
|
|
self.firstTime = True
|
|
self.finddlg = None
|
|
|
|
icon = images.getWXPdemoIcon()
|
|
self.SetIcon(icon)
|
|
|
|
self.tbicon = DemoTaskBarIcon(self)
|
|
|
|
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_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:
|
|
# This is another way to set Accelerators, in addition to
|
|
# using the '\t<key>' 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.SetStandardFonts()
|
|
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)
|
|
|
|
|
|
self.Bind(wx.EVT_ACTIVATE, self.OnActivate)
|
|
wx.GetApp().Bind(wx.EVT_ACTIVATE_APP, self.OnAppActivate)
|
|
|
|
# add the windows to the splitter and split it.
|
|
splitter2.SplitHorizontally(self.nb, self.log, -160)
|
|
splitter.SplitVertically(self.tree, splitter2, 200)
|
|
|
|
splitter.SetMinimumPaneSize(120)
|
|
splitter2.SetMinimumPaneSize(60)
|
|
|
|
# Make the splitter on the right expand the top window when resized
|
|
def SplitterOnSize(evt):
|
|
splitter = evt.GetEventObject()
|
|
sz = splitter.GetSize()
|
|
splitter.SetSashPosition(sz.height - 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 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
|
|
|
|
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 != '<html>' and lead != '<HTML>':
|
|
text = '<br>'.join(text.split('\n'))
|
|
if wx.USE_UNICODE:
|
|
text = text.decode('iso8859_1')
|
|
self.ovr.SetPage(text)
|
|
self.nb.SetPageText(0, name)
|
|
|
|
#---------------------------------------------
|
|
# 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):
|
|
if self.finddlg != None:
|
|
return
|
|
|
|
self.nb.SetSelection(1)
|
|
self.finddlg = wx.FindReplaceDialog(self, self.finddata, "Find",
|
|
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 = editor.GetLastPosition()
|
|
textstring = editor.GetRange(0, end).lower()
|
|
findstring = self.finddata.GetFindString().lower()
|
|
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
|
|
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',
|
|
wx.OK | wx.ICON_INFORMATION)
|
|
dlg.ShowModal()
|
|
dlg.Destroy()
|
|
if self.finddlg:
|
|
if loc == -1:
|
|
self.finddlg.SetFocus()
|
|
return
|
|
else:
|
|
self.finddlg.Destroy()
|
|
self.finddlg = None
|
|
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()
|
|
self.finddlg = None
|
|
|
|
|
|
def OnOpenShellWindow(self, evt):
|
|
if self.shell:
|
|
# if it already exists then just make sure it's visible
|
|
s = self.shell
|
|
if s.IsIconized():
|
|
s.Iconize(False)
|
|
s.Raise()
|
|
else:
|
|
# Make a PyShell window
|
|
from wx import py
|
|
namespace = { 'wx' : wx,
|
|
'app' : wx.GetApp(),
|
|
'frame' : self,
|
|
}
|
|
self.shell = py.shell.ShellFrame(None, locals=namespace)
|
|
self.shell.SetSize((640,480))
|
|
self.shell.Show()
|
|
|
|
# Hook the close event of the main frame window so that we
|
|
# close the shell at the same time if it still exists
|
|
def CloseShell(evt):
|
|
if self.shell:
|
|
self.shell.Close()
|
|
evt.Skip()
|
|
self.Bind(wx.EVT_CLOSE, CloseShell)
|
|
|
|
|
|
#---------------------------------------------
|
|
def OnCloseWindow(self, event):
|
|
self.dying = True
|
|
self.demoPage = None
|
|
self.codePage = None
|
|
self.mainmenu = None
|
|
self.tbicon.Destroy()
|
|
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 OnIconfiy(self, evt):
|
|
wx.LogMessage("OnIconfiy: %s" % evt.Iconized())
|
|
evt.Skip()
|
|
|
|
#---------------------------------------------
|
|
def OnMaximize(self, evt):
|
|
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.png")).ConvertToBitmap()
|
|
wx.SplashScreen.__init__(self, bmp,
|
|
wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT,
|
|
5000, None, -1)
|
|
self.Bind(wx.EVT_CLOSE, self.OnClose)
|
|
self.fc = wx.FutureCall(2000, self.ShowMain)
|
|
|
|
|
|
def OnClose(self, evt):
|
|
# Make sure the default handler runs too so this window gets
|
|
# destroyed
|
|
evt.Skip()
|
|
self.Hide()
|
|
|
|
# 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):
|
|
"""
|
|
Create and show the splash screen. It will then create and show
|
|
the main frame when it is time to do so.
|
|
"""
|
|
|
|
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
|
|
# ShowMain 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 = """<html><body>
|
|
<h2>wxPython</h2>
|
|
|
|
<p> wxPython is a <b>GUI toolkit</b> for the Python programming
|
|
language. It allows Python programmers to create programs with a
|
|
robust, highly functional graphical user interface, simply and easily.
|
|
It is implemented as a Python extension module (native code) that
|
|
wraps the popular wxWindows cross platform GUI library, which is
|
|
written in C++.
|
|
|
|
<p> Like Python and wxWindows, wxPython is <b>Open Source</b> which
|
|
means that it is free for anyone to use and the source code is
|
|
available for anyone to look at and modify. Or anyone can contribute
|
|
fixes or enhancements to the project.
|
|
|
|
<p> wxPython is a <b>cross-platform</b> 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 <b>simple, easy</b> to write and easy to
|
|
understand.
|
|
|
|
<p> <b>This demo</b> 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()
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|