# Cut and paste for interactive use: from tkroses import * # This is yet another incarnation of an old graphics hack based around # misdrawing an analytic geometry curve calld 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! # This program is derived from the first significant Python program I wrote, # wow, back in 2001 to Python 1.5.2 and without know a heck of a lot about # Object-Oriented programming beyond C things like X11 and ONC RPC/NFS. I # had written a Java version during a Java class, somewhere around 1995. # It's easy to argue I still don't know much about OOP and still write Python # code like a C programmer.... from Tkinter import * import clroses from math import sin, cos, pi import sys # The AppInterface class extends (finishes?) the rose class with architecturally # specific methods only the application can provide. Method names that start # with "App" are called by clroses. class AppInterface(clroses.rose): def __init__(self, win, canvas): self.win = win self.canvas = canvas self.after = None def AppClear(self): print 'App.clear: clear screen' self.canvas.delete("vec") def AppCreateLine(self, line, fill = 'green'): # print 'AppCreateLine, len', len(line), 'next', self.nextpt self.canvas.create_line(line, fill = "green", tags = "vec") # Here when clroses has set a new style and/or petal value, update # strings on display. def AppSetParam(self, style, petals, vectors): set_ent('Style', style) set_ent('Petal', petals) set_ent('Vec', vectors) def AppSetIncrs(self, sincr, pincr): set_ent('Sincr', sincr) set_ent('Pincr', pincr) # Not used at the moment. def AppSetTiming(self, vecPtick, msecPtick, delay): set_ent('Vec/tick', vecPtick) set_ent('msec/tick', msecPtick) set_ent('Delay', 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].configure(text=label) # Timer methods. # Method to provide a single callback after some amount of time. def AppAfter(self, msec, callback): self.timer_callback = callback self.after = self.win.after(msec, self.OnTimer) # Method to cancel something we might be waiting for but have lost interest in. def AppCancelTimer(self): print 'AppCancelTimer', self.after if self.after: self.win.after_cancel(self.after) # When the timer happens, we come here and jump off to roses internal code. def OnTimer(self): # print 'OnTimer', self.nextpt, 'delay', get_ent('Delay') self.timer_callback() return # Catch events that we need to use to redraw the pattern. Resize happens at # both window resizing and at startup, so it's a convenient way to draw the # first pattern. We seem to need a bit of time at startup or else our screen # clear and first vectors get wiped out. Hence the 300 msec at first. def ResizeHook(self, win): # Currently not handled. Probably never will be! return # Routine to pick out a number from a string variable without complaint # about ill-formed numbers def get_ent(name): try: val = int(vars[name][2].get()) except ValueError: 'Bad value in', name val = 1 return val # Might as well have one to set things too. def set_ent(name, val): vars[name][2].set(val) # This routine is to set the focus on a particular datum from an alphabetic # accelerator. Needs work - the character makes it into the displayed data # too. def set_focus(event): ch = event.char print 'Focus: ', ch focus[ch].focus() # Number field change callback for values that affect appearance. # Start drawing a new rose. def vis_chg(args): print 'vis_chg' rosesApp.SetStyle(get_ent('Style')) rosesApp.SetPetals(get_ent('Petal')) rosesApp.SetVectors(get_ent('Vec')) rosesApp.SetStep(get_ent('Vec/tick')) rosesApp.SetDrawDelay(get_ent('msec/tick')) # Number field change callback for values that affect automatic stepping. # Update the class's idea of what we want but don't trigger a new rose. def auto_chg(args): print 'auto_chg', args rosesApp.SetSincr(get_ent('Sincr')) rosesApp.SetPincr(get_ent('Pincr')) rosesApp.SetWaitDelay(get_ent('Delay')) def OnTimer(): self = rosesApp print 'OnTimer', self.nextpt self.timer_callback() return win = Tk() size = 700 c = Canvas(win, width = size, height = size, bg="gray20") rosesApp = AppInterface(win, c) true, false = 1, 0 vars = {} focus = {} ctrl_buttons = {} # Button widgets for command (NE) panel row = 0 # Setup control boxes in the four corners. Each tuple is a command, either # for a new frame ('f') with x, y, and corner or a tuple with # label name, method group name, initial value, and accelerator. for i in [ ('f', 0, 0, NW), ('Style', 'vis', 100, 's'), ('Sincr', 'auto', -1), \ ('Petal', 'vis', 2, 'p'), ('Pincr', 'auto', 1), \ ('f', 0, size, SW), ('Vec', 'vis', 399, 'v'), ('Vmin', 'notyet', 1), \ ('Vmax', 'notyet', 399), \ ('f', size, size, SE), ('Vec/tick', 'vis', 20, 't'), \ ('msec/tick', 'vis', 50, 'm'), ('Delay', 'auto', 2000, 'd') ]: if i[0] == 'f': fw = Frame(win, bg="gray20") c.create_window(i[1], i[2], window=fw, anchor=i[3]) else: il = Label(fw, text=i[0]) iv = StringVar() iv.set(i[2]) ie = Entry(fw, textvariable=iv, width=5) ie.svar = iv if i[1] == 'vis': ie.bind('', vis_chg) elif i[1] == 'auto': ie.bind('', auto_chg) il.grid(row=row, column=0) ie.grid(row=row, column=1) if len(i) >= 4: # Accelerator to move focus to entry widget focus[i[3]] = ie win.bind('<'+i[3]+'>', set_focus) vars[i[0]] = (il, ie, iv) row = row + 1 # Place stoplight buttons in upper right fw = Frame(win, bg="gray20") c.create_window(size, 0, window=fw, anchor=NE) ctrl_buttons[ 'Redraw'] = Button(fw, text= 'Redraw', command=rosesApp.cmd_stop, bg='red') ctrl_buttons['Forward'] = Button(fw, text='Forward', command=rosesApp.cmd_step, bg='yellow') ctrl_buttons[ 'Go'] = Button(fw, text= 'Go', command=rosesApp.cmd_go, bg='green') ctrl_buttons[ 'Back'] = Button(fw, text= 'Back', command=rosesApp.cmd_reverse, bg='blue') ctrl_buttons[ 'Redraw'].pack(anchor=E) ctrl_buttons['Forward'].pack(anchor=E) ctrl_buttons[ 'Go'].pack(anchor=E) ctrl_buttons[ 'Back'].pack(anchor=E) c.pack() rosesApp.resize((size, size), 300) auto_chg('foo') vis_chg('foo') print "Calling win.mainloop()" win.mainloop()