]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/doodle/superdoodle.py
Allow art provider IDs for the extra bitmaps in a wxBitmapButton. (The
[wxWidgets.git] / wxPython / samples / doodle / superdoodle.py
1 # superdoodle.py
2
3 """
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.
11 """
12
13 import wx # This module uses the new wx namespace
14 import wx.html
15 from wx.lib import buttons # for generic button classes
16 from doodle import DoodleWindow
17
18 import os, cPickle
19
20
21 #----------------------------------------------------------------------
22
23 wx.RegisterId(5000) # Give a high starting value for the IDs, just for kicks
24
25 idNEW = wx.NewId()
26 idOPEN = wx.NewId()
27 idSAVE = wx.NewId()
28 idSAVEAS = wx.NewId()
29 idCLEAR = wx.NewId()
30 idEXIT = wx.NewId()
31 idABOUT = wx.NewId()
32
33
34
35 class DoodleFrame(wx.Frame):
36 """
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.
40 """
41 title = "Do a doodle"
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()
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.
55 box = wx.BoxSizer(wx.HORIZONTAL)
56 box.Add(cPanel, 0, wx.EXPAND)
57 box.Add(self.doodle, 1, wx.EXPAND)
58
59 # Tell the frame that it should layout itself in response to
60 # size events using this sizer.
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:
80 wx.MessageBox("%s is not a doodle file." % self.filename,
81 "oops!", style=wx.OK|wx.ICON_EXCLAMATION)
82
83
84 def MakeMenu(self):
85 # create the file menu
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")
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
100 menu2 = wx.Menu()
101 menu2.Append(idABOUT, "&About\tCtrl-H", "Display the gratuitous 'about this app' thingamajig")
102
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)
108
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)
115
116
117
118 wildcard = "Doodle files (*.ddl)|*.ddl|All files (*.*)|*.*"
119
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()
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):
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
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
170 class ControlPanel(wx.Panel):
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 """
178
179 BMP_SIZE = 16
180 BMP_BORDER = 3
181
182 def __init__(self, parent, ID, doodle):
183 wx.Panel.__init__(self, parent, ID, style=wx.RAISED_BORDER, size=(20,20))
184
185 numCols = 4
186 spacing = 4
187
188 btnSize = wx.Size(self.BMP_SIZE + 2*self.BMP_BORDER,
189 self.BMP_SIZE + 2*self.BMP_BORDER)
190
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.
194 self.clrBtns = {}
195 colours = doodle.menuColours
196 keys = colours.keys()
197 keys.sort()
198 cGrid = wx.GridSizer(cols=numCols, hgap=2, vgap=2)
199 for k in keys:
200 bmp = self.MakeBitmap(colours[k])
201 b = buttons.GenBitmapToggleButton(self, k, bmp, size=btnSize )
202 b.SetBezelWidth(1)
203 b.SetUseFocusIndicator(False)
204 self.Bind(wx.EVT_BUTTON, self.OnSetColour, b)
205 cGrid.Add(b, 0)
206 self.clrBtns[colours[k]] = b
207 self.clrBtns[colours[keys[0]]].SetToggle(True)
208
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.
213 self.thknsBtns = {}
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)
217 b.SetBezelWidth(1)
218 b.SetUseFocusIndicator(False)
219 self.Bind(wx.EVT_BUTTON, self.OnSetThickness, b)
220 tGrid.Add(b, 0)
221 self.thknsBtns[x] = b
222 self.thknsBtns[1].SetToggle(True)
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.
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)
238 self.SetSizer(box)
239 self.SetAutoLayout(True)
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
250 it into a wx.MemoryDC and drawing on it. In this case we just set
251 a background brush and clear the dc.
252 """
253 bmp = wx.EmptyBitmap(self.BMP_SIZE, self.BMP_SIZE)
254 dc = wx.MemoryDC()
255 dc.SelectObject(bmp)
256 dc.SetBackground(wx.Brush(colour))
257 dc.Clear()
258 dc.SelectObject(wx.NullBitmap)
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()]
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
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 """
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
285
286
287 #----------------------------------------------------------------------
288
289 class ColourIndicator(wx.Window):
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):
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)
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 """
317 dc = wx.PaintDC(self)
318 if self.colour:
319 sz = self.GetClientSize()
320 pen = wx.Pen(self.colour, self.thickness)
321 dc.BeginDrawing()
322 dc.SetPen(pen)
323 dc.DrawLine(10, sz.height/2, sz.width-10, sz.height/2)
324 dc.EndDrawing()
325
326
327 #----------------------------------------------------------------------
328
329 class DoodleAbout(wx.Dialog):
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"
336 cellpadding="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
343 will hopefully teach you a thing or two. Just follow these simple
344 instructions: </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
354 &copy; 1997-2006.</p>
355 </body>
356 </html>
357 '''
358
359 def __init__(self, parent):
360 wx.Dialog.__init__(self, parent, -1, 'About SuperDoodle',
361 size=(420, 380) )
362
363 html = wx.html.HtmlWindow(self, -1)
364 html.SetPage(self.text)
365 button = wx.Button(self, wx.ID_OK, "Okay")
366
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)
374
375 # constraints for the button
376 lc = wx.LayoutConstraints()
377 lc.bottom.SameAs(self, wx.Bottom, 5)
378 lc.centreX.SameAs(self, wx.CentreX)
379 lc.width.AsIs()
380 lc.height.AsIs()
381 button.SetConstraints(lc)
382
383 self.SetAutoLayout(True)
384 self.Layout()
385 self.CentreOnParent(wx.BOTH)
386
387
388 #----------------------------------------------------------------------
389
390 class DoodleApp(wx.App):
391 def OnInit(self):
392 frame = DoodleFrame(None)
393 frame.Show(True)
394 self.SetTopWindow(frame)
395 return True
396
397
398 #----------------------------------------------------------------------
399
400 if __name__ == '__main__':
401 app = DoodleApp(redirect=True)
402 app.MainLoop()