]> git.saurik.com Git - wxWidgets.git/blame - wxPython/samples/doodle/superdoodle.py
Allow art provider IDs for the extra bitmaps in a wxBitmapButton. (The
[wxWidgets.git] / wxPython / samples / doodle / superdoodle.py
CommitLineData
c12bc4de
RD
1# superdoodle.py
2
3"""
4This module implements the SuperDoodle demo application. It takes the
5DoodleWindow previously presented and reuses it in a much more
6intelligent Frame. This one has a menu and a statusbar, is able to
7save and reload doodles, clear the workspace, and has a simple control
8panel for setting color and line thickness in addition to the popup
9menu that DoodleWindow provides. There is also a nice About dialog
1fded56b 10implmented using an wx.html.HtmlWindow.
c12bc4de
RD
11"""
12
1fded56b
RD
13import wx # This module uses the new wx namespace
14import wx.html
15from wx.lib import buttons # for generic button classes
c12bc4de
RD
16from doodle import DoodleWindow
17
18import os, cPickle
19
20
21#----------------------------------------------------------------------
22
1fded56b 23wx.RegisterId(5000) # Give a high starting value for the IDs, just for kicks
c12bc4de 24
1fded56b
RD
25idNEW = wx.NewId()
26idOPEN = wx.NewId()
27idSAVE = wx.NewId()
28idSAVEAS = wx.NewId()
29idCLEAR = wx.NewId()
30idEXIT = wx.NewId()
31idABOUT = wx.NewId()
c12bc4de 32
1fded56b
RD
33
34
35class DoodleFrame(wx.Frame):
c12bc4de
RD
36 """
37 A DoodleFrame contains a DoodleWindow and a ControlPanel and manages
1fded56b 38 their layout with a wx.BoxSizer. A menu and associated event handlers
c12bc4de
RD
39 provides for saving a doodle to a file, etc.
40 """
41 title = "Do a doodle"
42 def __init__(self, parent):
1fded56b
RD
43 wx.Frame.__init__(self, parent, -1, self.title, size=(800,600),
44 style=wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE)
c12bc4de
RD
45 self.CreateStatusBar()
46 self.MakeMenu()
47 self.filename = None
48
49 self.doodle = DoodleWindow(self, -1)
50 cPanel = ControlPanel(self, -1, self.doodle)
51
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.
1fded56b
RD
55 box = wx.BoxSizer(wx.HORIZONTAL)
56 box.Add(cPanel, 0, wx.EXPAND)
57 box.Add(self.doodle, 1, wx.EXPAND)
c12bc4de
RD
58
59 # Tell the frame that it should layout itself in response to
2d830433 60 # size events using this sizer.
c12bc4de
RD
61 self.SetSizer(box)
62
63
64 def SaveFile(self):
65 if self.filename:
66 data = self.doodle.GetLinesData()
67 f = open(self.filename, 'w')
68 cPickle.dump(data, f)
69 f.close()
70
71
72 def ReadFile(self):
73 if self.filename:
74 try:
75 f = open(self.filename, 'r')
76 data = cPickle.load(f)
77 f.close()
78 self.doodle.SetLinesData(data)
79 except cPickle.UnpicklingError:
1fded56b
RD
80 wx.MessageBox("%s is not a doodle file." % self.filename,
81 "oops!", style=wx.OK|wx.ICON_EXCLAMATION)
c12bc4de
RD
82
83
84 def MakeMenu(self):
85 # create the file menu
1fded56b
RD
86 menu1 = wx.Menu()
87
88 # Using the "\tKeyName" syntax automatically creates a
89 # wx.AcceleratorTable for this frame and binds the keys to
90 # the menu items.
91 menu1.Append(idOPEN, "&Open\tCtrl-O", "Open a doodle file")
92 menu1.Append(idSAVE, "&Save\tCtrl-S", "Save the doodle")
c12bc4de
RD
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")
98
99 # and the help menu
1fded56b
RD
100 menu2 = wx.Menu()
101 menu2.Append(idABOUT, "&About\tCtrl-H", "Display the gratuitous 'about this app' thingamajig")
c12bc4de
RD
102
103 # and add them to a menubar
1fded56b 104 menuBar = wx.MenuBar()
c12bc4de
RD
105 menuBar.Append(menu1, "&File")
106 menuBar.Append(menu2, "&Help")
107 self.SetMenuBar(menuBar)
108
2571247b
RD
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)
c12bc4de
RD
115
116
117
118 wildcard = "Doodle files (*.ddl)|*.ddl|All files (*.*)|*.*"
119
120 def OnMenuOpen(self, event):
1fded56b
RD
121 dlg = wx.FileDialog(self, "Open doodle file...", os.getcwd(),
122 style=wx.OPEN, wildcard = self.wildcard)
123 if dlg.ShowModal() == wx.ID_OK:
c12bc4de
RD
124 self.filename = dlg.GetPath()
125 self.ReadFile()
126 self.SetTitle(self.title + ' -- ' + self.filename)
127 dlg.Destroy()
128
129
130 def OnMenuSave(self, event):
131 if not self.filename:
132 self.OnMenuSaveAs(event)
133 else:
134 self.SaveFile()
135
136
137 def OnMenuSaveAs(self, event):
1fded56b
RD
138 dlg = wx.FileDialog(self, "Save doodle as...", os.getcwd(),
139 style=wx.SAVE | wx.OVERWRITE_PROMPT,
c12bc4de 140 wildcard = self.wildcard)
1fded56b 141 if dlg.ShowModal() == wx.ID_OK:
c12bc4de
RD
142 filename = dlg.GetPath()
143 if not os.path.splitext(filename)[1]:
144 filename = filename + '.ddl'
145 self.filename = filename
146 self.SaveFile()
147 self.SetTitle(self.title + ' -- ' + self.filename)
148 dlg.Destroy()
149
150
151 def OnMenuClear(self, event):
152 self.doodle.SetLinesData([])
153 self.SetTitle(self.title)
154
155
156 def OnMenuExit(self, event):
157 self.Close()
158
159
160 def OnMenuAbout(self, event):
161 dlg = DoodleAbout(self)
162 dlg.ShowModal()
163 dlg.Destroy()
164
165
166
167#----------------------------------------------------------------------
168
169
1fded56b 170class ControlPanel(wx.Panel):
c12bc4de
RD
171 """
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.
177 """
1fded56b
RD
178
179 BMP_SIZE = 16
180 BMP_BORDER = 3
181
c12bc4de 182 def __init__(self, parent, ID, doodle):
dc4b282d 183 wx.Panel.__init__(self, parent, ID, style=wx.RAISED_BORDER, size=(20,20))
c12bc4de
RD
184
185 numCols = 4
186 spacing = 4
187
1fded56b
RD
188 btnSize = wx.Size(self.BMP_SIZE + 2*self.BMP_BORDER,
189 self.BMP_SIZE + 2*self.BMP_BORDER)
190
c12bc4de
RD
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.
1fded56b 194 self.clrBtns = {}
c12bc4de
RD
195 colours = doodle.menuColours
196 keys = colours.keys()
197 keys.sort()
1fded56b 198 cGrid = wx.GridSizer(cols=numCols, hgap=2, vgap=2)
c12bc4de 199 for k in keys:
1fded56b
RD
200 bmp = self.MakeBitmap(colours[k])
201 b = buttons.GenBitmapToggleButton(self, k, bmp, size=btnSize )
202 b.SetBezelWidth(1)
203 b.SetUseFocusIndicator(False)
2571247b 204 self.Bind(wx.EVT_BUTTON, self.OnSetColour, b)
c12bc4de 205 cGrid.Add(b, 0)
1fded56b
RD
206 self.clrBtns[colours[k]] = b
207 self.clrBtns[colours[keys[0]]].SetToggle(True)
c12bc4de 208
c12bc4de
RD
209
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
212 # thickness value.
1fded56b
RD
213 self.thknsBtns = {}
214 tGrid = wx.GridSizer(cols=numCols, hgap=2, vgap=2)
c12bc4de 215 for x in range(1, doodle.maxThickness+1):
1fded56b
RD
216 b = buttons.GenToggleButton(self, x, str(x), size=btnSize)
217 b.SetBezelWidth(1)
218 b.SetUseFocusIndicator(False)
2571247b 219 self.Bind(wx.EVT_BUTTON, self.OnSetThickness, b)
c12bc4de 220 tGrid.Add(b, 0)
1fded56b
RD
221 self.thknsBtns[x] = b
222 self.thknsBtns[1].SetToggle(True)
c12bc4de
RD
223
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
226 # change
227 ci = ColourIndicator(self)
228 doodle.AddListener(ci)
229 doodle.Notify()
230 self.doodle = doodle
231
232 # Make a box sizer and put the two grids and the indicator
233 # window in it.
1fded56b
RD
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)
c12bc4de 238 self.SetSizer(box)
1e4a197e 239 self.SetAutoLayout(True)
c12bc4de
RD
240
241 # Resize this window so it is just large enough for the
242 # minimum requirements of the sizer.
243 box.Fit(self)
244
245
246
247 def MakeBitmap(self, colour):
248 """
249 We can create a bitmap of whatever we want by simply selecting
1fded56b 250 it into a wx.MemoryDC and drawing on it. In this case we just set
c12bc4de
RD
251 a background brush and clear the dc.
252 """
1fded56b
RD
253 bmp = wx.EmptyBitmap(self.BMP_SIZE, self.BMP_SIZE)
254 dc = wx.MemoryDC()
c12bc4de 255 dc.SelectObject(bmp)
1fded56b 256 dc.SetBackground(wx.Brush(colour))
c12bc4de 257 dc.Clear()
1fded56b 258 dc.SelectObject(wx.NullBitmap)
c12bc4de
RD
259 return bmp
260
261
262 def OnSetColour(self, event):
263 """
264 Use the event ID to get the colour, set that colour in the doodle.
265 """
266 colour = self.doodle.menuColours[event.GetId()]
1fded56b
RD
267 if colour != self.doodle.colour:
268 # untoggle the old colour button
269 self.clrBtns[self.doodle.colour].SetToggle(False)
270 # set the new colour
c12bc4de
RD
271 self.doodle.SetColour(colour)
272
273
274 def OnSetThickness(self, event):
275 """
276 Use the event ID to set the thickness in the doodle.
277 """
1fded56b
RD
278 thickness = event.GetId()
279 if thickness != self.doodle.thickness:
280 # untoggle the old thickness button
281 self.thknsBtns[self.doodle.thickness].SetToggle(False)
282 # set the new colour
283 self.doodle.SetThickness(thickness)
284
c12bc4de
RD
285
286
287#----------------------------------------------------------------------
288
1fded56b 289class ColourIndicator(wx.Window):
c12bc4de
RD
290 """
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.
293 """
294 def __init__(self, parent):
1fded56b
RD
295 wx.Window.__init__(self, parent, -1, style=wx.SUNKEN_BORDER)
296 self.SetBackgroundColour(wx.WHITE)
dc4b282d 297 self.SetSize( (45, 45) )
c12bc4de 298 self.colour = self.thickness = None
2571247b 299 self.Bind(wx.EVT_PAINT, self.OnPaint)
c12bc4de
RD
300
301
302 def Update(self, colour, thickness):
303 """
304 The doodle window calls this method any time the colour
305 or line thickness changes.
306 """
307 self.colour = colour
308 self.thickness = thickness
309 self.Refresh() # generate a paint event
310
311
312 def OnPaint(self, event):
313 """
314 This method is called when all or part of the window needs to be
315 redrawn.
316 """
1fded56b 317 dc = wx.PaintDC(self)
c12bc4de
RD
318 if self.colour:
319 sz = self.GetClientSize()
1fded56b 320 pen = wx.Pen(self.colour, self.thickness)
c12bc4de
RD
321 dc.BeginDrawing()
322 dc.SetPen(pen)
b68eef00 323 dc.DrawLine(10, sz.height/2, sz.width-10, sz.height/2)
c12bc4de
RD
324 dc.EndDrawing()
325
326
327#----------------------------------------------------------------------
328
1fded56b 329class DoodleAbout(wx.Dialog):
c12bc4de
RD
330 """ An about box that uses an HTML window """
331
332 text = '''
333<html>
334<body bgcolor="#ACAA60">
335<center><table bgcolor="#455481" width="100%" cellspacing="0"
336cellpadding="0" border="1">
337<tr>
338 <td align="center"><h1>SuperDoodle</h1></td>
339</tr>
340</table>
341</center>
342<p><b>SuperDoodle</b> is a demonstration program for <b>wxPython</b> that
343will hopefully teach you a thing or two. Just follow these simple
344instructions: </p>
345<p>
346<ol>
347 <li><b>Read</b> the Source...
348 <li><b>Learn</b>...
349 <li><b>Do!</b>
350</ol>
351
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
7d778540 354&copy; 1997-2006.</p>
c12bc4de
RD
355</body>
356</html>
357'''
358
359 def __init__(self, parent):
1fded56b
RD
360 wx.Dialog.__init__(self, parent, -1, 'About SuperDoodle',
361 size=(420, 380) )
c12bc4de 362
1fded56b 363 html = wx.html.HtmlWindow(self, -1)
c12bc4de 364 html.SetPage(self.text)
1fded56b 365 button = wx.Button(self, wx.ID_OK, "Okay")
c12bc4de
RD
366
367 # constraints for the html window
1fded56b
RD
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)
c12bc4de
RD
373 html.SetConstraints(lc)
374
375 # constraints for the button
1fded56b
RD
376 lc = wx.LayoutConstraints()
377 lc.bottom.SameAs(self, wx.Bottom, 5)
378 lc.centreX.SameAs(self, wx.CentreX)
c12bc4de
RD
379 lc.width.AsIs()
380 lc.height.AsIs()
381 button.SetConstraints(lc)
382
1e4a197e 383 self.SetAutoLayout(True)
c12bc4de 384 self.Layout()
1fded56b 385 self.CentreOnParent(wx.BOTH)
c12bc4de
RD
386
387
388#----------------------------------------------------------------------
389
1fded56b 390class DoodleApp(wx.App):
c12bc4de
RD
391 def OnInit(self):
392 frame = DoodleFrame(None)
1e4a197e 393 frame.Show(True)
c12bc4de 394 self.SetTopWindow(frame)
1e4a197e 395 return True
c12bc4de
RD
396
397
398#----------------------------------------------------------------------
399
400if __name__ == '__main__':
b68eef00 401 app = DoodleApp(redirect=True)
c12bc4de 402 app.MainLoop()