]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/roses/wxroses.py
1 #----------------------------------------------------------------------------
3 # Purpose: wxPython GUI using clroses.py to display a classic graphics
6 # Author: Ric Werme, Robin Dunn.
7 # WWW: http://WermeNH.com/roses
11 # Copyright: Public Domain, please give credit where credit is due.
12 # License: Sorry, no EULA.
13 #----------------------------------------------------------------------------
15 # This module is responsible for everything involving GUI usage
16 # as clroses knows nothing about wxpython, tkintr, etc.
18 # There are some notes about how the Roses algorithm works in clroses.py,
19 # but the best reference should be at http://WermeNH.com/roses/index.html .
21 # There are a number of enhancements that could be done to wxRoses, and
22 # contributions are welcome as long as you don't destory the general
23 # structure, flavor, and all that. The following list is in the order
24 # I'd like to see done. Some are easy, some aren't, some are easy if
25 # you have experience in the right parts of external code.
27 # Brighter crossing points.
28 # Where many vectors cross, the display becomes washed out as a solid shape
29 # of light. On (antique) refresh vector graphics systems, crossing points
30 # are brighter because the electron beam paints the pixels multiple times.
31 # This gives complex patterns a lacy feel to some, and a 3-D sense to
32 # fluted shapes where vectors lie tangent to some curve. It would be
33 # nice to do the same in a bitmap buffer, the code to draw a vector is
34 # pretty simple, adding something that adds brightness to it via math or
35 # a lookup table ought to be a simple addition.
37 # Monochrome is so 20th century.
38 # There are a number of things that could be done with color. The simplest
39 # is to step through colors in a color list, better things to do would be
40 # for clroses.py to determine the length of an interesting "generator pattern,"
41 # e.g. the square in the opening display. Then it could change colors either
42 # every four vectors or cycle through the first four colors in the list.
45 # As you play with wxRoses, you'll come across some patterns that are
46 # "keepers." A bookmark mechanism would be handy.
49 # It would be nice to have a Menu-bar/File/Save-as dialog to save a pattern
50 # as a jpg/png/gif file.
53 # A pulldown option to select various themes is worthwhile. E.g.:
54 # Start an interesting animation,
55 # Select complex, lacy Roses,
56 # Select the author's favorites,
57 # Return to the initial Rose.
58 # Actually, all that's necessary are some pre-loaded bookmarks.
64 # At CMU I created an interactive slide show that walked people through
65 # all the options and made suggestions about how to choose Style and Petal.
66 # I forget exactly what I said and may not have listings for it. At any rate,
67 # making the help mechanism start one of several "lessons" where it could
68 # control the display (without blocking the user's control) would be pretty
73 import wx
.lib
.colourselect
as cs
76 # Class SpinPanel creates a control that includes both a StaticText widget
77 # which holds the the name of a parameter and a SpinCtrl widget which
78 # displays the current value. Values are set at initialization and can
79 # change via the SpinCtrl widget or by the program. So that the program
80 # can easily access the SpinCtrl, the SpinPanel handles are saved in the
81 # spin_panels dictionary.
82 class SpinPanel(wx
.Panel
):
83 def __init__(self
, parent
, name
, min_value
, value
, max_value
, callback
):
84 wx
.Panel
.__init
__(self
, parent
, -1)
85 if "wxMac" in wx
.PlatformInfo
:
86 self
.SetWindowVariant(wx
.WINDOW_VARIANT_SMALL
)
87 self
.st
= wx
.StaticText(self
, -1, name
)
88 self
.sc
= wx
.SpinCtrl(self
, -1, "", size
= (70, -1))
89 self
.sc
.SetRange(min_value
, max_value
)
90 self
.sc
.SetValue(value
)
91 self
.sc
.Bind(wx
.EVT_SPINCTRL
, self
.OnSpin
)
92 self
.callback
= callback
94 sizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
95 sizer
.Add(self
.st
, 0, wx
.ALIGN_CENTER_VERTICAL
)
101 spin_panels
[name
] = self
103 # Called (generally through spin_panels{}) to set the SpinCtrl value.
104 def SetValue(self
, value
):
105 self
.sc
.SetValue(value
)
107 # Called when user changes the SpinCtrl value.
108 def OnSpin(self
, event
):
109 name
= self
.st
.GetLabel()
110 value
= self
.sc
.GetValue()
112 print 'OnSpin', name
, '=', value
113 self
.callback(name
, value
) # Call MyFrame.OnSpinback to call clroses
116 # This class is used to display the current rose diagram. It keeps a
117 # buffer bitmap of the current display, which it uses to refresh the
118 # screen with when needed. When it is told to draw some lines it does
119 # so to the buffer in order for it to always be up to date.
120 class RosePanel(wx
.Panel
):
121 def __init__(self
, *args
, **kw
):
122 wx
.Panel
.__init
__(self
, *args
, **kw
)
124 self
.resizeNeeded
= False
126 self
.useBuffer
= True
129 self
.SetBackgroundColour((51, 51, 51)) # gray20
130 self
.SetForegroundColour((164, 211, 238)) # lightskyblue2
132 # connect the size and paint events to handlers
133 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
134 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
135 self
.Bind(wx
.EVT_IDLE
, self
.OnIdle
)
138 def InitBuffer(self
):
139 size
= self
.GetClientSize()
140 self
.buffer = wx
.EmptyBitmap(max(1, size
.width
),
144 dc
= self
.useBuffer
and wx
.MemoryDC(self
.buffer) or wx
.ClientDC(self
)
145 dc
.SetBackground(wx
.Brush(self
.GetBackgroundColour()))
150 def DrawLines(self
, lines
):
153 dc
= self
.useBuffer
and wx
.MemoryDC(self
.buffer) or wx
.ClientDC(self
)
156 dc
.SetPen(wx
.Pen(self
.GetForegroundColour(), 1))
161 def TriggerResize(self
):
162 self
.GetParent().TriggerResize(self
.buffer.GetSize())
164 def TriggerRedraw(self
):
165 self
.GetParent().TriggerRedraw()
167 def OnSize(self
, evt
):
168 self
.resizeNeeded
= True
170 def OnIdle(self
, evt
):
171 if self
.resizeNeeded
:
176 self
.resizeNeeded
= False
178 def OnPaint(self
, evt
):
179 dc
= wx
.PaintDC(self
)
181 dc
.DrawBitmap(self
.buffer, 0,0)
186 # A panel used to collect options on how the rose is drawn
187 class OptionsPanel(wx
.Panel
):
188 def __init__(self
, parent
, rose
):
189 wx
.Panel
.__init
__(self
, parent
)
191 sizer
= wx
.StaticBoxSizer(wx
.StaticBox(self
, label
='Options'),
193 self
.useGCDC
= wx
.CheckBox(self
, label
="Use GCDC")
194 sizer
.Add(self
.useGCDC
, 0, wx
.BOTTOM|wx
.LEFT
, 2)
195 self
.useBuffer
= wx
.CheckBox(self
, label
="Use buffering")
196 sizer
.Add(self
.useBuffer
, 0, wx
.BOTTOM|wx
.LEFT
, 2)
198 def makeCButton(label
):
199 btn
= cs
.ColourSelect(self
, size
=(20,22))
200 lbl
= wx
.StaticText(self
, -1, label
)
201 sizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
204 sizer
.Add(lbl
, 0, wx
.ALIGN_CENTER_VERTICAL
)
207 s
, self
.fg
= makeCButton('Foreground')
209 s
, self
.bg
= makeCButton('Background')
213 self
.Bind(wx
.EVT_CHECKBOX
, self
.OnUseGCDC
, self
.useGCDC
)
214 self
.Bind(wx
.EVT_CHECKBOX
, self
.OnUseBuffer
, self
.useBuffer
)
215 self
.Bind(wx
.EVT_IDLE
, self
.OnIdle
)
216 self
.Bind(cs
.EVT_COLOURSELECT
, self
.OnSetFG
, self
.fg
)
217 self
.Bind(cs
.EVT_COLOURSELECT
, self
.OnSetBG
, self
.bg
)
219 def OnIdle(self
, evt
):
220 if self
.useGCDC
.GetValue() != self
.rose
.useGCDC
:
221 self
.useGCDC
.SetValue(self
.rose
.useGCDC
)
222 if self
.useBuffer
.GetValue() != self
.rose
.useBuffer
:
223 self
.useBuffer
.SetValue(self
.rose
.useBuffer
)
224 if self
.fg
.GetValue() != self
.rose
.GetForegroundColour():
225 self
.fg
.SetValue(self
.rose
.GetForegroundColour())
226 if self
.bg
.GetValue() != self
.rose
.GetBackgroundColour():
227 self
.bg
.SetValue(self
.rose
.GetBackgroundColour())
229 def OnUseGCDC(self
, evt
):
230 self
.rose
.useGCDC
= evt
.IsChecked()
231 self
.rose
.TriggerRedraw()
233 def OnUseBuffer(self
, evt
):
234 self
.rose
.useBuffer
= evt
.IsChecked()
235 self
.rose
.TriggerRedraw()
237 def OnSetFG(self
, evt
):
238 self
.rose
.SetForegroundColour(evt
.GetValue())
239 self
.rose
.TriggerRedraw()
241 def OnSetBG(self
, evt
):
242 self
.rose
.SetBackgroundColour(evt
.GetValue())
243 self
.rose
.TriggerRedraw()
246 # MyFrame is the traditional class name to create and populate the
247 # application's frame. The general GUI has control/status panels on
248 # the right side and a panel on the left side that draws the rose
250 # This class also derives from clroses.rose so it can implement the
251 # required interfaces to connect the GUI to the rose engine.
252 class MyFrame(wx
.Frame
, clroses
.rose
):
253 # Color matching dictionary, convert label name to color:
254 # Stop and Go ala traffic lights,
255 # Skip and Forward look ahead to the purple mountain majesties (really bluish),
256 # Reverse and Backward look morosely behind to maroon memories,
257 # Redraw looks at the brown earth right below your feet.
258 # Yeah, so it's lame. All I really wanted was to color Stop and Go.
260 'Go': 'dark green', 'Stop': 'red',
261 'Redraw': 'brown', 'Skip': 'dark slate blue',
262 'Backward': 'maroon', 'Forward': 'dark slate blue', 'Reverse': 'maroon'
266 def makeSP(name
, labels
, statictexts
= None):
267 panel
= wx
.Panel(self
.side_panel
, -1)
268 box
= wx
.StaticBox(panel
, -1, name
)
269 sizer
= wx
.StaticBoxSizer(box
, wx
.VERTICAL
)
270 for name
, min_value
, value
, max_value
in labels
:
271 sp
= SpinPanel(panel
, name
, min_value
, value
, max_value
, self
.OnSpinback
)
272 sizer
.Add(sp
, 0, wx
.EXPAND
)
274 for name
, text
in statictexts
:
275 st
= wx
.StaticText(panel
, -1, text
)
276 spin_panels
[name
] = st
# Supposed to be a SpinPanel....
277 sizer
.Add(st
, 0, wx
.EXPAND
)
278 panel
.SetSizer(sizer
)
281 wx
.Frame
.__init
__(self
, None, title
="Roses in wxPython")
283 self
.rose_panel
= RosePanel(self
)
284 self
.side_panel
= wx
.Panel(self
)
286 # The cmd panel is four buttons whose names and foreground colors
287 # change. Plop them in a StaticBox like the SpinPanels. Use
288 # a 2x2 grid, but StaticBoxSizer can't handle that. Therefore,
289 # create a sub panel, layout the buttons there, then give that to
290 # a higher panel that has the static box stuff.
291 self
.cmd_panel
= wx
.Panel(self
.side_panel
, -1)
292 self
.sub_panel
= wx
.Panel(self
.cmd_panel
, -1)
293 sizer
= wx
.GridSizer(rows
= 2, cols
= 2)
295 border
= 'wxMac' in wx
.PlatformInfo
and 3 or 1
296 for name
, handler
in (
297 ('Go', self
.OnGoStop
),
298 ('Redraw', self
.OnRedraw
),
299 ('Backward', self
.OnBackward
),
300 ('Forward', self
.OnForward
)):
301 button
= wx
.Button(self
.sub_panel
, -1, name
)
302 button
.SetForegroundColour(self
.labelColours
[name
])
303 ctrl_buttons
[name
] = button
304 button
.Bind(wx
.EVT_BUTTON
, handler
)
305 sizer
.Add(button
, 0, wx
.EXPAND|wx
.ALL
, border
)
306 self
.sub_panel
.SetSizer(sizer
)
308 # Set up cmd_panel with StaticBox stuff
309 box
= wx
.StaticBox(self
.cmd_panel
, -1, 'Command')
310 sizer
= wx
.StaticBoxSizer(box
, wx
.VERTICAL
)
311 sizer
.Add(self
.sub_panel
)
312 self
.cmd_panel
.SetSizer(sizer
)
314 # Now make the rest of the control panels...
315 # The order of creation of SpinCtrls and Buttons is the order that
316 # the tab key will step through, so the order of panel creation is
318 # In the SpinPanel data (name, min, value, max), value will be
319 # overridden by clroses.py defaults.
320 self
.coe_panel
= makeSP('Coefficient',
321 (('Style', 0, 100, 3600),
322 ('Sincr', -3600, -1, 3600),
323 ('Petal', 0, 2, 3600),
324 ('Pincr', -3600, 1, 3600)))
326 self
.vec_panel
= makeSP('Vector',
327 (('Vectors' , 1, 399, 3600),
328 ('Minimum' , 1, 1, 3600),
329 ('Maximum' , 1, 3600, 3600),
330 ('Skip first', 0, 0, 3600),
331 ('Draw only' , 1, 3600, 3600)),
332 (('Takes', 'Takes 0000 vectors'), ))
334 self
.tim_panel
= makeSP('Timing',
335 (('Vec/tick' , 1, 20, 3600),
336 ('msec/tick', 1, 50, 1000),
337 ('Delay' , 1, 2000, 9999)))
339 self
.opt_panel
= OptionsPanel(self
.side_panel
, self
.rose_panel
)
341 # put them all on in a sizer attached to the side_panel
342 panelSizer
= wx
.BoxSizer(wx
.VERTICAL
)
343 panelSizer
.Add(self
.cmd_panel
, 0, wx
.EXPAND|wx
.TOP|wx
.LEFT|wx
.RIGHT
, 5)
344 panelSizer
.Add(self
.coe_panel
, 0, wx
.EXPAND|wx
.TOP|wx
.LEFT|wx
.RIGHT
, 5)
345 panelSizer
.Add(self
.vec_panel
, 0, wx
.EXPAND|wx
.TOP|wx
.LEFT|wx
.RIGHT
, 5)
346 panelSizer
.Add(self
.tim_panel
, 0, wx
.EXPAND|wx
.TOP|wx
.LEFT|wx
.RIGHT
, 5)
347 panelSizer
.Add(self
.opt_panel
, 0, wx
.EXPAND|wx
.TOP|wx
.LEFT|wx
.RIGHT
, 5)
348 self
.side_panel
.SetSizer(panelSizer
)
350 # and now arrange the two main panels in another sizer for the frame
351 mainSizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
352 mainSizer
.Add(self
.rose_panel
, 1, wx
.EXPAND
)
353 mainSizer
.Add(self
.side_panel
, 0, wx
.EXPAND
)
354 self
.SetSizer(mainSizer
)
356 # bind event handlers
357 self
.timer
= wx
.Timer(self
)
358 self
.Bind(wx
.EVT_TIMER
, self
.OnTimer
, self
.timer
)
360 # Determine appropriate image size.
361 # At this point, the rose_panel and side_panel will both report
362 # size (20, 20). After mainSizer.Fit(self) they will report the
363 # same, but the Frame size, self.GetSize(), will report the desired
364 # side panel dimensions plus an extra 20 on the width. That lets
365 # us determine the frame size that will display the side panel and
366 # a square space for the diagram. Only after Show() will the two
367 # panels report the accurate sizes.
369 rw
, rh
= self
.rose_panel
.GetSize()
370 sw
, sh
= self
.side_panel
.GetSize()
371 fw
, fh
= self
.GetSize()
372 h
= max(600, fh
) # Change 600 to desired minimum size
375 print 'rose panel size', (rw
, rh
)
376 print 'side panel size', (sw
, sh
)
377 print ' frame size', (fw
, fh
)
378 print 'Want size', (w
,h
)
380 self
.SupplyControlValues() # Ask clroses to tell us all the defaults
383 # Command button event handlers. These are relabled when changing between auto
384 # and manual modes. They simply reflect the call to a method in the base class.
387 def OnGoStop(self
, event
):
393 def OnRedraw(self
, event
):
399 def OnBackward(self
, event
):
405 def OnForward(self
, event
):
411 # The clroses.roses class expects to have methods available that
412 # implement the missing parts of the functionality needed to do
413 # the actual work of getting the diagram to the screen and etc.
414 # Those are implemented here as the App* methods.
418 print 'AppClear: clear screen'
419 self
.rose_panel
.Clear()
421 def AppCreateLine(self
, line
):
422 # print 'AppCreateLine, len', len(line), 'next', self.nextpt
423 self
.rose_panel
.DrawLines(line
)
425 # Here when clroses has set a new style and/or petal value, update
426 # strings on display.
427 def AppSetParam(self
, style
, petals
, vectors
):
428 spin_panels
['Style'].SetValue(style
)
429 spin_panels
['Petal'].SetValue(petals
)
430 spin_panels
['Vectors'].SetValue(vectors
)
432 def AppSetIncrs(self
, sincr
, pincr
):
433 spin_panels
['Sincr'].SetValue(sincr
)
434 spin_panels
['Pincr'].SetValue(pincr
)
436 def AppSetVectors(self
, vectors
, minvec
, maxvec
, skipvec
, drawvec
):
437 spin_panels
['Vectors'].SetValue(vectors
)
438 spin_panels
['Minimum'].SetValue(minvec
)
439 spin_panels
['Maximum'].SetValue(maxvec
)
440 spin_panels
['Skip first'].SetValue(skipvec
)
441 spin_panels
['Draw only'].SetValue(drawvec
)
443 def AppSetTakesVec(self
, takes
):
444 spin_panels
['Takes'].SetLabel('Takes %d vectors' % takes
)
446 # clroses doesn't change this data, so it's not telling us something
447 # we don't already know.
448 def AppSetTiming(self
, vecPtick
, msecPtick
, delay
):
449 spin_panels
['Vec/tick'].SetValue(vecPtick
)
450 spin_panels
['msec/tick'].SetValue(msecPtick
)
451 spin_panels
['Delay'].SetValue(delay
)
453 # Command buttons change their names based on the whether we're in auto
455 def AppCmdLabels(self
, labels
):
456 for name
, label
in map(None, ('Go', 'Redraw', 'Backward', 'Forward'), labels
):
457 ctrl_buttons
[name
].SetLabel(label
)
458 ctrl_buttons
[name
].SetForegroundColour(self
.labelColours
[label
])
461 # Timer methods. The paranoia about checking up on the callers is
462 # primarily because it's easier to check here. We expect that calls to
463 # AppAfter and OnTimer alternate, but don't verify that AppCancelTimer()
464 # is canceling anything as callers of that may be uncertain about what's
467 # Method to provide a single callback after some amount of time.
468 def AppAfter(self
, msec
, callback
):
469 if self
.timer_callback
:
470 print 'AppAfter: timer_callback already set!',
471 # print 'AppAfter:', callback
472 self
.timer_callback
= callback
473 self
.timer
.Start(msec
, True)
475 # Method to cancel something we might be waiting for but have lost
477 def AppCancelTimer(self
):
479 # print 'AppCancelTimer'
480 self
.timer_callback
= None
482 # When the timer happens, we come here and jump off to clroses internal code.
483 def OnTimer(self
, evt
):
484 callback
= self
.timer_callback
485 self
.timer_callback
= None
486 # print 'OnTimer,', callback
488 callback() # Often calls AppAfter() and sets the callback
490 print 'OnTimer: no callback!'
495 def TriggerResize(self
, size
):
496 self
.resize(size
, self
.resize_delay
)
497 self
.resize_delay
= 100
499 def TriggerRedraw(self
):
502 # Called when data in spin boxes changes.
503 def OnSpinback(self
, name
, value
):
505 print 'OnSpinback', name
, value
508 elif name
== 'Sincr':
510 elif name
== 'Petal':
511 self
.SetPetals(value
)
512 elif name
== 'Pincr':
515 elif name
== 'Vectors':
516 self
.SetVectors(value
)
517 elif name
== 'Minimum':
518 self
.SetMinVec(value
)
519 elif name
== 'Maximum':
520 self
.SetMaxVec(value
)
521 elif name
== 'Skip first':
522 self
.SetSkipFirst(value
)
523 elif name
== 'Draw only':
524 self
.SetDrawOnly(value
)
526 elif name
== 'Vec/tick':
528 elif name
== 'msec/tick':
529 self
.SetDrawDelay(value
)
530 elif name
== 'Delay':
531 self
.SetWaitDelay(value
)
533 print 'OnSpinback: Don\'t recognize', name
535 verbose
= 0 # Need some command line options...
536 spin_panels
= {} # Hooks to get from rose to panel labels
537 ctrl_buttons
= {} # Button widgets for command (NE) panel
542 print 'spin_panels', spin_panels
.keys()
543 print 'ctrl_buttons', ctrl_buttons
.keys()