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