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