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-2006.</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)