4 This module implements the SuperDoodle demo application.  It takes the 
   5 DoodleWindow previously presented and reuses it in a much more 
   6 intelligent Frame.  This one has a menu and a statusbar, is able to 
   7 save and reload doodles, clear the workspace, and has a simple control 
   8 panel for setting color and line thickness in addition to the popup 
   9 menu that DoodleWindow provides.  There is also a nice About dialog 
  10 implmented using an wx.html.HtmlWindow. 
  13 import wx                  
# This module uses the new wx namespace 
  15 from wx
.lib 
import buttons 
# for generic button classes 
  16 from doodle 
import DoodleWindow
 
  21 #---------------------------------------------------------------------- 
  23 wx
.RegisterId(5000)  # Give a high starting value for the IDs, just for kicks 
  35 class DoodleFrame(wx
.Frame
): 
  37     A DoodleFrame contains a DoodleWindow and a ControlPanel and manages 
  38     their layout with a wx.BoxSizer.  A menu and associated event handlers 
  39     provides for saving a doodle to a file, etc. 
  42     def __init__(self
, parent
): 
  43         wx
.Frame
.__init
__(self
, parent
, -1, self
.title
, size
=(800,600), 
  44                          style
=wx
.DEFAULT_FRAME_STYLE | wx
.NO_FULL_REPAINT_ON_RESIZE
) 
  45         self
.CreateStatusBar() 
  49         self
.doodle 
= DoodleWindow(self
, -1) 
  50         cPanel 
= ControlPanel(self
, -1, self
.doodle
) 
  52         # Create a sizer to layout the two windows side-by-side. 
  53         # Both will grow vertically, the doodle window will grow 
  54         # horizontally as well. 
  55         box 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
  56         box
.Add(cPanel
, 0, wx
.EXPAND
) 
  57         box
.Add(self
.doodle
, 1, wx
.EXPAND
) 
  59         # Tell the frame that it should layout itself in response to 
  60         # size events using this sizer. 
  66             data 
= self
.doodle
.GetLinesData() 
  67             f 
= open(self
.filename
, 'w') 
  75                 f 
= open(self
.filename
, 'r') 
  76                 data 
= cPickle
.load(f
) 
  78                 self
.doodle
.SetLinesData(data
) 
  79             except cPickle
.UnpicklingError
: 
  80                 wx
.MessageBox("%s is not a doodle file." % self
.filename
, 
  81                              "oops!", style
=wx
.OK|wx
.ICON_EXCLAMATION
) 
  85         # create the file menu 
  88         # Using the "\tKeyName" syntax automatically creates a 
  89         # wx.AcceleratorTable for this frame and binds the keys to 
  91         menu1
.Append(idOPEN
, "&Open\tCtrl-O", "Open a doodle file") 
  92         menu1
.Append(idSAVE
, "&Save\tCtrl-S", "Save the doodle") 
  93         menu1
.Append(idSAVEAS
, "Save &As", "Save the doodle in a new file") 
  94         menu1
.AppendSeparator() 
  95         menu1
.Append(idCLEAR
, "&Clear", "Clear the current doodle") 
  96         menu1
.AppendSeparator() 
  97         menu1
.Append(idEXIT
, "E&xit", "Terminate the application") 
 101         menu2
.Append(idABOUT
, "&About\tCtrl-H", "Display the gratuitous 'about this app' thingamajig") 
 103         # and add them to a menubar 
 104         menuBar 
= wx
.MenuBar() 
 105         menuBar
.Append(menu1
, "&File") 
 106         menuBar
.Append(menu2
, "&Help") 
 107         self
.SetMenuBar(menuBar
) 
 109         self
.Bind(wx
.EVT_MENU
,   self
.OnMenuOpen
, id=idOPEN
) 
 110         self
.Bind(wx
.EVT_MENU
,   self
.OnMenuSave
, id=idSAVE
) 
 111         self
.Bind(wx
.EVT_MENU
, self
.OnMenuSaveAs
, id=idSAVEAS
) 
 112         self
.Bind(wx
.EVT_MENU
,  self
.OnMenuClear
, id=idCLEAR
) 
 113         self
.Bind(wx
.EVT_MENU
,   self
.OnMenuExit
, id=idEXIT
) 
 114         self
.Bind(wx
.EVT_MENU
,  self
.OnMenuAbout
, id=idABOUT
) 
 118     wildcard 
= "Doodle files (*.ddl)|*.ddl|All files (*.*)|*.*" 
 120     def OnMenuOpen(self
, event
): 
 121         dlg 
= wx
.FileDialog(self
, "Open doodle file...", os
.getcwd(), 
 122                            style
=wx
.OPEN
, wildcard 
= self
.wildcard
) 
 123         if dlg
.ShowModal() == wx
.ID_OK
: 
 124             self
.filename 
= dlg
.GetPath() 
 126             self
.SetTitle(self
.title 
+ ' -- ' + self
.filename
) 
 130     def OnMenuSave(self
, event
): 
 131         if not self
.filename
: 
 132             self
.OnMenuSaveAs(event
) 
 137     def OnMenuSaveAs(self
, event
): 
 138         dlg 
= wx
.FileDialog(self
, "Save doodle as...", os
.getcwd(), 
 139                            style
=wx
.SAVE | wx
.OVERWRITE_PROMPT
, 
 140                            wildcard 
= self
.wildcard
) 
 141         if dlg
.ShowModal() == wx
.ID_OK
: 
 142             filename 
= dlg
.GetPath() 
 143             if not os
.path
.splitext(filename
)[1]: 
 144                 filename 
= filename 
+ '.ddl' 
 145             self
.filename 
= filename
 
 147             self
.SetTitle(self
.title 
+ ' -- ' + self
.filename
) 
 151     def OnMenuClear(self
, event
): 
 152         self
.doodle
.SetLinesData([]) 
 153         self
.SetTitle(self
.title
) 
 156     def OnMenuExit(self
, event
): 
 160     def OnMenuAbout(self
, event
): 
 161         dlg 
= DoodleAbout(self
) 
 167 #---------------------------------------------------------------------- 
 170 class ControlPanel(wx
.Panel
): 
 172     This class implements a very simple control panel for the DoodleWindow. 
 173     It creates buttons for each of the colours and thickneses supported by 
 174     the DoodleWindow, and event handlers to set the selected values.  There is 
 175     also a little window that shows an example doodleLine in the selected 
 176     values.  Nested sizers are used for layout. 
 182     def __init__(self
, parent
, ID
, doodle
): 
 183         wx
.Panel
.__init
__(self
, parent
, ID
, style
=wx
.RAISED_BORDER
, size
=(20,20)) 
 188         btnSize 
= wx
.Size(self
.BMP_SIZE 
+ 2*self
.BMP_BORDER
, 
 189                           self
.BMP_SIZE 
+ 2*self
.BMP_BORDER
) 
 191         # Make a grid of buttons for each colour.  Attach each button 
 192         # event to self.OnSetColour.  The button ID is the same as the 
 193         # key in the colour dictionary. 
 195         colours 
= doodle
.menuColours
 
 196         keys 
= colours
.keys() 
 198         cGrid 
= wx
.GridSizer(cols
=numCols
, hgap
=2, vgap
=2) 
 200             bmp 
= self
.MakeBitmap(colours
[k
]) 
 201             b 
= buttons
.GenBitmapToggleButton(self
, k
, bmp
, size
=btnSize 
) 
 203             b
.SetUseFocusIndicator(False) 
 204             self
.Bind(wx
.EVT_BUTTON
, self
.OnSetColour
, b
) 
 206             self
.clrBtns
[colours
[k
]] = b
 
 207         self
.clrBtns
[colours
[keys
[0]]].SetToggle(True) 
 210         # Make a grid of buttons for the thicknesses.  Attach each button 
 211         # event to self.OnSetThickness.  The button ID is the same as the 
 214         tGrid 
= wx
.GridSizer(cols
=numCols
, hgap
=2, vgap
=2) 
 215         for x 
in range(1, doodle
.maxThickness
+1): 
 216             b 
= buttons
.GenToggleButton(self
, x
, str(x
), size
=btnSize
) 
 218             b
.SetUseFocusIndicator(False) 
 219             self
.Bind(wx
.EVT_BUTTON
, self
.OnSetThickness
, b
) 
 221             self
.thknsBtns
[x
] = b
 
 222         self
.thknsBtns
[1].SetToggle(True) 
 224         # Make a colour indicator window, it is registerd as a listener 
 225         # with the doodle window so it will be notified when the settings 
 227         ci 
= ColourIndicator(self
) 
 228         doodle
.AddListener(ci
) 
 232         # Make a box sizer and put the two grids and the indicator 
 234         box 
= wx
.BoxSizer(wx
.VERTICAL
) 
 235         box
.Add(cGrid
, 0, wx
.ALL
, spacing
) 
 236         box
.Add(tGrid
, 0, wx
.ALL
, spacing
) 
 237         box
.Add(ci
, 0, wx
.EXPAND|wx
.ALL
, spacing
) 
 239         self
.SetAutoLayout(True) 
 241         # Resize this window so it is just large enough for the 
 242         # minimum requirements of the sizer. 
 247     def MakeBitmap(self
, colour
): 
 249         We can create a bitmap of whatever we want by simply selecting 
 250         it into a wx.MemoryDC and drawing on it.  In this case we just set 
 251         a background brush and clear the dc. 
 253         bmp 
= wx
.EmptyBitmap(self
.BMP_SIZE
, self
.BMP_SIZE
) 
 256         dc
.SetBackground(wx
.Brush(colour
)) 
 258         dc
.SelectObject(wx
.NullBitmap
) 
 262     def OnSetColour(self
, event
): 
 264         Use the event ID to get the colour, set that colour in the doodle. 
 266         colour 
= self
.doodle
.menuColours
[event
.GetId()] 
 267         if colour 
!= self
.doodle
.colour
: 
 268             # untoggle the old colour button 
 269             self
.clrBtns
[self
.doodle
.colour
].SetToggle(False) 
 271         self
.doodle
.SetColour(colour
) 
 274     def OnSetThickness(self
, event
): 
 276         Use the event ID to set the thickness in the doodle. 
 278         thickness 
= event
.GetId() 
 279         if thickness 
!= self
.doodle
.thickness
: 
 280             # untoggle the old thickness button 
 281             self
.thknsBtns
[self
.doodle
.thickness
].SetToggle(False) 
 283         self
.doodle
.SetThickness(thickness
) 
 287 #---------------------------------------------------------------------- 
 289 class ColourIndicator(wx
.Window
): 
 291     An instance of this class is used on the ControlPanel to show 
 292     a sample of what the current doodle line will look like. 
 294     def __init__(self
, parent
): 
 295         wx
.Window
.__init
__(self
, parent
, -1, style
=wx
.SUNKEN_BORDER
) 
 296         self
.SetBackgroundColour(wx
.WHITE
) 
 297         self
.SetSize( (45, 45) ) 
 298         self
.colour 
= self
.thickness 
= None 
 299         self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
) 
 302     def Update(self
, colour
, thickness
): 
 304         The doodle window calls this method any time the colour 
 305         or line thickness changes. 
 308         self
.thickness 
= thickness
 
 309         self
.Refresh()  # generate a paint event 
 312     def OnPaint(self
, event
): 
 314         This method is called when all or part of the window needs to be 
 317         dc 
= wx
.PaintDC(self
) 
 319             sz 
= self
.GetClientSize() 
 320             pen 
= wx
.Pen(self
.colour
, self
.thickness
) 
 323             dc
.DrawLine(10, sz
.height
/2, sz
.width
-10, sz
.height
/2) 
 327 #---------------------------------------------------------------------- 
 329 class DoodleAbout(wx
.Dialog
): 
 330     """ An about box that uses an HTML window """ 
 334 <body bgcolor="#ACAA60"> 
 335 <center><table bgcolor="#455481" width="100%" cellspacing="0" 
 336 cellpadding="0" border="1"> 
 338     <td align="center"><h1>SuperDoodle</h1></td> 
 342 <p><b>SuperDoodle</b> is a demonstration program for <b>wxPython</b> that 
 343 will hopefully teach you a thing or two.  Just follow these simple 
 347   <li><b>Read</b> the Source... 
 352 <p><b>SuperDoodle</b> and <b>wxPython</b> are brought to you by 
 353 <b>Robin Dunn</b> and <b>Total Control Software</b>, Copyright 
 354 © 1997-2003.</p> 
 359     def __init__(self
, parent
): 
 360         wx
.Dialog
.__init
__(self
, parent
, -1, 'About SuperDoodle', 
 363         html 
= wx
.html
.HtmlWindow(self
, -1) 
 364         html
.SetPage(self
.text
) 
 365         button 
= wx
.Button(self
, wx
.ID_OK
, "Okay") 
 367         # constraints for the html window 
 368         lc 
= wx
.LayoutConstraints() 
 369         lc
.top
.SameAs(self
, wx
.Top
, 5) 
 370         lc
.left
.SameAs(self
, wx
.Left
, 5) 
 371         lc
.bottom
.SameAs(button
, wx
.Top
, 5) 
 372         lc
.right
.SameAs(self
, wx
.Right
, 5) 
 373         html
.SetConstraints(lc
) 
 375         # constraints for the button 
 376         lc 
= wx
.LayoutConstraints() 
 377         lc
.bottom
.SameAs(self
, wx
.Bottom
, 5) 
 378         lc
.centreX
.SameAs(self
, wx
.CentreX
) 
 381         button
.SetConstraints(lc
) 
 383         self
.SetAutoLayout(True) 
 385         self
.CentreOnParent(wx
.BOTH
) 
 388 #---------------------------------------------------------------------- 
 390 class DoodleApp(wx
.App
): 
 392         frame 
= DoodleFrame(None) 
 394         self
.SetTopWindow(frame
) 
 398 #---------------------------------------------------------------------- 
 400 if __name__ 
== '__main__': 
 401     app 
= DoodleApp(redirect
=True)