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
This commit is contained in:
371
wxPython/samples/roses/clroses.py
Normal file
371
wxPython/samples/roses/clroses.py
Normal file
@@ -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)
|
469
wxPython/samples/roses/wxroses.py
Normal file
469
wxPython/samples/roses/wxroses.py
Normal file
@@ -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()
|
Reference in New Issue
Block a user