From 1779d8df50410d975c927d2fbd2a1eb00414913d Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Mon, 11 Jun 2007 20:40:43 +0000 Subject: [PATCH] Added wxRoses sample from Ric Werme git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@46408 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- wxPython/samples/roses/clroses.py | 371 +++++++++++++++++++++++ wxPython/samples/roses/wxroses.py | 469 ++++++++++++++++++++++++++++++ 2 files changed, 840 insertions(+) create mode 100644 wxPython/samples/roses/clroses.py create mode 100644 wxPython/samples/roses/wxroses.py diff --git a/wxPython/samples/roses/clroses.py b/wxPython/samples/roses/clroses.py new file mode 100644 index 0000000000..238b1c95fb --- /dev/null +++ b/wxPython/samples/roses/clroses.py @@ -0,0 +1,371 @@ +#---------------------------------------------------------------------------- +# Name: clroses.py +# Purpose: Class definitions for Roses interactive display programs. +# +# Author: Ric Werme +# WWW: http://WermeNH.com/roses +# +# Created: June 2007 +# CVS-ID: $Id$ +# Copyright: Public Domain, please give credit where credit is due. +# License: Sorry, no EULA. +#---------------------------------------------------------------------------- + +# This is yet another incarnation of an old graphics hack based around +# misdrawing an analytic geometry curve called a rose. The basic form is +# simply the polar coordinate function r = cos(a * theta). "a" is the +# "order" of the rose, a zero value degenerates to r = 1, a circle. While +# this program is happy to draw that, much more interesting things happen when +# one or more of the following is in effect: + +# 1) The "delta theta" between points is large enough to distort the curve, +# e.g. 90 degrees will draw a square, slightly less will be interesting. + +# 2) The order of the rose is too large to draw it accurately. + +# 3) Vectors are drawn at less than full speed. + +# 4) The program is stepping through different patterns on its own. + +# While you will be able to predict some aspects of the generated patterns, +# a lot of what there is to be found is found at random! + +# The rose class has all the knowledge to implement generating vector data for +# roses and handles all the timing issues. It does not have the user interface +# for changing all the drawing parameters. It offers a "vision" of what an +# ideal Roses program should be, however, callers are welcome to assert their +# independence, override defaults, ignore features, etc. + +from math import sin, cos, pi + +# Rose class knows about: +# > Generating points and vectors (returning data as a list of points) +# > Starting a new rose (e.g. telling user to erase old vectors) +# > Stepping from one pattern to the next. + +class rose: + "Defines everything needed for drawing a rose with timers." + + # The following data is accessible by callers, but there are set + # methods for most everything and various method calls to client methods + # to display current values. + style = 100 # Angular distance along curve between points + sincr = -1 # Amount to increment style by in auto mode + petals = 2 # Lobes on the rose (even values have 2X lobes) + pincr = 1 # Amount to increment petals by in auto mode + nvec = 399 # Number of vectors to draw the rose + minvec = 0 # Minimum number acceptable in automatic mode + maxvec = 3600 # Maximum number acceptable in automatic mode + skipvec = 0 # Don't draw this many at the start (cheap animations) + drawvec = 3600 # Draw only this many (cheap animations) + step = 20 # Number of vectors to draw each clock tick + draw_delay = 50 # Time between roselet calls to watch pattern draw + wait_delay = 2000 # Time between roses in automatic mode + + # Other variables that the application shouldn't access. + verbose = 0 # No good way to set this at the moment. + nextpt = 0 # Next position to draw on next clock tick + + # Internal states: + INT_IDLE, INT_DRAW, INT_SEARCH, INT_WAIT, INT_RESIZE = range(5) + int_state = INT_IDLE + + # Command states + CMD_STOP, CMD_GO = range(2) + cmd_state = CMD_STOP + + # Return full rose line (a tuple of (x, y) tuples). Not used by interactive + # clients but still useful for command line and batch clients. + # This is the "purest" code and doesn't require the App* methods defined + # by the caller. + def rose(self, style, petals, vectors): + self.nvec = vectors + self.make_tables(vectors) + line = [(1.0, 0.0)] + for i in range (1, vectors): + theta = (style * i) % vectors + r = self.cos_table[(petals * theta) % vectors] + line.append((r * self.cos_table[theta], r * self.sin_table[theta])) + line.append((1.0, 0.0)) + return line + + # Generate vectors for the next chunk of rose. + + # This is not meant to be called from an external module, as it is closely + # coupled to parameters set up within the class and limits set up by + # restart(). Restart() initializes all data this needs to start drawing a + # pattern, and clock() calls this to compute the next batch of points and + # hear if that is the last batch. We maintain all data we need to draw each + # batch after the first. theta should be 2.0*pi * style*i/self.nvec + # radians, but we deal in terms of the lookup table so it's just the index + # that refers to the same spot. + def roselet(self): + line = [] + stop = self.nextpt + self.step + keep_running = True + if stop > self.endpt: + stop = self.endpt + keep_running = False + for i in range (self.nextpt, stop + 1): + theta = (self.style * i) % self.nvec + r = self.cos_table[(self.petals * theta) % self.nvec] + line.append((r * self.cos_table[theta], r * self.sin_table[theta])) + self.nextpt = stop + return line, keep_running + + # Generate sine and cosine lookup tables. We could create data for just + # 1/4 of a circle, at least if vectors was a multiple of 4, and share a + # table for both sine and cosine, but memory is cheaper than it was in + # PDP-11 days. OTOH, small, shared tables would be more cache friendly, + # but if we were that concerned, this would be in C. + def make_tables(self, vectors): + self.sin_table = [sin(2.0 * pi * i / vectors) for i in range(vectors)] + self.cos_table = [cos(2.0 * pi * i / vectors) for i in range(vectors)] + + # Rescale (x,y) data to match our window. + def rescale(self, line, offset, scale): + for i in range(len(line)): + line[i] = (line[i][0] * scale + offset[0], line[i][1] * scale + offset[1]) + return line + + # Euler's Method for computing the greatest common divisor. Knuth's + # "The Art of Computer Programming" vol.2 is the standard reference, + # but the web has several good ones too. Basically this sheds factors + # that aren't in the GCD and returns when there's nothing left to shed. + # N.B. Call with a >= b. + def gcd(self, a, b): + while b != 0: + a, b = b, a % b + return a + + # Erase any old vectors and start drawing a new rose. When the program + # starts, the sine and cosine tables don't exist, build them here. (Of + # course, if an __init__() method is added, move the call there. + # If we're in automatic mode, check to see if the new pattern has neither + # too few or too many vectors and skip it if so. Skip by setting up for + # a one tick wait to let us get back to the main loop so the user can + # update parameters or stop. + def restart(self): + if self.verbose: + print 'restart: int_state', self.int_state, 'cmd_state', self.cmd_state + try: + tmp = self.sin_table[0] + except: + self.make_tables(self.nvec) + + new_state = self.INT_DRAW + self.takesvec = self.nvec / self.gcd(self.nvec, self.style) + if not self.takesvec & 1 and self.petals & 1: + self.takesvec /= 2 + if self.cmd_state == self.CMD_GO: + if self.minvec > self.takesvec or self.maxvec < self.takesvec: + new_state = self.INT_SEARCH + self.AppSetTakesVec(self.takesvec) + self.AppClear() + self.nextpt = self.skipvec + self.endpt = min(self.takesvec, self.skipvec + self.drawvec) + old_state, self.int_state = self.int_state, new_state + if old_state == self.INT_IDLE: # Clock not running + self.clock() + elif old_state == self.INT_WAIT: # May be long delay, restart + self.AppCancelTimer() + self.clock() + else: + return 1 # If called by clock(), return and start clock + return 0 # We're in INT_IDLE or INT_WAIT, clock running + + # Called from App. Recompute the center and scale values for the subsequent pattern. + # Force us into INT_RESIZE state if not already there so that in 100 ms we'll start + # to draw something to give an idea of the new size. + def resize(self, size, delay): + xsize, ysize = size + self.center = (xsize / 2, ysize / 2) + self.scale = min(xsize, ysize) / 2.1 + self.repaint(delay) + + # Called from App or above. From App, called with small delay because + # some window managers will produce a flood of expose events or call us + # before initialization is done. + def repaint(self, delay): + if self.int_state != self.INT_RESIZE: + # print 'repaint after', delay + self.int_state = self.INT_RESIZE + self.AppCancelTimer() + self.AppAfter(delay, self.clock) + + # Method that returns the next style and petal values for automatic + # mode and remembers them internally. Keep things scaled in the + # range [0:nvec) because there's little reason to exceed that. + def next(self): + self.style += self.sincr + self.petals += self.pincr + if self.style <= 0 or self.petals < 0: + self.style, self.petals = \ + abs(self.petals) + 1, abs(self.style) + if self.style >= self.nvec: + self.style %= self.nvec # Don't bother defending against 0 + if self.petals >= self.nvec: + self.petals %= self.nvec + self.AppSetParam(self.style, self.petals, self.nvec) + + # Resume pattern drawing with the next one to display. + def resume(self): + self.next() + return self.restart() + + # Stop/Redraw button. + def cmd_stop(self): + if self.cmd_state == self.CMD_STOP: + self.restart() # Redraw current pattern + elif self.cmd_state == self.CMD_GO: + self.cmd_state = self.CMD_STOP + self.update_labels() + + # Skip/Forward button. CMD_STOP & CMD_GO both just call resume. + def cmd_step(self): + # print 'cmd_step, cmd_state', self.cmd_state + self.resume() # Draw next pattern + + # Go/Redraw button + def cmd_go(self): + if self.cmd_state == self.CMD_STOP: + self.cmd_state = self.CMD_GO + self.update_labels() + self.resume() # Draw next pattern + elif self.cmd_state == self.CMD_GO: + self.restart() # Redraw current pattern + + # Centralize button naming to share with initialization. + def update_labels(self): + if self.cmd_state == self.CMD_STOP: + self.AppCmdLabels(('Redraw', 'Forward', 'Go', 'Back')) + else: # Must be in state CMD_GO + self.AppCmdLabels(('Stop', 'Skip', 'Redraw', 'Reverse')) + + # Reverse button. Useful for when you see an interesting pattern and want + # to go back to it. If running, just change direction. If stopped, back + # up one step. The resume code handles the step, then we change the + # incrementers back to what they were. (Unless resume changed them too.) + def cmd_reverse(self): + self.sincr = -self.sincr + self.pincr = -self.pincr + if self.cmd_state == self.CMD_STOP: + self.resume(); + self.sincr = -self.sincr # Go forward again + self.pincr = -self.pincr + else: + self.AppSetIncrs(self.sincr, self.pincr) + + # Handler called on each timer event. This handles the metered drawing + # of a rose and the delays between them. It also registers for the next + # timer event unless we're idle (rose is done and the delay between + # roses is 0.) + def clock(self): + if self.int_state == self.INT_IDLE: + # print 'clock called in idle state' + delay = 0 + elif self.int_state == self.INT_DRAW: + line, run = self.roselet() + self.AppCreateLine(self.rescale(line, self.center, self.scale)) + if run: + delay = self.draw_delay + else: + if self.cmd_state == self.CMD_GO: + self.int_state = self.INT_WAIT + delay = self.wait_delay + else: + self.int_state = self.INT_IDLE + delay = 0 + elif self.int_state == self.INT_SEARCH: + delay = self.resume() # May call us to start drawing + if self.int_state == self.INT_SEARCH: + delay = self.draw_delay # but not if searching. + elif self.int_state == self.INT_WAIT: + if self.cmd_state == self.CMD_GO: + delay = self.resume() # Calls us to start drawing + else: + self.int_state = self.INT_IDLE + delay = 0 + elif self.int_state == self.INT_RESIZE: # Waiting for resize event stream to settle + self.AppSetParam(self.style, self.petals, self.nvec) + self.AppSetIncrs(self.sincr, self.pincr) + delay = self.restart() # Calls us to start drawing + + if delay == 0: + if self.verbose: + print 'clock: going idle from state', self.int_state + else: + self.AppAfter(delay, self.clock) + + # Methods to allow App to change the parameters on the screen. + # These expect to be called when the associated paramenter changes, + # but work reasonably well if several are called at once. (E.g. + # tkroses.py groups them into things that affect the visual display + # and warrant a new start, and things that just change and don't affect + # the ultimate pattern. All parameters within a group are updated + # at once even if the value hasn't changed. + + # We restrict the style and petals parameters to the range [0: nvec) + # since numbers outside of that range aren't interesting. We don't + # immediately update the value in the application, we probably should. + + # NW control window - key parameters + def SetStyle(self, value): + self.style = value % self.nvec + self.restart() + + def SetSincr(self, value): + self.sincr = value + + def SetPetals(self, value): + self.petals = value % self.nvec + self.restart() + + def SetPincr(self, value): + self.pincr = value + + + # SW control window - vectors + def SetVectors(self, value): + self.nvec = value + self.style %= value + self.petals %= value + self.AppSetParam(self.style, self.petals, self.nvec) + self.make_tables(value) + self.restart() + + def SetMinVec(self, value): + if self.maxvec >= value and self.nvec >= value: + self.minvec = value + + def SetMaxVec(self, value): + if self.minvec < value: + self.maxvec = value + + def SetSkipFirst(self, value): + self.skipvec = value + self.restart() + + def SetDrawOnly(self, value): + self.drawvec = value + self.restart() + + + # SE control window - timings + def SetStep(self, value): + self.step = value + + def SetDrawDelay(self, value): + self.draw_delay = value + + def SetWaitDelay(self, value): + self.wait_delay = value + + # Method for client to use to have us supply our defaults. + def SupplyControlValues(self): + self.update_labels() + self.AppSetParam(self.style, self.petals, self.nvec) + self.AppSetIncrs(self.sincr, self.pincr) + self.AppSetVectors(self.nvec, self.minvec, self.maxvec, + self.skipvec, self.drawvec) + self.AppSetTiming(self.step, self.draw_delay, self.wait_delay) diff --git a/wxPython/samples/roses/wxroses.py b/wxPython/samples/roses/wxroses.py new file mode 100644 index 0000000000..535d1ca7df --- /dev/null +++ b/wxPython/samples/roses/wxroses.py @@ -0,0 +1,469 @@ +#---------------------------------------------------------------------------- +# Name: wxroses.py +# Purpose: wxPython GUI using clroses.py to display a classic graphics +# hack. +# +# Author: Ric Werme, Robin Dunn. +# WWW: http://WermeNH.com/roses +# +# Created: June 2007 +# CVS-ID: $Id$ +# Copyright: Public Domain, please give credit where credit is due. +# License: Sorry, no EULA. +#---------------------------------------------------------------------------- + +# This module is responsible for everything involving GUI usage +# as clroses knows nothing about wxpython, tkintr, etc. + +import wx +import clroses +import wx.lib.colourselect as cs + + +# Class SpinPanel creates a control that includes both a StaticText widget +# which holds the the name of a parameter and a SpinCtrl widget which +# displays the current value. Values are set at initialization and can +# change via the SpinCtrl widget or by the program. So that the program +# can easily access the SpinCtrl, the SpinPanel handles are saved in the +# spin_panels dictionary. +class SpinPanel(wx.Panel): + def __init__(self, parent, name, min_value, value, max_value, callback): + wx.Panel.__init__(self, parent, -1) + if "wxMac" in wx.PlatformInfo: + self.SetWindowVariant(wx.WINDOW_VARIANT_SMALL) + self.st = wx.StaticText(self, -1, name) + self.sc = wx.SpinCtrl(self, -1, "", size = (70, -1)) + self.sc.SetRange(min_value, max_value) + self.sc.SetValue(value) + self.sc.Bind(wx.EVT_SPINCTRL, self.OnSpin) + self.callback = callback + + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(self.st, 0, wx.ALIGN_CENTER_VERTICAL) + sizer.Add((1,1), 1) + sizer.Add(self.sc) + self.SetSizer(sizer) + + global spin_panels + spin_panels[name] = self + + # Called (generally through spin_panels{}) to set the SpinCtrl value. + def SetValue(self, value): + self.sc.SetValue(value) + + # Called when user changes the SpinCtrl value. + def OnSpin(self, event): + name = self.st.GetLabel() + value = self.sc.GetValue() + if verbose: + print 'OnSpin', name, '=', value + self.callback(name, value) # Call MyFrame.OnSpinback to call clroses + + +# This class is used to display the current rose diagram. It keeps a +# buffer bitmap of the current display, which it uses to refresh the +# screen with when needed. When it is told to draw some lines it does +# so to the buffer in order for it to always be up to date. +class RosePanel(wx.Panel): + def __init__(self, *args, **kw): + wx.Panel.__init__(self, *args, **kw) + self.InitBuffer() + self.resizeNeeded = False + self.useGCDC = False + self.useBuffer = True + + # set default colors + self.SetBackgroundColour((51,51,51)) # gray20 + self.SetForegroundColour((164, 211, 238)) # lightskyblue2 + + # connect the size and paint events to handlers + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self.Bind(wx.EVT_IDLE, self.OnIdle) + + + def InitBuffer(self): + size = self.GetClientSize() + self.buffer = wx.EmptyBitmap(max(1, size.width), + max(1, size.height)) + + def Clear(self): + dc = self.useBuffer and wx.MemoryDC(self.buffer) or wx.ClientDC(self) + dc.SetBackground(wx.Brush(self.GetBackgroundColour())) + dc.Clear() + if self.useBuffer: + self.Refresh(False) + + def DrawLines(self, lines): + if len(lines) <= 1: + return + dc = self.useBuffer and wx.MemoryDC(self.buffer) or wx.ClientDC(self) + if self.useGCDC: + dc = wx.GCDC(dc) + dc.SetPen(wx.Pen(self.GetForegroundColour(), 1)) + dc.DrawLines(lines) + if self.useBuffer: + self.Refresh(False) + + def TriggerResize(self): + self.GetParent().TriggerResize(self.buffer.GetSize()) + + def TriggerRedraw(self): + self.GetParent().TriggerRedraw() + + def OnSize(self, evt): + self.resizeNeeded = True + + def OnIdle(self, evt): + if self.resizeNeeded: + self.InitBuffer() + self.TriggerResize() + if self.useBuffer: + self.Refresh() + self.resizeNeeded = False + + def OnPaint(self, evt): + dc = wx.PaintDC(self) + if self.useBuffer: + dc.DrawBitmap(self.buffer, 0,0) + else: + self.TriggerRedraw() + + +# A panel used to collect options on how the rose is drawn +class OptionsPanel(wx.Panel): + def __init__(self, parent, rose): + wx.Panel.__init__(self, parent) + self.rose = rose + sizer = wx.StaticBoxSizer(wx.StaticBox(self, label='Options'), + wx.VERTICAL) + self.useGCDC = wx.CheckBox(self, label="Use GCDC") + sizer.Add(self.useGCDC, 0, wx.BOTTOM|wx.LEFT, 2) + self.useBuffer = wx.CheckBox(self, label="Use buffering") + sizer.Add(self.useBuffer, 0, wx.BOTTOM|wx.LEFT, 2) + + def makeCButton(label): + btn = cs.ColourSelect(self, size=(20,22)) + lbl = wx.StaticText(self, -1, label) + sizer = wx.BoxSizer(wx.HORIZONTAL) + sizer.Add(btn) + sizer.Add((4,4)) + sizer.Add(lbl, 0, wx.ALIGN_CENTER_VERTICAL) + return sizer, btn + + s, self.fg = makeCButton('foreground') + sizer.Add(s) + s, self.bg = makeCButton('background') + sizer.Add(s) + self.SetSizer(sizer) + + self.Bind(wx.EVT_CHECKBOX, self.OnUseGCDC, self.useGCDC) + self.Bind(wx.EVT_CHECKBOX, self.OnUseBuffer, self.useBuffer) + self.Bind(wx.EVT_IDLE, self.OnIdle) + self.Bind(cs.EVT_COLOURSELECT, self.OnSetFG, self.fg) + self.Bind(cs.EVT_COLOURSELECT, self.OnSetBG, self.bg) + + def OnIdle(self, evt): + if self.useGCDC.GetValue() != self.rose.useGCDC: + self.useGCDC.SetValue(self.rose.useGCDC) + if self.useBuffer.GetValue() != self.rose.useBuffer: + self.useBuffer.SetValue(self.rose.useBuffer) + if self.fg.GetValue() != self.rose.GetForegroundColour(): + self.fg.SetValue(self.rose.GetForegroundColour()) + if self.bg.GetValue() != self.rose.GetBackgroundColour(): + self.bg.SetValue(self.rose.GetBackgroundColour()) + + def OnUseGCDC(self, evt): + self.rose.useGCDC = evt.IsChecked() + self.rose.TriggerRedraw() + + def OnUseBuffer(self, evt): + self.rose.useBuffer = evt.IsChecked() + self.rose.TriggerRedraw() + + def OnSetFG(self, evt): + self.rose.SetForegroundColour(evt.GetValue()) + self.rose.TriggerRedraw() + + def OnSetBG(self, evt): + self.rose.SetBackgroundColour(evt.GetValue()) + self.rose.TriggerRedraw() + + +# MyFrame is the traditional class name to create and populate the +# application's frame. The general GUI has control/status panels on +# the right side and a panel on the left side that draws the rose +# +# This class also derives from clroses.rose so it can implement the +# required interfaces to connect the GUI to the rose engine. +class MyFrame(wx.Frame, clroses.rose): + def __init__(self): + def makeSP(name, labels, statictexts = None): + panel = wx.Panel(self.side_panel, -1) + box = wx.StaticBox(panel, -1, name) + sizer = wx.StaticBoxSizer(box, wx.VERTICAL) + for name, min_value, value, max_value in labels: + sp = SpinPanel(panel, name, min_value, value, max_value, self.OnSpinback) + sizer.Add(sp, 0, wx.EXPAND) + if statictexts: + for name, text in statictexts: + st = wx.StaticText(panel, -1, text) + spin_panels[name] = st # Supposed to be a SpinPanel.... + sizer.Add(st, 0, wx.EXPAND) + panel.SetSizer(sizer) + return panel + + wx.Frame.__init__(self, None, title="Roses in wxPython") + + self.rose_panel = RosePanel(self) + self.side_panel = wx.Panel(self) + + # The cmd panel is four buttons whose names and foreground colors + # change. Plop them in a StaticBox like the SpinPanels. + self.cmd_panel = wx.Panel(self.side_panel, -1) + box = wx.StaticBox(self.cmd_panel, -1, 'Command') + sizer = wx.StaticBoxSizer(box, wx.VERTICAL) + global ctrl_buttons + border = 'wxMac' in wx.PlatformInfo and 3 or 0 + for name, color, handler in ( + ('Redraw', 'red', self.OnRedraw), + ('Forward', 'magenta', self.OnForward), + ('Go', 'dark green', self.OnGo), + ('Back', 'blue', self.OnBack)): + button = wx.Button(self.cmd_panel, -1, name) + button.SetForegroundColour(color) + ctrl_buttons[name] = button + button.Bind(wx.EVT_BUTTON, handler) + sizer.Add(button, 0, wx.EXPAND|wx.TOP|wx.BOTTOM, border) + self.cmd_panel.SetSizer(sizer) + + # Now make the rest of the control panels... + # The order of creation of SpinCtrls and Buttons is the order that + # the tab key will step through, so the order of panel creation is + # important. + # In the SpinPanel data (name, min, value, max), value will be + # overridden by clroses.py defaults. + self.coe_panel = makeSP('Coefficients', + (('Style', 0, 100, 3600), + ('Sincr', -3600, -1, 3600), + ('Petal', 0, 2, 3600), + ('Pincr', -3600, 1, 3600))) + + self.vec_panel = makeSP('Vector', + (('Vectors' , 1, 399, 3600), + ('Minimum' , 1, 1, 3600), + ('Maximum' , 1, 3600, 3600), + ('Skip first', 0, 0, 3600), + ('Draw only' , 1, 3600, 3600)), + (('Takes', 'Takes 0000 vectors'), )) + + self.tim_panel = makeSP('Timing', + (('Vec/tick' , 1, 20, 3600), + ('msec/tick', 1, 50, 1000), + ('Delay' , 1, 2000, 9999))) + + self.opt_panel = OptionsPanel(self.side_panel, self.rose_panel) + + # put them all on in a sizer attached to the side_panel + panelSizer = wx.BoxSizer(wx.VERTICAL) + panelSizer.Add(self.cmd_panel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5) + panelSizer.Add(self.coe_panel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5) + panelSizer.Add(self.vec_panel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5) + panelSizer.Add(self.tim_panel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5) + panelSizer.Add(self.opt_panel, 0, wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, 5) + self.side_panel.SetSizer(panelSizer) + + # and now arrange the two main panels in another sizer for the frame + mainSizer = wx.BoxSizer(wx.HORIZONTAL) + mainSizer.Add(self.rose_panel, 1, wx.EXPAND) + mainSizer.Add(self.side_panel, 0, wx.EXPAND) + self.SetSizer(mainSizer) + + # bind event handlers + self.timer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer) + + # Determine appropriate image size. + # At this point, the rose_panel and side_panel will both report + # size (20, 20). After mainSizer.Fit(self) they will report the + # same, but the Frame size, self.GetSize(), will report the desired + # side panel dimensions plus an extra 20 on the width. That lets + # us determine the frame size that will display the side panel and + # a square space for the diagram. Only after Show() will the two + # panels report the accurate sizes. + mainSizer.Fit(self) + rw, rh = self.rose_panel.GetSize() + sw, sh = self.side_panel.GetSize() + fw, fh = self.GetSize() + h = max(600, fh) # Change 600 to desired minimum size + w = h + fw - rw + if verbose: + print 'rose panel size', (rw, rh) + print 'side panel size', (sw, sh) + print ' frame size', (fw, fh) + print 'Want size', (w,h) + self.SetSize((w, h)) + self.SupplyControlValues() # Ask clroses to tell us all the defaults + self.Show() + + # Command button event handlers. These are relabled when changing between auto + # and manual modes. They simply reflect the call to a method in the base class. + # + # Stop/Redraw button + def OnRedraw(self, event): + if verbose: + print 'OnRedraw' + self.cmd_stop() + + # Skip/Forward + def OnForward(self, event): + if verbose: + print 'OnForward' + self.cmd_step() + + # Redraw/Go + def OnGo(self, event): + if verbose: + print 'OnGo' + self.cmd_go() + + # Reverse/Back + def OnBack(self, event): + if verbose: + print 'OnBack' + self.cmd_reverse() + + + # The clroses.roses class expects to have methods available that + # implement the missing parts of the functionality needed to do + # the actual work of getting the diagram to the screen and etc. + # Those are implemented here as the App* methods. + + def AppClear(self): + if verbose: + print 'AppClear: clear screen' + self.rose_panel.Clear() + + def AppCreateLine(self, line): + # print 'AppCreateLine, len', len(line), 'next', self.nextpt + self.rose_panel.DrawLines(line) + + # Here when clroses has set a new style and/or petal value, update + # strings on display. + def AppSetParam(self, style, petals, vectors): + spin_panels['Style'].SetValue(style) + spin_panels['Petal'].SetValue(petals) + spin_panels['Vectors'].SetValue(vectors) + + def AppSetIncrs(self, sincr, pincr): + spin_panels['Sincr'].SetValue(sincr) + spin_panels['Pincr'].SetValue(pincr) + + def AppSetVectors(self, vectors, minvec, maxvec, skipvec, drawvec): + spin_panels['Vectors'].SetValue(vectors) + spin_panels['Minimum'].SetValue(minvec) + spin_panels['Maximum'].SetValue(maxvec) + spin_panels['Skip first'].SetValue(skipvec) + spin_panels['Draw only'].SetValue(drawvec) + + def AppSetTakesVec(self, takes): + spin_panels['Takes'].SetLabel('Takes %d vectors' % takes) + + # clroses doesn't change this data, so it's not telling us something + # we don't already know. + def AppSetTiming(self, vecPtick, msecPtick, delay): + spin_panels['Vec/tick'].SetValue(vecPtick) + spin_panels['msec/tick'].SetValue(msecPtick) + spin_panels['Delay'].SetValue(delay) + + # Command buttons change their names based on the whether we're in auto + # or manual mode. + def AppCmdLabels(self, labels): + for name, label in map(None, ('Redraw', 'Forward', 'Go', 'Back'), labels): + ctrl_buttons[name].SetLabel(label) + + + # Timer methods. The paranoia about checking up on the callers is + # primarily because it's easier to check here. We expect that calls to + # AppAfter and OnTimer alternate, but don't verify that AppCancelTimer() + # is canceling anything as callers of that may be uncertain about what's + # happening. + + # Method to provide a single callback after some amount of time. + def AppAfter(self, msec, callback): + if self.timer_callback: + print 'AppAfter: timer_callback already set!', + # print 'AppAfter:', callback + self.timer_callback = callback + self.timer.Start(msec, True) + + # Method to cancel something we might be waiting for but have lost + # interest in. + def AppCancelTimer(self): + self.timer.Stop() + # print 'AppCancelTimer' + self.timer_callback = None + + # When the timer happens, we come here and jump off to clroses internal code. + def OnTimer(self, evt): + callback = self.timer_callback + self.timer_callback = None + # print 'OnTimer,', callback + if callback: + callback() # Often calls AppAfter() and sets the callback + else: + print 'OnTimer: no callback!' + + + resize_delay = 300 + + def TriggerResize(self, size): + self.resize(size, self.resize_delay) + self.resize_delay = 100 + + def TriggerRedraw(self): + self.repaint(10) + + # Called when data in spin boxes changes. + def OnSpinback(self, name, value): + if verbose: + print 'OnSpinback', name, value + if name == 'Style': + self.SetStyle(value) + elif name == 'Sincr': + self.SetSincr(value) + elif name == 'Petal': + self.SetPetals(value) + elif name == 'Pincr': + self.SetPincr(value) + + elif name == 'Vectors': + self.SetVectors(value) + elif name == 'Minimum': + self.SetMinVec(value) + elif name == 'Maximum': + self.SetMaxVec(value) + elif name == 'Skip first': + self.SetSkipFirst(value) + elif name == 'Draw only': + self.SetDrawOnly(value) + + elif name == 'Vec/tick': + self.SetStep(value) + elif name == 'msec/tick': + self.SetDrawDelay(value) + elif name == 'Delay': + self.SetWaitDelay(value) + else: + print 'OnSpinback: Don\'t recognize', name + +verbose = 0 # Need some command line options... +spin_panels = {} # Hooks to get from rose to panel labels +ctrl_buttons = {} # Button widgets for command (NE) panel + +app = wx.App(False) +MyFrame() +if verbose: + print 'spin_panels', spin_panels.keys() + print 'ctrl_buttons', ctrl_buttons.keys() +app.MainLoop()