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 wx
.EVT_MENU(self
, idOPEN
, self
.OnMenuOpen
)
111 wx
.EVT_MENU(self
, idSAVE
, self
.OnMenuSave
)
112 wx
.EVT_MENU(self
, idSAVEAS
, self
.OnMenuSaveAs
)
113 wx
.EVT_MENU(self
, idCLEAR
, self
.OnMenuClear
)
114 wx
.EVT_MENU(self
, idEXIT
, self
.OnMenuExit
)
115 wx
.EVT_MENU(self
, idABOUT
, self
.OnMenuAbout
)
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
)
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 wx
.EVT_BUTTON(self
, k
, self
.OnSetColour
)
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 wx
.EVT_BUTTON(self
, x
, self
.OnSetThickness
)
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( (-1, 45) )
299 self
.colour
= self
.thickness
= None
300 wx
.EVT_PAINT(self
, 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__':