]> git.saurik.com Git - wxWidgets.git/blame - wxPython/demo/Main.py
All the demo samples return a panel now, no need to cater for frames
[wxWidgets.git] / wxPython / demo / Main.py
CommitLineData
cf694132
RD
1#!/bin/env python
2#----------------------------------------------------------------------------
3# Name: Main.py
4# Purpose: Testing lots of stuff, controls, window types, etc.
5#
f6bcfd97 6# Author: Robin Dunn
cf694132 7#
f6bcfd97 8# Created: A long time ago, in a galaxy far, far away...
cf694132
RD
9# RCS-ID: $Id$
10# Copyright: (c) 1999 by Total Control Software
11# Licence: wxWindows license
12#----------------------------------------------------------------------------
13
08ecc920
RD
14# FIXME List:
15# * Problems with flickering related to ERASE_BACKGROUND
16# and the splitters. Might be a problem with this 2.5 beta...?
17# UPDATE: can't see on 2.5.2 GTK - maybe just a faster machine :)
18# * Demo Code menu?
19# * Annoying switching between tabs and resulting flicker
20# how to replace a page in the notebook without deleting/adding?
21# Where is SetPage!? tried freeze...tried reparent of dummy panel....
22
23# TODO List:
24# * UI design more prefessional
25# * save file positions (new field in demoModules) (@ LoadDemoSource)
26# * Update main overview
27
28# * Why don't we move _treeList into a separate module
29
30import sys, os, time, traceback, types
cf694132 31
1fded56b
RD
32import wx # This module uses the new wx namespace
33import wx.html
606d919c 34
96bfd053
RD
35import images
36
d14a1e28
RD
37# For debugging
38##wx.Trap();
ef8b9c3e 39##print "wx.VERSION_STRING = ", wx.VERSION_STRING
4276dc52 40##print os.getpid();
ef8b9c3e 41##raw_input("Press Enter...")
6fa68946 42
1e4a197e 43
cf694132
RD
44#---------------------------------------------------------------------------
45
46
47_treeList = [
9c67cbec 48 # new stuff
77d4f443 49 ('Recent Additions/Updates', [
62038e59 50 'StandardPaths',
256c055e 51 'MediaCtrl',
9c67cbec
RD
52 ]),
53
1e4a197e 54 # managed windows == things with a (optional) caption you can close
03a604a6 55 ('Frames and Dialogs', [
299647ac
RD
56 'Dialog',
57 'Frame',
58 'MDIWindows',
59 'MiniFrame',
60 'Wizard',
9c67cbec
RD
61 ]),
62
63 # the common dialogs
64 ('Common Dialogs', [
299647ac
RD
65 'ColourDialog',
66 'DirDialog',
67 'FileDialog',
299647ac
RD
68 'FindReplaceDialog',
69 'FontDialog',
70 'MessageDialog',
71 'PageSetupDialog',
72 'PrintDialog',
73 'ProgressDialog',
74 'SingleChoiceDialog',
75 'TextEntryDialog',
9c67cbec
RD
76 ]),
77
af83019e 78 # dialogs from libraries
9c67cbec 79 ('More Dialogs', [
9c67cbec 80 'ImageBrowser',
299647ac
RD
81 'MultipleChoiceDialog',
82 'ScrolledMessageDialog',
9c67cbec
RD
83 ]),
84
85 # core controls
86 ('Core Windows/Controls', [
8b7fa2d9 87 'BitmapButton',
299647ac
RD
88 'Button',
89 'CheckBox',
90 'CheckListBox',
91 'Choice',
9694b21a 92 'Choicebook',
299647ac
RD
93 'ComboBox',
94 'Gauge',
95 'Grid',
96 'Grid_MegaExample',
97 'ListBox',
98 'ListCtrl',
99 'ListCtrl_virtual',
15513a80 100 'ListCtrl_edit',
299647ac
RD
101 'Listbook',
102 'Menu',
103 'Notebook',
1fded56b 104 'PopupMenu',
299647ac
RD
105 'PopupWindow',
106 'RadioBox',
107 'RadioButton',
108 'SashWindow',
109 'ScrolledWindow',
110 'Slider',
111 'SpinButton',
112 'SpinCtrl',
113 'SplitterWindow',
114 'StaticBitmap',
115 'StaticText',
116 'StatusBar',
f2b4a4ea 117 'StockButtons',
299647ac
RD
118 'TextCtrl',
119 'ToggleButton',
120 'ToolBar',
121 'TreeCtrl',
122 'Validator',
9c67cbec
RD
123 ]),
124
d14a1e28
RD
125 ('Custom Controls', [
126 'AnalogClockWindow',
127 'ColourSelect',
299647ac 128 'Editor',
d14a1e28 129 'GenericButtons',
299647ac
RD
130 'GenericDirCtrl',
131 'LEDNumberCtrl',
132 'MultiSash',
133 'PopupControl',
134 'PyColourChooser',
135 'TreeListCtrl',
d14a1e28
RD
136 ]),
137
8b9a4190 138 # controls coming from other libraries
9c67cbec 139 ('More Windows/Controls', [
b7c75283
RD
140 'ActiveX_FlashWindow',
141 'ActiveX_IEHtmlWindow',
142 'ActiveX_PDFWindow',
299647ac
RD
143 #'RightTextCtrl', deprecated as we have wxTE_RIGHT now.
144 'Calendar',
145 'CalendarCtrl',
9c67cbec 146 'ContextHelp',
299647ac
RD
147 'DynamicSashWindow',
148 'EditableListBox',
9c67cbec
RD
149 'FancyText',
150 'FileBrowseButton',
299647ac
RD
151 'FloatBar',
152 'FloatCanvas',
153 'HtmlWindow',
299647ac 154 'IntCtrl',
256c055e 155 'MediaCtrl',
299647ac 156 'MVCTree',
1fded56b 157 'MaskedEditControls',
299647ac
RD
158 'MaskedNumCtrl',
159 'MimeTypesManager',
9c67cbec 160 'PyCrust',
80b27b4e 161 'PyPlot',
299647ac
RD
162 'PyShell',
163 'ScrolledPanel',
9c67cbec 164 'SplitTree',
299647ac
RD
165 'StyledTextCtrl_1',
166 'StyledTextCtrl_2',
9c67cbec 167 'TablePrint',
1e4a197e 168 'Throbber',
cccce1a6 169 'Ticker',
299647ac
RD
170 'TimeCtrl',
171 'VListBox',
9c67cbec
RD
172 ]),
173
174 # How to lay out the controls in a frame/dialog
175 ('Window Layout', [
299647ac 176 'GridBagSizer',
9c67cbec 177 'LayoutAnchors',
299647ac 178 'LayoutConstraints',
9c67cbec
RD
179 'Layoutf',
180 'RowColSizer',
299647ac 181 'ScrolledPanel',
9c67cbec 182 'Sizers',
299647ac
RD
183 'XmlResource',
184 'XmlResourceHandler',
185 'XmlResourceSubclass',
9c67cbec
RD
186 ]),
187
188 # ditto
189 ('Process and Events', [
1e4a197e 190 'EventManager',
299647ac 191 'KeyEvents',
299647ac 192 'Process',
9c67cbec
RD
193 'PythonEvents',
194 'Threads',
299647ac 195 'Timer',
ebd09fe9 196 ##'infoframe', # needs better explaination and some fixing
9c67cbec
RD
197 ]),
198
199 # Clipboard and DnD
200 ('Clipboard and DnD', [
201 'CustomDragAndDrop',
202 'DragAndDrop',
203 'URLDragAndDrop',
204 ]),
205
206 # Images
1e4a197e 207 ('Using Images', [
299647ac 208 'ArtProvider',
76343679 209 'Cursor',
299647ac
RD
210 'DragImage',
211 'Image',
8a88769e 212 'ImageAlpha',
299647ac
RD
213 'ImageFromStream',
214 'Mask',
1e4a197e 215 'Throbber',
9c67cbec
RD
216 ]),
217
218 # Other stuff
219 ('Miscellaneous', [
220 'ColourDB',
ebd09fe9 221 ##'DialogUnits', # needs more explainations
9c67cbec 222 'DrawXXXList',
299647ac 223 'FileHistory',
9c67cbec 224 'FontEnumerator',
299647ac 225 'Joystick',
299647ac 226 'OGL',
9c67cbec 227 'PrintFramework',
3628e088 228 'ShapedWindow',
78862f24 229 'Sound',
62038e59 230 'StandardPaths',
9c67cbec 231 'Unicode',
9c67cbec
RD
232 ]),
233
234 # need libs not coming with the demo
d7403ad2 235 ('Samples using an external library', [
299647ac 236 'GLCanvas',
9c67cbec
RD
237 ]),
238
70a357c2 239
9c67cbec
RD
240 ('Check out the samples dir too', [
241 ]),
242
9c67cbec
RD
243]
244
245
cf694132
RD
246
247#---------------------------------------------------------------------------
8b9a4190 248# Show how to derive a custom wxLog class
cf694132 249
1fded56b 250class MyLog(wx.PyLog):
76bfdc78 251 def __init__(self, textCtrl, logTime=0):
1fded56b 252 wx.PyLog.__init__(self)
76bfdc78
RD
253 self.tc = textCtrl
254 self.logTime = logTime
255
256 def DoLogString(self, message, timeStamp):
62038e59
RD
257 #print message, timeStamp
258 #if self.logTime:
259 # message = time.strftime("%X", time.localtime(timeStamp)) + \
260 # ": " + message
1e4a197e
RD
261 if self.tc:
262 self.tc.AppendText(message + '\n')
76bfdc78
RD
263
264
1fded56b 265class MyTP(wx.PyTipProvider):
861a0196
RD
266 def GetTip(self):
267 return "This is my tip"
268
c4ef95da
RD
269#---------------------------------------------------------------------------
270# A class to be used to simply display a message in the demo pane
271# rather than running the sample itself.
272
273class MessagePanel(wx.Panel):
274 def __init__(self, parent, message, caption='', flags=0):
275 wx.Panel.__init__(self, parent)
276
277 # Make widgets
278 if flags:
279 artid = None
280 if flags & wx.ICON_EXCLAMATION:
281 artid = wx.ART_WARNING
282 elif flags & wx.ICON_ERROR:
283 artid = wx.ART_ERROR
284 elif flags & wx.ICON_QUESTION:
285 artid = wx.ART_QUESTION
286 elif flags & wx.ICON_INFORMATION:
287 artid = wx.ART_INFORMATION
288
289 if artid is not None:
290 bmp = wx.ArtProvider.GetBitmap(artid, wx.ART_MESSAGE_BOX, (32,32))
291 icon = wx.StaticBitmap(self, -1, bmp)
292 else:
293 icon = (32,32) # make a spacer instead
294
295 if caption:
296 caption = wx.StaticText(self, -1, caption)
297 caption.SetFont(wx.Font(28, wx.SWISS, wx.NORMAL, wx.BOLD))
298
299 message = wx.StaticText(self, -1, message)
300
301 # add to sizers for layout
302 tbox = wx.BoxSizer(wx.VERTICAL)
303 if caption:
304 tbox.Add(caption)
305 tbox.Add((10,10))
306 tbox.Add(message)
307
308 hbox = wx.BoxSizer(wx.HORIZONTAL)
309 hbox.Add((10,10), 1)
310 hbox.Add(icon)
311 hbox.Add((10,10))
312 hbox.Add(tbox)
313 hbox.Add((10,10), 1)
314
315 box = wx.BoxSizer(wx.VERTICAL)
316 box.Add((10,10), 1)
317 box.Add(hbox, 0, wx.EXPAND)
318 box.Add((10,10), 2)
319
320 self.SetSizer(box)
321
322
08ecc920 323
1fded56b
RD
324#---------------------------------------------------------------------------
325# A class to be used to display source code in the demo. Try using the
299647ac 326# wxSTC in the StyledTextCtrl_2 sample first, fall back to wxTextCtrl
1fded56b 327# if there is an error, such as the stc module not being present.
8b9a4190 328#
1fded56b
RD
329
330try:
4638eaea 331 ##raise ImportError # for testing the alternate implementation
1fded56b 332 from wx import stc
299647ac 333 from StyledTextCtrl_2 import PythonSTC
08ecc920
RD
334
335 class DemoCodeEditor(PythonSTC):
336 def __init__(self, parent):
2e839e96 337 PythonSTC.__init__(self, parent, -1, style=wx.BORDER_NONE)
80b27b4e 338 self.SetUpEditor()
1fded56b
RD
339
340 # Some methods to make it compatible with how the wxTextCtrl is used
341 def SetValue(self, value):
6c7d1792
RD
342 if wx.USE_UNICODE:
343 value = value.decode('iso8859_1')
1fded56b 344 self.SetText(value)
08ecc920
RD
345 self.EmptyUndoBuffer()
346 self.SetSavePoint()
347
348 def IsModified(self):
349 return self.GetModify()
1fded56b
RD
350
351 def Clear(self):
352 self.ClearAll()
353
354 def SetInsertionPoint(self, pos):
355 self.SetCurrentPos(pos)
08ecc920 356 self.SetAnchor(pos)
1fded56b
RD
357
358 def ShowPosition(self, pos):
08ecc920 359 line = self.LineFromPosition(pos)
887a1bd9
RD
360 #self.EnsureVisible(line)
361 self.GotoLine(line)
1fded56b
RD
362
363 def GetLastPosition(self):
364 return self.GetLength()
365
08ecc920
RD
366 def GetPositionFromLine(self, line):
367 return self.PositionFromLine(line)
368
1fded56b
RD
369 def GetRange(self, start, end):
370 return self.GetTextRange(start, end)
371
372 def GetSelection(self):
373 return self.GetAnchor(), self.GetCurrentPos()
374
375 def SetSelection(self, start, end):
376 self.SetSelectionStart(start)
377 self.SetSelectionEnd(end)
378
08ecc920
RD
379 def SelectLine(self, line):
380 start = self.PositionFromLine(line)
381 end = self.GetLineEndPosition(line)
382 self.SetSelection(start, end)
383
80b27b4e
RD
384 def SetUpEditor(self):
385 """
386 This method carries out the work of setting up the demo editor.
387 It's seperate so as not to clutter up the init code.
388 """
389 import keyword
390
391 self.SetLexer(stc.STC_LEX_PYTHON)
392 self.SetKeyWords(0, " ".join(keyword.kwlist))
393
394 # Enable folding
395 self.SetProperty("fold", "1" )
396
397 # Highlight tab/space mixing (shouldn't be any)
398 self.SetProperty("tab.timmy.whinge.level", "1")
399
400 # Set left and right margins
401 self.SetMargins(2,2)
402
403 # Set up the numbers in the margin for margin #1
404 self.SetMarginType(1, wx.stc.STC_MARGIN_NUMBER)
405 # Reasonable value for, say, 4-5 digits using a mono font (40 pix)
406 self.SetMarginWidth(1, 40)
407
408 # Indentation and tab stuff
409 self.SetIndent(4) # Proscribed indent size for wx
410 self.SetIndentationGuides(True) # Show indent guides
411 self.SetBackSpaceUnIndents(True)# Backspace unindents rather than delete 1 space
412 self.SetTabIndents(True) # Tab key indents
413 self.SetTabWidth(4) # Proscribed tab size for wx
414 self.SetUseTabs(False) # Use spaces rather than tabs, or
415 # TabTimmy will complain!
416 # White space
417 self.SetViewWhiteSpace(False) # Don't view white space
418
887a1bd9
RD
419 # EOL: Since we are loading/saving ourselves, and the
420 # strings will always have \n's in them, set the STC to
421 # edit them that way.
422 self.SetEOLMode(wx.stc.STC_EOL_LF)
423 self.SetViewEOL(False)
424
80b27b4e
RD
425 # No right-edge mode indicator
426 self.SetEdgeMode(stc.STC_EDGE_NONE)
427
428 # Setup a margin to hold fold markers
429 self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
430 self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
431 self.SetMarginSensitive(2, True)
432 self.SetMarginWidth(2, 12)
433
434 # and now set up the fold markers
435 self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "black")
436 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "black")
437 self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "black")
438 self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "black")
439 self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "black")
440 self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "black")
441 self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "black")
442
443 # Global default style
444 if wx.Platform == '__WXMSW__':
445 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
446 'fore:#000000,back:#FFFFFF,face:Courier New,size:9')
447 else:
448 self.StyleSetSpec(stc.STC_STYLE_DEFAULT,
449 'fore:#000000,back:#FFFFFF,face:Courier,size:12')
450
451 # Clear styles and revert to default.
452 self.StyleClearAll()
453
454 # Following style specs only indicate differences from default.
455 # The rest remains unchanged.
456
457 # Line numbers in margin
e44b5196 458 self.StyleSetSpec(wx.stc.STC_STYLE_LINENUMBER,'fore:#000000,back:#99A9C2')
80b27b4e
RD
459 # Highlighted brace
460 self.StyleSetSpec(wx.stc.STC_STYLE_BRACELIGHT,'fore:#00009D,back:#FFFF00')
461 # Unmatched brace
462 self.StyleSetSpec(wx.stc.STC_STYLE_BRACEBAD,'fore:#00009D,back:#FF0000')
463 # Indentation guide
464 self.StyleSetSpec(wx.stc.STC_STYLE_INDENTGUIDE, "fore:#CDCDCD")
465
466 # Python styles
467 self.StyleSetSpec(wx.stc.STC_P_DEFAULT, 'fore:#000000')
468 # Comments
a253aa20
RD
469 self.StyleSetSpec(wx.stc.STC_P_COMMENTLINE, 'fore:#008000,back:#F0FFF0')
470 self.StyleSetSpec(wx.stc.STC_P_COMMENTBLOCK, 'fore:#008000,back:#F0FFF0')
80b27b4e
RD
471 # Numbers
472 self.StyleSetSpec(wx.stc.STC_P_NUMBER, 'fore:#008080')
473 # Strings and characters
474 self.StyleSetSpec(wx.stc.STC_P_STRING, 'fore:#800080')
475 self.StyleSetSpec(wx.stc.STC_P_CHARACTER, 'fore:#800080')
476 # Keywords
477 self.StyleSetSpec(wx.stc.STC_P_WORD, 'fore:#000080,bold')
478 # Triple quotes
479 self.StyleSetSpec(wx.stc.STC_P_TRIPLE, 'fore:#800080,back:#FFFFEA')
480 self.StyleSetSpec(wx.stc.STC_P_TRIPLEDOUBLE, 'fore:#800080,back:#FFFFEA')
481 # Class names
482 self.StyleSetSpec(wx.stc.STC_P_CLASSNAME, 'fore:#0000FF,bold')
483 # Function names
484 self.StyleSetSpec(wx.stc.STC_P_DEFNAME, 'fore:#008080,bold')
485 # Operators
486 self.StyleSetSpec(wx.stc.STC_P_OPERATOR, 'fore:#800000,bold')
487 # Identifiers. I leave this as not bold because everything seems
488 # to be an identifier if it doesn't match the above criterae
489 self.StyleSetSpec(wx.stc.STC_P_IDENTIFIER, 'fore:#000000')
490
491 # Caret color
492 self.SetCaretForeground("BLUE")
493 # Selection background
494 self.SetSelBackground(1, '#66CCFF')
495
496 self.SetSelBackground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT))
497 self.SetSelForeground(True, wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT))
498
08ecc920
RD
499 def RegisterModifiedEvent(self, eventHandler):
500 self.Bind(wx.stc.EVT_STC_CHANGE, eventHandler)
501
1fded56b
RD
502
503except ImportError:
08ecc920
RD
504 class DemoCodeEditor(wx.TextCtrl):
505 def __init__(self, parent):
2e839e96
RD
506 wx.TextCtrl.__init__(self, parent, -1, style =
507 wx.TE_MULTILINE | wx.HSCROLL | wx.TE_RICH2 | wx.TE_NOHIDESEL)
1fded56b 508
08ecc920
RD
509 def RegisterModifiedEvent(self, eventHandler):
510 self.Bind(wx.EVT_TEXT, eventHandler)
511
512 def SetReadOnly(self, flag):
513 self.SetEditable(not flag)
514 # NOTE: STC already has this method
515
516 def GetText(self):
517 return self.GetValue()
518
b6176ab7 519 def GetPositionFromLine(self, line):
08ecc920
RD
520 return self.XYToPosition(0,line)
521
522 def GotoLine(self, line):
b6176ab7
RD
523 pos = self.GetPositionFromLine(line)
524 self.SetInsertionPoint(pos)
525 self.ShowPosition(pos)
08ecc920
RD
526
527 def SelectLine(self, line):
528 start = self.GetPositionFromLine(line)
529 end = start + self.GetLineLength(line)
530 self.SetSelection(start, end)
531
532
533#---------------------------------------------------------------------------
534# Constants for module versions
535
536modOriginal = 0
537modModified = 1
538modDefault = modOriginal
539
540#---------------------------------------------------------------------------
541
542class DemoCodePanel(wx.Panel):
543 """Panel for the 'Demo Code' tab"""
544 def __init__(self, parent, mainFrame):
2e839e96 545 wx.Panel.__init__(self, parent, size=(1,1))
e0465cbd
RD
546 if 'wxMSW' in wx.PlatformInfo:
547 self.Hide()
08ecc920
RD
548 self.mainFrame = mainFrame
549 self.editor = DemoCodeEditor(self)
550 self.editor.RegisterModifiedEvent(self.OnCodeModified)
551
552 self.btnSave = wx.Button(self, -1, "Save Changes")
553 self.btnRestore = wx.Button(self, -1, "Delete Modified")
554 self.btnSave.Enable(False)
555 self.btnSave.Bind(wx.EVT_BUTTON, self.OnSave)
556 self.btnRestore.Bind(wx.EVT_BUTTON, self.OnRestore)
557
558 self.radioButtons = { modOriginal: wx.RadioButton(self, -1, "Original", style = wx.RB_GROUP),
559 modModified: wx.RadioButton(self, -1, "Modified") }
560
561 self.controlBox = wx.BoxSizer(wx.HORIZONTAL)
562 self.controlBox.Add(wx.StaticText(self, -1, "Active Version:"), 0,
563 wx.RIGHT | wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 5)
564 for modID, radioButton in self.radioButtons.items():
565 self.controlBox.Add(radioButton, 0, wx.EXPAND | wx.RIGHT, 5)
566 radioButton.modID = modID # makes it easier for the event handler
567 radioButton.Bind(wx.EVT_RADIOBUTTON, self.OnRadioButton)
568
569 self.controlBox.Add(self.btnSave, 0, wx.RIGHT, 5)
570 self.controlBox.Add(self.btnRestore, 0)
571
572 self.box = wx.BoxSizer(wx.VERTICAL)
573 self.box.Add(self.controlBox, 0, wx.EXPAND)
887a1bd9 574 self.box.Add(wx.StaticLine(self), 0, wx.EXPAND)
08ecc920
RD
575 self.box.Add(self.editor, 1, wx.EXPAND)
576
577 self.box.Fit(self)
578 self.SetSizer(self.box)
579
580
581 # Loads a demo from a DemoModules object
582 def LoadDemo(self, demoModules):
583 self.demoModules = demoModules
584 if (modDefault == modModified) and demoModules.Exists(modModified):
585 demoModules.SetActive(modModified)
586 else:
587 demoModules.SetActive(modOriginal)
588 self.radioButtons[demoModules.GetActiveID()].Enable(True)
589 self.ActiveModuleChanged()
590
591
592 def ActiveModuleChanged(self):
593 self.LoadDemoSource(self.demoModules.GetSource())
594 self.UpdateControlState()
a6780fa2 595 self.ReloadDemo()
08ecc920
RD
596
597
598 def LoadDemoSource(self, source):
599 self.editor.Clear()
600 self.editor.SetValue(source)
601 self.JumpToLine(0)
602 self.btnSave.Enable(False)
603
604
605 def JumpToLine(self, line, highlight=False):
606 self.editor.GotoLine(line)
607 self.editor.SetFocus()
608 if highlight:
609 self.editor.SelectLine(line)
610
611
612 def UpdateControlState(self):
613 active = self.demoModules.GetActiveID()
614 # Update the radio/restore buttons
615 for moduleID in self.radioButtons:
616 btn = self.radioButtons[moduleID]
617 if moduleID == active:
618 btn.SetValue(True)
619 else:
620 btn.SetValue(False)
621
622 if self.demoModules.Exists(moduleID):
623 btn.Enable(True)
624 if moduleID == modModified:
625 self.btnRestore.Enable(True)
626 else:
627 btn.Enable(False)
628 if moduleID == modModified:
629 self.btnRestore.Enable(False)
630
631
632 def OnRadioButton(self, event):
633 radioSelected = event.GetEventObject()
634 modSelected = radioSelected.modID
635 if modSelected != self.demoModules.GetActiveID():
636 busy = wx.BusyInfo("Reloading demo module...")
637 self.demoModules.SetActive(modSelected)
638 self.ActiveModuleChanged()
639
640
641 def ReloadDemo(self):
642 if self.demoModules.name != __name__:
a6780fa2 643 self.mainFrame.RunModule()
08ecc920
RD
644
645
646 def OnCodeModified(self, event):
647 self.btnSave.Enable(self.editor.IsModified())
648
649
650 def OnSave(self, event):
651 if self.demoModules.Exists(modModified):
652 if self.demoModules.GetActiveID() == modOriginal:
653 overwriteMsg = "You are about to overwrite an already existing modified copy\n" + \
654 "Do you want to continue?"
655 dlg = wx.MessageDialog(self, overwriteMsg, "wxPython Demo",
656 wx.YES_NO | wx.NO_DEFAULT| wx.ICON_EXCLAMATION)
657 result = dlg.ShowModal()
658 if result == wx.ID_NO:
659 return
660 dlg.Destroy()
661
662 self.demoModules.SetActive(modModified)
663 modifiedFilename = GetModifiedFilename(self.demoModules.name)
664
665 # Create the demo directory if one doesn't already exist
666 if not os.path.exists(GetModifiedDirectory()):
667 try:
668 os.makedirs(GetModifiedDirectory())
669 if not os.path.exists(GetModifiedDirectory()):
670 wx.LogMessage("BUG: Created demo directory but it still doesn't exit")
671 raise AssetionError
672 except:
673 wx.LogMessage("Error creating demo directory: %s" % GetModifiedDirectory())
674 return
675 else:
676 wx.LogMessage("Created directory for modified demos: %s" % GetModifiedDirectory())
677
678 # Save
887a1bd9 679 f = open(modifiedFilename, "wt")
08ecc920
RD
680 source = self.editor.GetText()
681 try:
682 f.write(source)
683 finally:
684 f.close()
685
686 busy = wx.BusyInfo("Reloading demo module...")
687 self.demoModules.LoadFromFile(modModified, modifiedFilename)
688 self.ActiveModuleChanged()
689
690
691 def OnRestore(self, event): # Handles the "Delete Modified" button
692 modifiedFilename = GetModifiedFilename(self.demoModules.name)
693 self.demoModules.Delete(modModified)
694 os.unlink(modifiedFilename) # Delete the modified copy
695 busy = wx.BusyInfo("Reloading demo module...")
696 self.ActiveModuleChanged()
697
1fded56b 698
6c5ae2d2
RD
699#---------------------------------------------------------------------------
700
701def opj(path):
702 """Convert paths to the platform-specific separator"""
08ecc920
RD
703 str = apply(os.path.join, tuple(path.split('/')))
704 # HACK: on Linux, a leading / gets lost...
705 if path.startswith('/'):
706 str = '/' + str
707 return str
708
709
710def GetModifiedDirectory():
711 """
712 Returns the directory where modified versions of the demo files
713 are stored
714 """
715 return opj(wx.GetHomeDir() + "/.wxPyDemo/modified/")
716
717
718def GetModifiedFilename(name):
719 """
720 Returns the filename of the modified version of the specified demo
721 """
722 if not name.endswith(".py"):
723 name = name + ".py"
724 return GetModifiedDirectory() + name
725
726
727def GetOriginalFilename(name):
728 """
729 Returns the filename of the original version of the specified demo
730 """
731 if not name.endswith(".py"):
732 name = name + ".py"
733 return name
734
735
736def DoesModifiedExist(name):
737 """Returns whether the specified demo has a modified copy"""
738 if os.path.exists(GetModifiedFilename(name)):
739 return True
740 else:
741 return False
742
743
744#---------------------------------------------------------------------------
745
746class ModuleDictWrapper:
747 """Emulates a module with a dynamically compiled __dict__"""
748 def __init__(self, dict):
749 self.dict = dict
750
751 def __getattr__(self, name):
752 if name in self.dict:
753 return self.dict[name]
754 else:
755 raise AttributeError
756
757class DemoModules:
758 """
759 Dynamically manages the original/modified versions of a demo
760 module
761 """
762 def __init__(self, name):
763 self.modActive = -1
764 self.name = name
765
766 # (dict , source , filename , description , error information )
767 # ( 0 , 1 , 2 , 3 , 4 )
768 self.modules = [[None, "" , "" , "<original>" , None],
769 [None, "" , "" , "<modified>" , None]]
770
771 # load original module
772 self.LoadFromFile(modOriginal, GetOriginalFilename(name))
317a64e5 773 self.SetActive(modOriginal)
08ecc920
RD
774
775 # load modified module (if one exists)
776 if DoesModifiedExist(name):
777 self.LoadFromFile(modModified, GetModifiedFilename(name))
778
779
780 def LoadFromFile(self, modID, filename):
781 self.modules[modID][2] = filename
887a1bd9 782 file = open(filename, "rt")
08ecc920
RD
783 self.LoadFromSource(modID, file.read())
784 file.close()
785
786
787 def LoadFromSource(self, modID, source):
788 self.modules[modID][1] = source
789 self.LoadDict(modID)
790
791
792 def LoadDict(self, modID):
793 if self.name != __name__:
794 source = self.modules[modID][1]
795 description = self.modules[modID][3]
796
797 try:
798 self.modules[modID][0] = {}
799 code = compile(source, description, "exec")
800 exec code in self.modules[modID][0]
801 except:
802 self.modules[modID][4] = DemoError(sys.exc_info())
803 self.modules[modID][0] = None
804 else:
805 self.modules[modID][4] = None
806
807
808 def SetActive(self, modID):
809 if modID != modOriginal and modID != modModified:
810 raise LookupError
811 else:
812 self.modActive = modID
813
814
815 def GetActive(self):
816 dict = self.modules[self.modActive][0]
817 if dict is None:
818 return None
819 else:
820 return ModuleDictWrapper(dict)
821
822
823 def GetActiveID(self):
824 return self.modActive
825
826
827 def GetSource(self, modID = None):
828 if modID is None:
829 modID = self.modActive
830 return self.modules[modID][1]
831
832
833 def GetFilename(self, modID = None):
834 if modID is None:
835 modID = self.modActive
836 return self.modules[self.modActive][2]
837
838
839 def GetErrorInfo(self, modID = None):
840 if modID is None:
841 modID = self.modActive
842 return self.modules[self.modActive][4]
843
844
845 def Exists(self, modID):
846 return self.modules[modID][1] != ""
847
848
849 def UpdateFile(self, modID = None):
850 """Updates the file from which a module was loaded
851 with (possibly updated) source"""
852 if modID is None:
853 modID = self.modActive
854
855 source = self.modules[modID][1]
856 filename = self.modules[modID][2]
857
858 try:
887a1bd9 859 file = open(filename, "wt")
08ecc920
RD
860 file.write(source)
861 finally:
862 file.close()
863
864
865 def Delete(self, modID):
866 if self.modActive == modID:
867 self.SetActive(0)
868
869 self.modules[modID][0] = None
870 self.modules[modID][1] = ""
871 self.modules[modID][2] = ""
872
873
08ecc920
RD
874#---------------------------------------------------------------------------
875
876class DemoError:
877 """Wraps and stores information about the current exception"""
878 def __init__(self, exc_info):
879 import copy
880
881 excType, excValue = exc_info[:2]
882 # traceback list entries: (filename, line number, function name, text)
883 self.traceback = traceback.extract_tb(exc_info[2])
884
885 # --Based on traceback.py::format_exception_only()--
886 if type(excType) == types.ClassType:
887 self.exception_type = excType.__name__
888 else:
889 self.exception_type = excType
890
891 # If it's a syntax error, extra information needs
892 # to be added to the traceback
893 if excType is SyntaxError:
894 try:
895 msg, (filename, lineno, self.offset, line) = excValue
896 except:
897 pass
898 else:
899 if not filename:
900 filename = "<string>"
901 line = line.strip()
902 self.traceback.append( (filename, lineno, "", line) )
903 excValue = msg
904 try:
905 self.exception_details = str(excValue)
906 except:
907 self.exception_details = "<unprintable %s object>" & type(excValue).__name__
908
909 del exc_info
910
911 def __str__(self):
912 ret = "Type %s \n \
913 Traceback: %s \n \
914 Details : %s" % ( str(self.exception_type), str(self.traceback), self.exception_details )
915 return ret
916
917#---------------------------------------------------------------------------
918
919class DemoErrorPanel(wx.Panel):
920 """Panel put into the demo tab when the demo fails to run due to errors"""
921
922 def __init__(self, parent, codePanel, demoError, log):
923 wx.Panel.__init__(self, parent, -1)#, style=wx.NO_FULL_REPAINT_ON_RESIZE)
924 self.codePanel = codePanel
925 self.nb = parent
926 self.log = log
927
928 self.box = wx.BoxSizer(wx.VERTICAL)
929
930 # Main Label
931 self.box.Add(wx.StaticText(self, -1, "An error has occured while trying to run the demo")
932 , 0, wx.ALIGN_CENTER | wx.TOP, 10)
933
934 # Exception Information
935 boxInfo = wx.StaticBox(self, -1, "Exception Info" )
936 boxInfoSizer = wx.StaticBoxSizer(boxInfo, wx.VERTICAL ) # Used to center the grid within the box
937 boxInfoGrid = wx.FlexGridSizer(0, 2, 0, 0)
938 textFlags = wx.ALIGN_RIGHT | wx.LEFT | wx.RIGHT | wx.TOP
939 boxInfoGrid.Add(wx.StaticText(self, -1, "Type: "), 0, textFlags, 5 )
940 boxInfoGrid.Add(wx.StaticText(self, -1, demoError.exception_type) , 0, textFlags, 5 )
941 boxInfoGrid.Add(wx.StaticText(self, -1, "Details: ") , 0, textFlags, 5 )
942 boxInfoGrid.Add(wx.StaticText(self, -1, demoError.exception_details) , 0, textFlags, 5 )
943 boxInfoSizer.Add(boxInfoGrid, 0, wx.ALIGN_CENTRE | wx.ALL, 5 )
944 self.box.Add(boxInfoSizer, 0, wx.ALIGN_CENTER | wx.ALL, 5)
945
946 # Set up the traceback list
947 # This one automatically resizes last column to take up remaining space
948 from ListCtrl import TestListCtrl
949 self.list = TestListCtrl(self, -1, style=wx.LC_REPORT | wx.SUNKEN_BORDER)
950 self.list.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
951 self.list.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
952 self.list.InsertColumn(0, "Filename")
953 self.list.InsertColumn(1, "Line", wx.LIST_FORMAT_RIGHT)
954 self.list.InsertColumn(2, "Function")
955 self.list.InsertColumn(3, "Code")
956 self.InsertTraceback(self.list, demoError.traceback)
957 self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
958 self.list.SetColumnWidth(2, wx.LIST_AUTOSIZE)
959 self.box.Add(wx.StaticText(self, -1, "Traceback:")
960 , 0, wx.ALIGN_CENTER | wx.TOP, 5)
961 self.box.Add(self.list, 1, wx.GROW | wx.ALIGN_CENTER | wx.ALL, 5)
962 self.box.Add(wx.StaticText(self, -1, "Entries from the demo module are shown in blue\n"
963 + "Double-click on them to go to the offending line")
964 , 0, wx.ALIGN_CENTER | wx.BOTTOM, 5)
965
966 self.box.Fit(self)
967 self.SetSizer(self.box)
968
969
970 def InsertTraceback(self, list, traceback):
971 #Add the traceback data
972 for x in range(len(traceback)):
973 data = traceback[x]
974 list.InsertStringItem(x, os.path.basename(data[0])) # Filename
975 list.SetStringItem(x, 1, str(data[1])) # Line
976 list.SetStringItem(x, 2, str(data[2])) # Function
977 list.SetStringItem(x, 3, str(data[3])) # Code
978
979 # Check whether this entry is from the demo module
980 if data[0] == "<original>" or data[0] == "<modified>": # FIXME: make more generalised
981 self.list.SetItemData(x, int(data[1])) # Store line number for easy access
982 # Give it a blue colour
983 item = self.list.GetItem(x)
984 item.SetTextColour(wx.BLUE)
985 self.list.SetItem(item)
986 else:
987 self.list.SetItemData(x, -1) # Editor can't jump into this one's code
988
989
990 def OnItemSelected(self, event):
991 # This occurs before OnDoubleClick and can be used to set the
992 # currentItem. OnDoubleClick doesn't get a wxListEvent....
993 self.currentItem = event.m_itemIndex
994 event.Skip()
995
996
997 def OnDoubleClick(self, event):
998 # If double-clicking on a demo's entry, jump to the line number
999 line = self.list.GetItemData(self.currentItem)
1000 if line != -1:
1001 self.nb.SetSelection(1) # Switch to the code viewer tab
1002 wx.CallAfter(self.codePanel.JumpToLine, line-1, True)
1003 event.Skip()
1004
6c5ae2d2 1005
76bfdc78
RD
1006#---------------------------------------------------------------------------
1007
1cc18e15
RD
1008class DemoTaskBarIcon(wx.TaskBarIcon):
1009 TBMENU_RESTORE = wx.NewId()
1010 TBMENU_CLOSE = wx.NewId()
1011 TBMENU_CHANGE = wx.NewId()
1012 TBMENU_REMOVE = wx.NewId()
1013
1014 def __init__(self, frame):
1015 wx.TaskBarIcon.__init__(self)
1016 self.frame = frame
1017
1018 # Set the image
8596dc0b 1019 icon = self.MakeIcon(images.getWXPdemoImage())
1cc18e15 1020 self.SetIcon(icon, "wxPython Demo")
8596dc0b 1021 self.imgidx = 1
1cc18e15
RD
1022
1023 # bind some events
1024 self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTaskBarActivate)
1025 self.Bind(wx.EVT_MENU, self.OnTaskBarActivate, id=self.TBMENU_RESTORE)
1026 self.Bind(wx.EVT_MENU, self.OnTaskBarClose, id=self.TBMENU_CLOSE)
1027 self.Bind(wx.EVT_MENU, self.OnTaskBarChange, id=self.TBMENU_CHANGE)
1028 self.Bind(wx.EVT_MENU, self.OnTaskBarRemove, id=self.TBMENU_REMOVE)
1029
1030
1031 def CreatePopupMenu(self):
1032 """
1033 This method is called by the base class when it needs to popup
1034 the menu for the default EVT_RIGHT_DOWN event. Just create
1035 the menu how you want it and return it from this function,
1036 the base class takes care of the rest.
1037 """
1038 menu = wx.Menu()
1039 menu.Append(self.TBMENU_RESTORE, "Restore wxPython Demo")
1040 menu.Append(self.TBMENU_CLOSE, "Close wxPython Demo")
1041 menu.AppendSeparator()
1042 menu.Append(self.TBMENU_CHANGE, "Change the TB Icon")
1043 menu.Append(self.TBMENU_REMOVE, "Remove the TB Icon")
1044 return menu
1045
1046
1047 def MakeIcon(self, img):
1048 """
1049 The various platforms have different requirements for the
1050 icon size...
1051 """
1052 if "wxMSW" in wx.PlatformInfo:
8596dc0b 1053 img = img.Scale(16, 16)
1cc18e15 1054 elif "wxGTK" in wx.PlatformInfo:
8596dc0b
RD
1055 img = img.Scale(22, 22)
1056 # wxMac can be any size upto 128x128, so leave the source img alone....
1cc18e15
RD
1057 icon = wx.IconFromBitmap(img.ConvertToBitmap() )
1058 return icon
1059
1060
1061 def OnTaskBarActivate(self, evt):
1062 if self.frame.IsIconized():
1063 self.frame.Iconize(False)
1064 if not self.frame.IsShown():
1065 self.frame.Show(True)
1066 self.frame.Raise()
1067
1068
1069 def OnTaskBarClose(self, evt):
1070 self.frame.Close()
1071
1cc18e15
RD
1072
1073 def OnTaskBarChange(self, evt):
8596dc0b
RD
1074 names = [ "WXPdemo", "WXP", "Mondrian", "Test2m",
1075 "Blom08m", "Blom10m", "Blom15m" ]
1076 name = names[self.imgidx]
1077
1078 getFunc = getattr(images, "get%sImage" % name)
1079 self.imgidx += 1
1080 if self.imgidx >= len(names):
1081 self.imgidx = 0
1082
1083 icon = self.MakeIcon(getFunc())
1084 self.SetIcon(icon, "This is a new icon: " + name)
1cc18e15
RD
1085
1086
1087 def OnTaskBarRemove(self, evt):
1088 self.RemoveIcon()
1089
1090
1091#---------------------------------------------------------------------------
1fded56b 1092class wxPythonDemo(wx.Frame):
e9159fe8 1093 overviewText = "wxPython Overview"
c368d904 1094
08ecc920 1095 def __init__(self, parent, title):
8ee202f3 1096 wx.Frame.__init__(self, parent, -1, title, size = (950, 720),
08ecc920 1097 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
2f90df85 1098
f290d1c7
RD
1099 self.SetMinSize((640,480))
1100
08ecc920 1101 self.loaded = False
694759cf 1102 self.cwd = os.getcwd()
3ca6a5f0 1103 self.curOverview = ""
08ecc920
RD
1104 self.demoPage = None
1105 self.codePage = None
887a1bd9 1106 self.shell = None
a6780fa2 1107 self.firstTime = True
694759cf 1108
8596dc0b 1109 icon = images.getWXPdemoIcon()
96bfd053 1110 self.SetIcon(icon)
c368d904 1111
1cc18e15 1112 self.tbicon = DemoTaskBarIcon(self)
c368d904 1113
1fded56b 1114 wx.CallAfter(self.ShowTip)
cf694132
RD
1115
1116 self.otherWin = None
80b27b4e
RD
1117 self.Bind(wx.EVT_IDLE, self.OnIdle)
1118 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
1119 self.Bind(wx.EVT_ICONIZE, self.OnIconfiy)
1120 self.Bind(wx.EVT_MAXIMIZE, self.OnMaximize)
cf694132 1121
1fded56b
RD
1122 self.Centre(wx.BOTH)
1123 self.CreateStatusBar(1, wx.ST_SIZEGRIP)
5a7823f5 1124
0c8f2860
RD
1125 splitter = wx.SplitterWindow(self, -1, style=wx.CLIP_CHILDREN | wx.SP_LIVE_UPDATE | wx.SP_3D)
1126 splitter2 = wx.SplitterWindow(splitter, -1, style=wx.CLIP_CHILDREN | wx.SP_LIVE_UPDATE | wx.SP_3D)
5a7823f5 1127
d56cebe7 1128 def EmptyHandler(evt): pass
80b27b4e
RD
1129 #splitter.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler)
1130 #splitter2.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler)
cf694132 1131
1e4a197e
RD
1132 # Prevent TreeCtrl from displaying all items after destruction when True
1133 self.dying = False
cf694132 1134
08ecc920
RD
1135 # Create a Notebook
1136 self.nb = wx.Notebook(splitter2, -1, style=wx.CLIP_CHILDREN)
1137
cf694132 1138 # Make a File menu
1fded56b
RD
1139 self.mainmenu = wx.MenuBar()
1140 menu = wx.Menu()
330af869
RD
1141 item = menu.Append(-1, '&Redirect Output',
1142 'Redirect print statements to a window',
1143 wx.ITEM_CHECK)
1144 self.Bind(wx.EVT_MENU, self.OnToggleRedirect, item)
08ecc920 1145
c8000995
RD
1146 item = menu.Append(-1, 'E&xit\tAlt-X', 'Get the heck outta here!')
1147 self.Bind(wx.EVT_MENU, self.OnFileExit, item)
05871e13 1148 wx.App.SetMacExitMenuItemId(item.GetId())
cf694132
RD
1149 self.mainmenu.Append(menu, '&File')
1150
ec3e670f 1151 # Make a Demo menu
1fded56b 1152 menu = wx.Menu()
ec3e670f 1153 for item in _treeList:
1fded56b 1154 submenu = wx.Menu()
ec3e670f 1155 for childItem in item[1]:
c8000995
RD
1156 mi = submenu.Append(-1, childItem)
1157 self.Bind(wx.EVT_MENU, self.OnDemoMenu, mi)
1fded56b 1158 menu.AppendMenu(wx.NewId(), item[0], submenu)
ec3e670f
RD
1159 self.mainmenu.Append(menu, '&Demo')
1160
08ecc920
RD
1161 # Make a Demo Code menu
1162 #TODO: Add new menu items
1163 # Like the option-enabled entries to select the
1164 # active module
1165 #TODO: should we bother?
1166
1167 #menu = wx.Menu()
1168 #saveID = wx.NewId()
1169 #restoreID = wx.NewId()
1170 #
1171 #menu.Append(saveID, '&Save\tCtrl-S', 'Save edited demo')
1172 #menu.Append(restoreID, '&Delete Modified\tCtrl-R', 'Delete modified copy')
1173 #self.Bind(wx.EVT_MENU, self.codePage.OnSave, id=saveID)
1174 #self.Bind(wx.EVT_MENU, self.codePage.OnRestore, id=restoreID)
1175 #self.mainmenu.Append(menu, 'Demo &Code')
1176 #
ec3e670f 1177
cf694132 1178 # Make a Help menu
1fded56b 1179 menu = wx.Menu()
c8000995
RD
1180 findItem = menu.Append(-1, '&Find\tCtrl-F', 'Find in the Demo Code')
1181 findnextItem = menu.Append(-1, 'Find &Next\tF3', 'Find Next')
1e4a197e 1182 menu.AppendSeparator()
887a1bd9
RD
1183
1184 shellItem = menu.Append(-1, 'Open Py&Shell Window\tF5',
1185 'An interactive interpreter window with the demo app and frame objects in the namesapce')
1186 menu.AppendSeparator()
c8000995 1187 helpItem = menu.Append(-1, '&About\tCtrl-H', 'wxPython RULES!!!')
05871e13 1188 wx.App.SetMacAboutMenuItemId(helpItem.GetId())
887a1bd9
RD
1189
1190 self.Bind(wx.EVT_MENU, self.OnOpenShellWindow, shellItem)
c8000995
RD
1191 self.Bind(wx.EVT_MENU, self.OnHelpAbout, helpItem)
1192 self.Bind(wx.EVT_MENU, self.OnHelpFind, findItem)
1193 self.Bind(wx.EVT_MENU, self.OnFindNext, findnextItem)
80b27b4e
RD
1194 self.Bind(wx.EVT_COMMAND_FIND, self.OnFind)
1195 self.Bind(wx.EVT_COMMAND_FIND_NEXT, self.OnFind)
1196 self.Bind(wx.EVT_COMMAND_FIND_CLOSE, self.OnFindClose)
cf694132
RD
1197 self.mainmenu.Append(menu, '&Help')
1198 self.SetMenuBar(self.mainmenu)
1199
1fded56b 1200 self.finddata = wx.FindReplaceData()
1e4a197e
RD
1201
1202 if 0:
1203 # This is another way to set Accelerators, in addition to
1204 # using the '\t<key>' syntax in the menu items.
1fded56b
RD
1205 aTable = wx.AcceleratorTable([(wx.ACCEL_ALT, ord('X'), exitID),
1206 (wx.ACCEL_CTRL, ord('H'), helpID),
1207 (wx.ACCEL_CTRL, ord('F'), findID),
1208 (wx.ACCEL_NORMAL, WXK_F3, findnextID)
1209 ])
1e4a197e 1210 self.SetAcceleratorTable(aTable)
2f90df85 1211
bb0054cd 1212
cf694132 1213 # Create a TreeCtrl
1fded56b 1214 tID = wx.NewId()
f6bcfd97 1215 self.treeMap = {}
3628e088
RD
1216 self.tree = wx.TreeCtrl(splitter, tID, style =
1217 wx.TR_DEFAULT_STYLE #| wx.TR_HAS_VARIABLE_ROW_HEIGHT
eb0f373c 1218 )
afb810d9 1219
e9159fe8 1220 root = self.tree.AddRoot("wxPython Overview")
f6bcfd97
BP
1221 firstChild = None
1222 for item in _treeList:
1223 child = self.tree.AppendItem(root, item[0])
1224 if not firstChild: firstChild = child
1225 for childItem in item[1]:
1226 theDemo = self.tree.AppendItem(child, childItem)
1227 self.treeMap[childItem] = theDemo
1228
1229 self.tree.Expand(root)
1230 self.tree.Expand(firstChild)
80b27b4e
RD
1231 self.tree.Bind(wx.EVT_TREE_ITEM_EXPANDED, self.OnItemExpanded, id=tID)
1232 self.tree.Bind(wx.EVT_TREE_ITEM_COLLAPSED, self.OnItemCollapsed, id=tID)
1233 self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged, id=tID)
1234 self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnTreeLeftDown)
cf694132 1235
1fded56b 1236 # Set up a wx.html.HtmlWindow on the Overview Notebook page
0bdca46d
RD
1237 # we put it in a panel first because there seems to be a
1238 # refresh bug of some sort (wxGTK) when it is directly in
1239 # the notebook...
1240 if 0: # the old way
1fded56b 1241 self.ovr = wx.html.HtmlWindow(self.nb, -1, size=(400, 400))
0bdca46d
RD
1242 self.nb.AddPage(self.ovr, self.overviewText)
1243
1fded56b
RD
1244 else: # hopefully I can remove this hacky code soon, see SF bug #216861
1245 panel = wx.Panel(self.nb, -1, style=wx.CLIP_CHILDREN)
1246 self.ovr = wx.html.HtmlWindow(panel, -1, size=(400, 400))
0bdca46d
RD
1247 self.nb.AddPage(panel, self.overviewText)
1248
1249 def OnOvrSize(evt, ovr=self.ovr):
1250 ovr.SetSize(evt.GetSize())
80b27b4e
RD
1251 panel.Bind(wx.EVT_SIZE, OnOvrSize)
1252 panel.Bind(wx.EVT_ERASE_BACKGROUND, EmptyHandler)
0bdca46d 1253
08ecc920 1254 if "gtk2" in wx.PlatformInfo:
385721a8 1255 self.ovr.SetStandardFonts()
08ecc920 1256 self.SetOverview(self.overviewText, mainOverview)
c368d904 1257
cf694132 1258
08ecc920
RD
1259 # Set up a log window
1260 self.log = wx.TextCtrl(splitter2, -1,
1261 style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
1262
1263 # Set the wxWindows log target to be this textctrl
1264 #wx.Log_SetActiveTarget(wx.LogTextCtrl(self.log))
cf694132 1265
08ecc920
RD
1266 # But instead of the above we want to show how to use our own wx.Log class
1267 wx.Log_SetActiveTarget(MyLog(self.log))
1268
1269 # for serious debugging
1270 #wx.Log_SetActiveTarget(wx.LogStderr())
1271 #wx.Log_SetTraceMask(wx.TraceMessages)
cf694132
RD
1272
1273
e44b5196
RD
1274 self.Bind(wx.EVT_ACTIVATE, self.OnActivate)
1275 wx.GetApp().Bind(wx.EVT_ACTIVATE_APP, self.OnAppActivate)
1276
f6bcfd97 1277 # add the windows to the splitter and split it.
08ecc920
RD
1278 splitter2.SplitHorizontally(self.nb, self.log, -160)
1279 splitter.SplitVertically(self.tree, splitter2, 200)
afb810d9 1280
f290d1c7
RD
1281 splitter.SetMinimumPaneSize(120)
1282 splitter2.SetMinimumPaneSize(60)
afb810d9 1283
11926a78 1284 # Make the splitter on the right expand the top window when resized
2f0f3b0f
RD
1285 def SplitterOnSize(evt):
1286 splitter = evt.GetEventObject()
1287 sz = splitter.GetSize()
08ecc920 1288 splitter.SetSashPosition(sz.height - 160, False)
2f0f3b0f 1289 evt.Skip()
80b27b4e
RD
1290
1291 splitter2.Bind(wx.EVT_SIZE, SplitterOnSize)
2f0f3b0f 1292
bb0054cd
RD
1293 # select initial items
1294 self.nb.SetSelection(0)
f6bcfd97 1295 self.tree.SelectItem(root)
ec3e670f 1296
08ecc920
RD
1297 # Load 'Main' module
1298 self.LoadDemo(self.overviewText)
1299 self.loaded = True
1300
1301 # select some other initial module?
1302 if len(sys.argv) > 1:
1303 arg = sys.argv[1]
1304 if arg.endswith('.py'):
1305 arg = arg[:-3]
1306 selectedDemo = self.treeMap.get(arg, None)
f6bcfd97 1307 if selectedDemo:
ec3e670f
RD
1308 self.tree.SelectItem(selectedDemo)
1309 self.tree.EnsureVisible(selectedDemo)
1310
bb0054cd 1311
cf694132
RD
1312 #---------------------------------------------
1313 def WriteText(self, text):
f6bcfd97
BP
1314 if text[-1:] == '\n':
1315 text = text[:-1]
1fded56b 1316 wx.LogMessage(text)
f6bcfd97 1317
cf694132
RD
1318 def write(self, txt):
1319 self.WriteText(txt)
1320
1321 #---------------------------------------------
1322 def OnItemExpanded(self, event):
1323 item = event.GetItem()
1fded56b 1324 wx.LogMessage("OnItemExpanded: %s" % self.tree.GetItemText(item))
c368d904 1325 event.Skip()
cf694132
RD
1326
1327 #---------------------------------------------
1328 def OnItemCollapsed(self, event):
1329 item = event.GetItem()
1fded56b 1330 wx.LogMessage("OnItemCollapsed: %s" % self.tree.GetItemText(item))
c368d904 1331 event.Skip()
f6bcfd97
BP
1332
1333 #---------------------------------------------
1334 def OnTreeLeftDown(self, event):
08ecc920 1335 # reset the overview text if the tree item is clicked on again
f6bcfd97
BP
1336 pt = event.GetPosition();
1337 item, flags = self.tree.HitTest(pt)
1338 if item == self.tree.GetSelection():
d975da9b 1339 self.SetOverview(self.tree.GetItemText(item)+" Overview", self.curOverview)
185d7c3e 1340 event.Skip()
cf694132
RD
1341
1342 #---------------------------------------------
1343 def OnSelChanged(self, event):
08ecc920 1344 if self.dying or not self.loaded:
cf694132
RD
1345 return
1346
5a7823f5
RD
1347 item = event.GetItem()
1348 itemText = self.tree.GetItemText(item)
08ecc920 1349 self.LoadDemo(itemText)
5a7823f5
RD
1350
1351 #---------------------------------------------
08ecc920
RD
1352 def LoadDemo(self, demoName):
1353 try:
1354 wx.BeginBusyCursor()
1355
1356 os.chdir(self.cwd)
1357 self.ShutdownDemoModule()
1358
1359 if demoName == self.overviewText:
1360 # User selected the "wxPython Overview" node
1361 # ie: _this_ module
1362 # Changing the main window at runtime not yet supported...
1363 self.demoModules = DemoModules(__name__)
1364 self.SetOverview(self.overviewText, mainOverview)
1365 self.LoadDemoSource()
1366 self.UpdateNotebook(0)
1367 else:
1368 if os.path.exists(GetOriginalFilename(demoName)):
1369 wx.LogMessage("Loading demo %s.py..." % demoName)
1370 self.demoModules = DemoModules(demoName)
a6780fa2 1371 self.LoadDemoSource()
08ecc920
RD
1372 self.tree.Refresh()
1373 else:
1374 self.SetOverview("wxPython", mainOverview)
1375 self.codePage = None
1376 self.UpdateNotebook(0)
1377 finally:
1378 wx.EndBusyCursor()
cf694132 1379
08ecc920
RD
1380 #---------------------------------------------
1381 def LoadDemoSource(self):
1382 self.codePage = None
1383 self.codePage = DemoCodePanel(self.nb, self)
1384 self.codePage.LoadDemo(self.demoModules)
1385
1386 #---------------------------------------------
a6780fa2 1387 def RunModule(self):
08ecc920 1388 """Runs the active module"""
cf694132 1389
08ecc920
RD
1390 module = self.demoModules.GetActive()
1391 self.ShutdownDemoModule()
1392 overviewText = ""
1393
8412feb0
RD
1394 # o The RunTest() for all samples must now return a window that can
1395 # be palced in a tab in the main notebook.
08ecc920
RD
1396 # o If an error occurs (or has occured before) an error tab is created.
1397
1398 if module is not None:
1399 wx.LogMessage("Running demo module...")
1400 if hasattr(module, "overview"):
1401 overviewText = module.overview
cf694132 1402
08ecc920
RD
1403 try:
1404 self.demoPage = module.runTest(self, self.nb, self)
08ecc920 1405 except:
317a64e5 1406 self.demoPage = DemoErrorPanel(self.nb, self.codePage,
7d0f8766
RD
1407 DemoError(sys.exc_info()), self)
1408
1409 assert self.demoPage is not None, "runTest must return a window!"
1410
08ecc920
RD
1411 else:
1412 # There was a previous error in compiling or exec-ing
317a64e5
RD
1413 self.demoPage = DemoErrorPanel(self.nb, self.codePage,
1414 self.demoModules.GetErrorInfo(), self)
a6780fa2 1415
08ecc920 1416 self.SetOverview(self.demoModules.name + " Overview", overviewText)
a6780fa2
RD
1417
1418 if self.firstTime:
1419 # cahnge to the demo page the first time a module is run
1420 self.UpdateNotebook(2)
1421 self.firstTime = False
1422 else:
1423 # otherwise just stay on the same tab in case the user has changed to another one
1424 self.UpdateNotebook()
cf694132
RD
1425
1426 #---------------------------------------------
08ecc920
RD
1427 def ShutdownDemoModule(self):
1428 if self.demoPage:
1429 # inform the window that it's time to quit if it cares
1430 if hasattr(self.demoPage, "ShutdownDemo"):
1431 self.demoPage.ShutdownDemo()
1432 wx.YieldIfNeeded() # in case the page has pending events
1433 self.demoPage = None
1434
1435 #---------------------------------------------
1436 def UpdateNotebook(self, select = -1):
1437 nb = self.nb
1438 debug = False
1439
1440 def UpdatePage(page, pageText):
1441 pageExists = False
1442 pagePos = -1
1443 for i in range(nb.GetPageCount()):
1444 if nb.GetPageText(i) == pageText:
1445 pageExists = True
1446 pagePos = i
1447 break
1448
1449 if page:
1450 if not pageExists:
1451 # Add a new page
08ecc920
RD
1452 nb.AddPage(page, pageText)
1453 if debug: wx.LogMessage("DBG: ADDED %s" % pageText)
1454 else:
08ecc920
RD
1455 if nb.GetPage(pagePos) != page:
1456 # Reload an existing page
1457 nb.Freeze()
08ecc920
RD
1458 nb.DeletePage(pagePos)
1459 nb.InsertPage(pagePos, page, pageText)
1460 nb.Thaw()
1461 if debug: wx.LogMessage("DBG: RELOADED %s" % pageText)
1462 else:
1463 # Excellent! No redraw/flicker
1464 if debug: wx.LogMessage("DBG: SAVED from reloading %s" % pageText)
1465 elif pageExists:
1466 # Delete a page
1467 nb.DeletePage(pagePos)
1468 if debug: wx.LogMessage("DBG: DELETED %s" % pageText)
1469 else:
1470 if debug: wx.LogMessage("DBG: STILL GONE - %s" % pageText)
1471
1472 if select == -1:
1473 select = nb.GetSelection()
cf694132 1474
08ecc920
RD
1475 UpdatePage(self.codePage, "Demo Code")
1476 UpdatePage(self.demoPage, "Demo")
cf694132 1477
887a1bd9 1478 if select >= 0 and select < nb.GetPageCount():
08ecc920
RD
1479 nb.SetSelection(select)
1480
cf694132
RD
1481 #---------------------------------------------
1482 def SetOverview(self, name, text):
f6bcfd97
BP
1483 self.curOverview = text
1484 lead = text[:6]
1485 if lead != '<html>' and lead != '<HTML>':
1e4a197e 1486 text = '<br>'.join(text.split('\n'))
6c7d1792
RD
1487 if wx.USE_UNICODE:
1488 text = text.decode('iso8859_1')
f6bcfd97 1489 self.ovr.SetPage(text)
cf694132 1490 self.nb.SetPageText(0, name)
cf694132
RD
1491
1492 #---------------------------------------------
1493 # Menu methods
c368d904 1494 def OnFileExit(self, *event):
cf694132
RD
1495 self.Close()
1496
330af869
RD
1497 def OnToggleRedirect(self, event):
1498 app = wx.GetApp()
1499 if event.Checked():
1500 app.RedirectStdio()
1501 print "Print statements and other standard output will now be directed to this window."
1502 else:
1503 app.RestoreStdio()
1504 print "Print statements and other standard output will now be sent to the usual location."
08ecc920 1505
cf694132 1506 def OnHelpAbout(self, event):
e166644c 1507 from About import MyAboutBox
ec3e670f 1508 about = MyAboutBox(self)
cf694132
RD
1509 about.ShowModal()
1510 about.Destroy()
1511
1e4a197e
RD
1512 def OnHelpFind(self, event):
1513 self.nb.SetSelection(1)
1fded56b
RD
1514 self.finddlg = wx.FindReplaceDialog(self, self.finddata, "Find",
1515 wx.FR_NOUPDOWN |
1516 wx.FR_NOMATCHCASE |
1517 wx.FR_NOWHOLEWORD)
1e4a197e
RD
1518 self.finddlg.Show(True)
1519
1520 def OnFind(self, event):
08ecc920 1521 editor = self.codePage.editor
1e4a197e 1522 self.nb.SetSelection(1)
08ecc920
RD
1523 end = editor.GetLastPosition()
1524 textstring = editor.GetRange(0, end).lower()
1525 start = editor.GetSelection()[1]
1e4a197e
RD
1526 findstring = self.finddata.GetFindString().lower()
1527 loc = textstring.find(findstring, start)
1528 if loc == -1 and start != 0:
1529 # string not found, start at beginning
1530 start = 0
1531 loc = textstring.find(findstring, start)
1532 if loc == -1:
1fded56b 1533 dlg = wx.MessageDialog(self, 'Find String Not Found',
1e4a197e 1534 'Find String Not Found in Demo File',
1fded56b 1535 wx.OK | wx.ICON_INFORMATION)
1e4a197e
RD
1536 dlg.ShowModal()
1537 dlg.Destroy()
1538 if self.finddlg:
1539 if loc == -1:
1540 self.finddlg.SetFocus()
1541 return
1542 else:
1543 self.finddlg.Destroy()
08ecc920
RD
1544 editor.ShowPosition(loc)
1545 editor.SetSelection(loc, loc + len(findstring))
1e4a197e
RD
1546
1547
1548
1549 def OnFindNext(self, event):
1550 if self.finddata.GetFindString():
1551 self.OnFind(event)
1552 else:
1553 self.OnHelpFind(event)
1554
1555 def OnFindClose(self, event):
1556 event.GetDialog().Destroy()
1557
cf694132 1558
887a1bd9
RD
1559 def OnOpenShellWindow(self, evt):
1560 if self.shell:
1561 # if it already exists then just make sure it's visible
1562 s = self.shell
1563 if s.IsIconized():
1564 s.Iconize(False)
1565 s.Raise()
1566 else:
1567 # Make a PyShell window
1568 from wx import py
1569 namespace = { 'wx' : wx,
1570 'app' : wx.GetApp(),
1571 'frame' : self,
1572 }
1573 self.shell = py.shell.ShellFrame(None, locals=namespace)
1574 self.shell.SetSize((640,480))
1575 self.shell.Show()
1576
1577 # Hook the close event of the main frame window so that we
1578 # close the shell at the same time if it still exists
1579 def CloseShell(evt):
1580 if self.shell:
1581 self.shell.Close()
1582 evt.Skip()
1583 self.Bind(wx.EVT_CLOSE, CloseShell)
1584
1585
cf694132
RD
1586 #---------------------------------------------
1587 def OnCloseWindow(self, event):
1e4a197e 1588 self.dying = True
08ecc920
RD
1589 self.demoPage = None
1590 self.codePage = None
26197023 1591 self.mainmenu = None
1cc18e15 1592 self.tbicon.Destroy()
cf694132
RD
1593 self.Destroy()
1594
c368d904 1595
cf694132
RD
1596 #---------------------------------------------
1597 def OnIdle(self, event):
1598 if self.otherWin:
1599 self.otherWin.Raise()
08ecc920 1600 self.demoPage = self.otherWin
cf694132
RD
1601 self.otherWin = None
1602
ccf691a4
RD
1603
1604 #---------------------------------------------
1605 def ShowTip(self):
1606 try:
1607 showTipText = open(opj("data/showTips")).read()
1608 showTip, index = eval(showTipText)
1609 except IOError:
1610 showTip, index = (1, 0)
1611 if showTip:
1fded56b 1612 tp = wx.CreateFileTipProvider(opj("data/tips.txt"), index)
861a0196 1613 ##tp = MyTP(0)
1fded56b 1614 showTip = wx.ShowTip(self, tp)
ccf691a4
RD
1615 index = tp.GetCurrentTip()
1616 open(opj("data/showTips"), "w").write(str( (showTip, index) ))
1617
1618
ec3e670f
RD
1619 #---------------------------------------------
1620 def OnDemoMenu(self, event):
f6bcfd97
BP
1621 try:
1622 selectedDemo = self.treeMap[self.mainmenu.GetLabel(event.GetId())]
1623 except:
1624 selectedDemo = None
1625 if selectedDemo:
1626 self.tree.SelectItem(selectedDemo)
1627 self.tree.EnsureVisible(selectedDemo)
ec3e670f 1628
c368d904 1629
c368d904 1630
f3d9dc1d
RD
1631 #---------------------------------------------
1632 def OnIconfiy(self, evt):
8412feb0 1633 wx.LogMessage("OnIconfiy: %s" % evt.Iconized())
f3d9dc1d
RD
1634 evt.Skip()
1635
1636 #---------------------------------------------
1637 def OnMaximize(self, evt):
1fded56b 1638 wx.LogMessage("OnMaximize")
f3d9dc1d
RD
1639 evt.Skip()
1640
8412feb0
RD
1641 #---------------------------------------------
1642 def OnActivate(self, evt):
1643 wx.LogMessage("OnActivate: %s" % evt.GetActive())
1644 evt.Skip()
f3d9dc1d 1645
8412feb0
RD
1646 #---------------------------------------------
1647 def OnAppActivate(self, evt):
1648 wx.LogMessage("OnAppActivate: %s" % evt.GetActive())
1649 evt.Skip()
f3d9dc1d 1650
cf694132
RD
1651#---------------------------------------------------------------------------
1652#---------------------------------------------------------------------------
1653
1fded56b 1654class MySplashScreen(wx.SplashScreen):
b5a5d647 1655 def __init__(self):
1fded56b
RD
1656 bmp = wx.Image(opj("bitmaps/splash.gif")).ConvertToBitmap()
1657 wx.SplashScreen.__init__(self, bmp,
a253aa20
RD
1658 wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT,
1659 3000, None, -1)
80b27b4e 1660 self.Bind(wx.EVT_CLOSE, self.OnClose)
b5a5d647
RD
1661
1662 def OnClose(self, evt):
8eca4fef 1663 self.Hide()
08ecc920 1664 frame = wxPythonDemo(None, "wxPython: (A Demonstration)")
1e4a197e 1665 frame.Show()
ccf691a4 1666 evt.Skip() # Make sure the default handler runs too...
cf694132 1667
b5a5d647 1668
1fded56b 1669class MyApp(wx.App):
b5a5d647
RD
1670 def OnInit(self):
1671 """
68320e40 1672 Create and show the splash screen. It will then create and show
b5a5d647
RD
1673 the main frame when it is time to do so.
1674 """
1e4a197e 1675
35ee3288
RD
1676 # For debugging
1677 #self.SetAssertMode(wx.PYAPP_ASSERT_DIALOG)
1678
a253aa20
RD
1679 # Normally when using a SplashScreen you would create it, show
1680 # it and then continue on with the applicaiton's
1681 # initialization, finally creating and showing the main
1682 # application window(s). In this case we have nothing else to
1683 # do so we'll delay showing the main frame until later (see
b7aef858 1684 # OnClose above) so the users can see the SplashScreen effect.
b5a5d647
RD
1685 splash = MySplashScreen()
1686 splash.Show()
a253aa20 1687
1e4a197e 1688 return True
b5a5d647
RD
1689
1690
1691
cf694132
RD
1692#---------------------------------------------------------------------------
1693
1694def main():
e02c03a4 1695 try:
d56cebe7 1696 demoPath = os.path.dirname(__file__)
e02c03a4
RD
1697 os.chdir(demoPath)
1698 except:
1699 pass
03a604a6 1700 app = MyApp(False)
cf694132
RD
1701 app.MainLoop()
1702
cf694132
RD
1703#---------------------------------------------------------------------------
1704
1705
08ecc920 1706mainOverview = """<html><body>
1fded56b
RD
1707<h2>wxPython</h2>
1708
f6063c19
RD
1709<p> wxPython is a <b>GUI toolkit</b> for the Python programming
1710language. It allows Python programmers to create programs with a
1711robust, highly functional graphical user interface, simply and easily.
1712It is implemented as a Python extension module (native code) that
1713wraps the popular wxWindows cross platform GUI library, which is
1714written in C++.
1fded56b
RD
1715
1716<p> Like Python and wxWindows, wxPython is <b>Open Source</b> which
1717means that it is free for anyone to use and the source code is
1718available for anyone to look at and modify. Or anyone can contribute
8b9a4190 1719fixes or enhancements to the project.
1fded56b
RD
1720
1721<p> wxPython is a <b>cross-platform</b> toolkit. This means that the
1722same program will run on multiple platforms without modification.
1723Currently supported platforms are 32-bit Microsoft Windows, most Unix
1724or unix-like systems, and Macintosh OS X. Since the language is
1725Python, wxPython programs are <b>simple, easy</b> to write and easy to
1726understand.
1727
1728<p> <b>This demo</b> is not only a collection of test cases for
1729wxPython, but is also designed to help you learn about and how to use
1730wxPython. Each sample is listed in the tree control on the left.
1731When a sample is selected in the tree then a module is loaded and run
1732(usually in a tab of this notebook,) and the source code of the module
1733is loaded in another tab for you to browse and learn from.
1734
1735"""
cf694132
RD
1736
1737
1738#----------------------------------------------------------------------------
1739#----------------------------------------------------------------------------
1740
1741if __name__ == '__main__':
08ecc920 1742 __name__ = 'Main'
cf694132
RD
1743 main()
1744
1745#----------------------------------------------------------------------------
1746
1747
1748
1749
1750
1751
1752