]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/doodle/superdoodle.py
another change note
[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.
61 self.SetAutoLayout(True)
62 self.SetSizer(box)
63
64
65 def SaveFile(self):
66 if self.filename:
67 data = self.doodle.GetLinesData()
68 f = open(self.filename, 'w')
69 cPickle.dump(data, f)
70 f.close()
71
72
73 def ReadFile(self):
74 if self.filename:
75 try:
76 f = open(self.filename, 'r')
77 data = cPickle.load(f)
78 f.close()
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)
83
84
85 def MakeMenu(self):
86 # create the file menu
87 menu1 = wx.Menu()
88
89 # Using the "\tKeyName" syntax automatically creates a
90 # wx.AcceleratorTable for this frame and binds the keys to
91 # the menu items.
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")
99
100 # and the help menu
101 menu2 = wx.Menu()
102 menu2.Append(idABOUT, "&About\tCtrl-H", "Display the gratuitous 'about this app' thingamajig")
103
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)
109
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)
116
117
118
119 wildcard = "Doodle files (*.ddl)|*.ddl|All files (*.*)|*.*"
120
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()
126 self.ReadFile()
127 self.SetTitle(self.title + ' -- ' + self.filename)
128 dlg.Destroy()
129
130
131 def OnMenuSave(self, event):
132 if not self.filename:
133 self.OnMenuSaveAs(event)
134 else:
135 self.SaveFile()
136
137
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
147 self.SaveFile()
148 self.SetTitle(self.title + ' -- ' + self.filename)
149 dlg.Destroy()
150
151
152 def OnMenuClear(self, event):
153 self.doodle.SetLinesData([])
154 self.SetTitle(self.title)
155
156
157 def OnMenuExit(self, event):
158 self.Close()
159
160
161 def OnMenuAbout(self, event):
162 dlg = DoodleAbout(self)
163 dlg.ShowModal()
164 dlg.Destroy()
165
166
167
168 #----------------------------------------------------------------------
169
170
171 class ControlPanel(wx.Panel):
172 """
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.
178 """
179
180 BMP_SIZE = 16
181 BMP_BORDER = 3
182
183 def __init__(self, parent, ID, doodle):
184 wx.Panel.__init__(self, parent, ID, style=wx.RAISED_BORDER, size=(20,20))
185
186 numCols = 4
187 spacing = 4
188
189 btnSize = wx.Size(self.BMP_SIZE + 2*self.BMP_BORDER,
190 self.BMP_SIZE + 2*self.BMP_BORDER)
191
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.
195 self.clrBtns = {}
196 colours = doodle.menuColours
197 keys = colours.keys()
198 keys.sort()
199 cGrid = wx.GridSizer(cols=numCols, hgap=2, vgap=2)
200 for k in keys:
201 bmp = self.MakeBitmap(colours[k])
202 b = buttons.GenBitmapToggleButton(self, k, bmp, size=btnSize )
203 b.SetBezelWidth(1)
204 b.SetUseFocusIndicator(False)
205 self.Bind(wx.EVT_BUTTON, self.OnSetColour, b)
206 cGrid.Add(b, 0)
207 self.clrBtns[colours[k]] = b
208 self.clrBtns[colours[keys[0]]].SetToggle(True)
209
210
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
213 # thickness value.
214 self.thknsBtns = {}
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)
218 b.SetBezelWidth(1)
219 b.SetUseFocusIndicator(False)
220 self.Bind(wx.EVT_BUTTON, self.OnSetThickness, b)
221 tGrid.Add(b, 0)
222 self.thknsBtns[x] = b
223 self.thknsBtns[1].SetToggle(True)
224
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
227 # change
228 ci = ColourIndicator(self)
229 doodle.AddListener(ci)
230 doodle.Notify()
231 self.doodle = doodle
232
233 # Make a box sizer and put the two grids and the indicator
234 # window in it.
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)
239 self.SetSizer(box)
240 self.SetAutoLayout(True)
241
242 # Resize this window so it is just large enough for the
243 # minimum requirements of the sizer.
244 box.Fit(self)
245
246
247
248 def MakeBitmap(self, colour):
249 """
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.
253 """
254 bmp = wx.EmptyBitmap(self.BMP_SIZE, self.BMP_SIZE)
255 dc = wx.MemoryDC()
256 dc.SelectObject(bmp)
257 dc.SetBackground(wx.Brush(colour))
258 dc.Clear()
259 dc.SelectObject(wx.NullBitmap)
260 return bmp
261
262
263 def OnSetColour(self, event):
264 """
265 Use the event ID to get the colour, set that colour in the doodle.
266 """
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)
271 # set the new colour
272 self.doodle.SetColour(colour)
273
274
275 def OnSetThickness(self, event):
276 """
277 Use the event ID to set the thickness in the doodle.
278 """
279 thickness = event.GetId()
280 if thickness != self.doodle.thickness:
281 # untoggle the old thickness button
282 self.thknsBtns[self.doodle.thickness].SetToggle(False)
283 # set the new colour
284 self.doodle.SetThickness(thickness)
285
286
287
288 #----------------------------------------------------------------------
289
290 class ColourIndicator(wx.Window):
291 """
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.
294 """
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)
301
302
303 def Update(self, colour, thickness):
304 """
305 The doodle window calls this method any time the colour
306 or line thickness changes.
307 """
308 self.colour = colour
309 self.thickness = thickness
310 self.Refresh() # generate a paint event
311
312
313 def OnPaint(self, event):
314 """
315 This method is called when all or part of the window needs to be
316 redrawn.
317 """
318 dc = wx.PaintDC(self)
319 if self.colour:
320 sz = self.GetClientSize()
321 pen = wx.Pen(self.colour, self.thickness)
322 dc.BeginDrawing()
323 dc.SetPen(pen)
324 dc.DrawLine(10, sz.height/2, sz.width-10, sz.height/2)
325 dc.EndDrawing()
326
327
328 #----------------------------------------------------------------------
329
330 class DoodleAbout(wx.Dialog):
331 """ An about box that uses an HTML window """
332
333 text = '''
334 <html>
335 <body bgcolor="#ACAA60">
336 <center><table bgcolor="#455481" width="100%" cellspacing="0"
337 cellpadding="0" border="1">
338 <tr>
339 <td align="center"><h1>SuperDoodle</h1></td>
340 </tr>
341 </table>
342 </center>
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
345 instructions: </p>
346 <p>
347 <ol>
348 <li><b>Read</b> the Source...
349 <li><b>Learn</b>...
350 <li><b>Do!</b>
351 </ol>
352
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 &copy; 1997-2003.</p>
356 </body>
357 </html>
358 '''
359
360 def __init__(self, parent):
361 wx.Dialog.__init__(self, parent, -1, 'About SuperDoodle',
362 size=(420, 380) )
363
364 html = wx.html.HtmlWindow(self, -1)
365 html.SetPage(self.text)
366 button = wx.Button(self, wx.ID_OK, "Okay")
367
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)
375
376 # constraints for the button
377 lc = wx.LayoutConstraints()
378 lc.bottom.SameAs(self, wx.Bottom, 5)
379 lc.centreX.SameAs(self, wx.CentreX)
380 lc.width.AsIs()
381 lc.height.AsIs()
382 button.SetConstraints(lc)
383
384 self.SetAutoLayout(True)
385 self.Layout()
386 self.CentreOnParent(wx.BOTH)
387
388
389 #----------------------------------------------------------------------
390
391 class DoodleApp(wx.App):
392 def OnInit(self):
393 frame = DoodleFrame(None)
394 frame.Show(True)
395 self.SetTopWindow(frame)
396 return True
397
398
399 #----------------------------------------------------------------------
400
401 if __name__ == '__main__':
402 app = DoodleApp(redirect=True)
403 app.MainLoop()