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 
  61         self
.SetAutoLayout(True) 
  67             data 
= self
.doodle
.GetLinesData() 
  68             f 
= open(self
.filename
, 'w') 
  76                 f 
= open(self
.filename
, 'r') 
  77                 data 
= cPickle
.load(f
) 
  79                 self
.doodle
.SetLinesData(data
) 
  80             except cPickle
.UnpicklingError
: 
  81                 wx
.MessageBox("%s is not a doodle file." % self
.filename
, 
  82                              "oops!", style
=wx
.OK|wx
.ICON_EXCLAMATION
) 
  86         # create the file menu 
  89         # Using the "\tKeyName" syntax automatically creates a 
  90         # wx.AcceleratorTable for this frame and binds the keys to 
  92         menu1
.Append(idOPEN
, "&Open\tCtrl-O", "Open a doodle file") 
  93         menu1
.Append(idSAVE
, "&Save\tCtrl-S", "Save the doodle") 
  94         menu1
.Append(idSAVEAS
, "Save &As", "Save the doodle in a new file") 
  95         menu1
.AppendSeparator() 
  96         menu1
.Append(idCLEAR
, "&Clear", "Clear the current doodle") 
  97         menu1
.AppendSeparator() 
  98         menu1
.Append(idEXIT
, "E&xit", "Terminate the application") 
 102         menu2
.Append(idABOUT
, "&About\tCtrl-H", "Display the gratuitous 'about this app' thingamajig") 
 104         # and add them to a menubar 
 105         menuBar 
= wx
.MenuBar() 
 106         menuBar
.Append(menu1
, "&File") 
 107         menuBar
.Append(menu2
, "&Help") 
 108         self
.SetMenuBar(menuBar
) 
 110         self
.Bind(wx
.EVT_MENU
,   self
.OnMenuOpen
, id=idOPEN
) 
 111         self
.Bind(wx
.EVT_MENU
,   self
.OnMenuSave
, id=idSAVE
) 
 112         self
.Bind(wx
.EVT_MENU
, self
.OnMenuSaveAs
, id=idSAVEAS
) 
 113         self
.Bind(wx
.EVT_MENU
,  self
.OnMenuClear
, id=idCLEAR
) 
 114         self
.Bind(wx
.EVT_MENU
,   self
.OnMenuExit
, id=idEXIT
) 
 115         self
.Bind(wx
.EVT_MENU
,  self
.OnMenuAbout
, id=idABOUT
) 
 119     wildcard 
= "Doodle files (*.ddl)|*.ddl|All files (*.*)|*.*" 
 121     def OnMenuOpen(self
, event
): 
 122         dlg 
= wx
.FileDialog(self
, "Open doodle file...", os
.getcwd(), 
 123                            style
=wx
.OPEN
, wildcard 
= self
.wildcard
) 
 124         if dlg
.ShowModal() == wx
.ID_OK
: 
 125             self
.filename 
= dlg
.GetPath() 
 127             self
.SetTitle(self
.title 
+ ' -- ' + self
.filename
) 
 131     def OnMenuSave(self
, event
): 
 132         if not self
.filename
: 
 133             self
.OnMenuSaveAs(event
) 
 138     def OnMenuSaveAs(self
, event
): 
 139         dlg 
= wx
.FileDialog(self
, "Save doodle as...", os
.getcwd(), 
 140                            style
=wx
.SAVE | wx
.OVERWRITE_PROMPT
, 
 141                            wildcard 
= self
.wildcard
) 
 142         if dlg
.ShowModal() == wx
.ID_OK
: 
 143             filename 
= dlg
.GetPath() 
 144             if not os
.path
.splitext(filename
)[1]: 
 145                 filename 
= filename 
+ '.ddl' 
 146             self
.filename 
= filename
 
 148             self
.SetTitle(self
.title 
+ ' -- ' + self
.filename
) 
 152     def OnMenuClear(self
, event
): 
 153         self
.doodle
.SetLinesData([]) 
 154         self
.SetTitle(self
.title
) 
 157     def OnMenuExit(self
, event
): 
 161     def OnMenuAbout(self
, event
): 
 162         dlg 
= DoodleAbout(self
) 
 168 #---------------------------------------------------------------------- 
 171 class ControlPanel(wx
.Panel
): 
 173     This class implements a very simple control panel for the DoodleWindow. 
 174     It creates buttons for each of the colours and thickneses supported by 
 175     the DoodleWindow, and event handlers to set the selected values.  There is 
 176     also a little window that shows an example doodleLine in the selected 
 177     values.  Nested sizers are used for layout. 
 183     def __init__(self
, parent
, ID
, doodle
): 
 184         wx
.Panel
.__init
__(self
, parent
, ID
, style
=wx
.RAISED_BORDER
, size
=(20,20)) 
 189         btnSize 
= wx
.Size(self
.BMP_SIZE 
+ 2*self
.BMP_BORDER
, 
 190                           self
.BMP_SIZE 
+ 2*self
.BMP_BORDER
) 
 192         # Make a grid of buttons for each colour.  Attach each button 
 193         # event to self.OnSetColour.  The button ID is the same as the 
 194         # key in the colour dictionary. 
 196         colours 
= doodle
.menuColours
 
 197         keys 
= colours
.keys() 
 199         cGrid 
= wx
.GridSizer(cols
=numCols
, hgap
=2, vgap
=2) 
 201             bmp 
= self
.MakeBitmap(colours
[k
]) 
 202             b 
= buttons
.GenBitmapToggleButton(self
, k
, bmp
, size
=btnSize 
) 
 204             b
.SetUseFocusIndicator(False) 
 205             self
.Bind(wx
.EVT_BUTTON
, self
.OnSetColour
, b
) 
 207             self
.clrBtns
[colours
[k
]] = b
 
 208         self
.clrBtns
[colours
[keys
[0]]].SetToggle(True) 
 211         # Make a grid of buttons for the thicknesses.  Attach each button 
 212         # event to self.OnSetThickness.  The button ID is the same as the 
 215         tGrid 
= wx
.GridSizer(cols
=numCols
, hgap
=2, vgap
=2) 
 216         for x 
in range(1, doodle
.maxThickness
+1): 
 217             b 
= buttons
.GenToggleButton(self
, x
, str(x
), size
=btnSize
) 
 219             b
.SetUseFocusIndicator(False) 
 220             self
.Bind(wx
.EVT_BUTTON
, self
.OnSetThickness
, b
) 
 222             self
.thknsBtns
[x
] = b
 
 223         self
.thknsBtns
[1].SetToggle(True) 
 225         # Make a colour indicator window, it is registerd as a listener 
 226         # with the doodle window so it will be notified when the settings 
 228         ci 
= ColourIndicator(self
) 
 229         doodle
.AddListener(ci
) 
 233         # Make a box sizer and put the two grids and the indicator 
 235         box 
= wx
.BoxSizer(wx
.VERTICAL
) 
 236         box
.Add(cGrid
, 0, wx
.ALL
, spacing
) 
 237         box
.Add(tGrid
, 0, wx
.ALL
, spacing
) 
 238         box
.Add(ci
, 0, wx
.EXPAND|wx
.ALL
, spacing
) 
 240         self
.SetAutoLayout(True) 
 242         # Resize this window so it is just large enough for the 
 243         # minimum requirements of the sizer. 
 248     def MakeBitmap(self
, colour
): 
 250         We can create a bitmap of whatever we want by simply selecting 
 251         it into a wx.MemoryDC and drawing on it.  In this case we just set 
 252         a background brush and clear the dc. 
 254         bmp 
= wx
.EmptyBitmap(self
.BMP_SIZE
, self
.BMP_SIZE
) 
 257         dc
.SetBackground(wx
.Brush(colour
)) 
 259         dc
.SelectObject(wx
.NullBitmap
) 
 263     def OnSetColour(self
, event
): 
 265         Use the event ID to get the colour, set that colour in the doodle. 
 267         colour 
= self
.doodle
.menuColours
[event
.GetId()] 
 268         if colour 
!= self
.doodle
.colour
: 
 269             # untoggle the old colour button 
 270             self
.clrBtns
[self
.doodle
.colour
].SetToggle(False) 
 272         self
.doodle
.SetColour(colour
) 
 275     def OnSetThickness(self
, event
): 
 277         Use the event ID to set the thickness in the doodle. 
 279         thickness 
= event
.GetId() 
 280         if thickness 
!= self
.doodle
.thickness
: 
 281             # untoggle the old thickness button 
 282             self
.thknsBtns
[self
.doodle
.thickness
].SetToggle(False) 
 284         self
.doodle
.SetThickness(thickness
) 
 288 #---------------------------------------------------------------------- 
 290 class ColourIndicator(wx
.Window
): 
 292     An instance of this class is used on the ControlPanel to show 
 293     a sample of what the current doodle line will look like. 
 295     def __init__(self
, parent
): 
 296         wx
.Window
.__init
__(self
, parent
, -1, style
=wx
.SUNKEN_BORDER
) 
 297         self
.SetBackgroundColour(wx
.WHITE
) 
 298         self
.SetSize( (45, 45) ) 
 299         self
.colour 
= self
.thickness 
= None 
 300         self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
) 
 303     def Update(self
, colour
, thickness
): 
 305         The doodle window calls this method any time the colour 
 306         or line thickness changes. 
 309         self
.thickness 
= thickness
 
 310         self
.Refresh()  # generate a paint event 
 313     def OnPaint(self
, event
): 
 315         This method is called when all or part of the window needs to be 
 318         dc 
= wx
.PaintDC(self
) 
 320             sz 
= self
.GetClientSize() 
 321             pen 
= wx
.Pen(self
.colour
, self
.thickness
) 
 324             dc
.DrawLine(10, sz
.height
/2, sz
.width
-10, sz
.height
/2) 
 328 #---------------------------------------------------------------------- 
 330 class DoodleAbout(wx
.Dialog
): 
 331     """ An about box that uses an HTML window """ 
 335 <body bgcolor="#ACAA60"> 
 336 <center><table bgcolor="#455481" width="100%" cellspacing="0" 
 337 cellpadding="0" border="1"> 
 339     <td align="center"><h1>SuperDoodle</h1></td> 
 343 <p><b>SuperDoodle</b> is a demonstration program for <b>wxPython</b> that 
 344 will hopefully teach you a thing or two.  Just follow these simple 
 348   <li><b>Read</b> the Source... 
 353 <p><b>SuperDoodle</b> and <b>wxPython</b> are brought to you by 
 354 <b>Robin Dunn</b> and <b>Total Control Software</b>, Copyright 
 355 © 1997-2003.</p> 
 360     def __init__(self
, parent
): 
 361         wx
.Dialog
.__init
__(self
, parent
, -1, 'About SuperDoodle', 
 364         html 
= wx
.html
.HtmlWindow(self
, -1) 
 365         html
.SetPage(self
.text
) 
 366         button 
= wx
.Button(self
, wx
.ID_OK
, "Okay") 
 368         # constraints for the html window 
 369         lc 
= wx
.LayoutConstraints() 
 370         lc
.top
.SameAs(self
, wx
.Top
, 5) 
 371         lc
.left
.SameAs(self
, wx
.Left
, 5) 
 372         lc
.bottom
.SameAs(button
, wx
.Top
, 5) 
 373         lc
.right
.SameAs(self
, wx
.Right
, 5) 
 374         html
.SetConstraints(lc
) 
 376         # constraints for the button 
 377         lc 
= wx
.LayoutConstraints() 
 378         lc
.bottom
.SameAs(self
, wx
.Bottom
, 5) 
 379         lc
.centreX
.SameAs(self
, wx
.CentreX
) 
 382         button
.SetConstraints(lc
) 
 384         self
.SetAutoLayout(True) 
 386         self
.CentreOnParent(wx
.BOTH
) 
 389 #---------------------------------------------------------------------- 
 391 class DoodleApp(wx
.App
): 
 393         frame 
= DoodleFrame(None) 
 395         self
.SetTopWindow(frame
) 
 399 #---------------------------------------------------------------------- 
 401 if __name__ 
== '__main__': 
 402     app 
= DoodleApp(redirect
=True)