diff options
author | Guido van Rossum <guido@python.org> | 1997-04-03 00:04:51 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 1997-04-03 00:04:51 (GMT) |
commit | 9a8cb84072842d183c3614b2a232d1426bd8c870 (patch) | |
tree | ddb939385e496570d98810c96ad9b31dd0c90530 | |
parent | 387c575d5c2ec43bc96c6ef8f5647e28e053a314 (diff) | |
download | cpython-9a8cb84072842d183c3614b2a232d1426bd8c870.zip cpython-9a8cb84072842d183c3614b2a232d1426bd8c870.tar.gz cpython-9a8cb84072842d183c3614b2a232d1426bd8c870.tar.bz2 |
Checked in some new Tk demos that I wrote a while ago.
-rw-r--r-- | Demo/tkinter/guido/canvasevents.py | 244 | ||||
-rw-r--r-- | Demo/tkinter/guido/newmenubardemo.py | 42 | ||||
-rw-r--r-- | Demo/tkinter/guido/regexdemo.py | 127 | ||||
-rw-r--r-- | Demo/tkinter/guido/sortvisu.py | 634 |
4 files changed, 1047 insertions, 0 deletions
diff --git a/Demo/tkinter/guido/canvasevents.py b/Demo/tkinter/guido/canvasevents.py new file mode 100644 index 0000000..60f4096 --- /dev/null +++ b/Demo/tkinter/guido/canvasevents.py @@ -0,0 +1,244 @@ +#! /usr/bin/env python + +from Tkinter import * +from Canvas import Oval, Group, CanvasText + + +# Fix a bug in Canvas.Group as distributed in Python 1.4. The +# distributed bind() method is broken. This is what should be used: + +class Group(Group): + def bind(self, sequence=None, command=None): + return self.canvas.tag_bind(self.id, sequence, command) + +class Object: + + """Base class for composite graphical objects. + + Objects belong to a canvas, and can be moved around on the canvas. + They also belong to at most one ``pile'' of objects, and can be + transferred between piles (or removed from their pile). + + Objects have a canonical ``x, y'' position which is moved when the + object is moved. Where the object is relative to this position + depends on the object; for simple objects, it may be their center. + + Objects have mouse sensitivity. They can be clicked, dragged and + double-clicked. The behavior may actually determined by the pile + they are in. + + All instance attributes are public since the derived class may + need them. + + """ + + def __init__(self, canvas, x=0, y=0, fill='red', text='object'): + self.canvas = canvas + self.x = x + self.y = y + self.pile = None + self.group = Group(self.canvas) + self.createitems(fill, text) + + def __str__(self): + return str(self.group) + + def createitems(self, fill, text): + self.__oval = Oval(self.canvas, + self.x-20, self.y-10, self.x+20, self.y+10, + fill=fill, width=3) + self.group.addtag_withtag(self.__oval) + self.__text = CanvasText(self.canvas, + self.x, self.y, text=text) + self.group.addtag_withtag(self.__text) + + def moveby(self, dx, dy): + if dx == dy == 0: + return + self.group.move(dx, dy) + self.x = self.x + dx + self.y = self.y + dy + + def moveto(self, x, y): + self.moveby(x - self.x, y - self.y) + + def transfer(self, pile): + if self.pile: + self.pile.delete(self) + self.pile = None + self.pile = pile + if self.pile: + self.pile.add(self) + + def tkraise(self): + self.group.tkraise() + + +class Bottom(Object): + + """An object to serve as the bottom of a pile.""" + + def createitems(self, *args): + self.__oval = Oval(self.canvas, + self.x-20, self.y-10, self.x+20, self.y+10, + fill='gray', outline='') + self.group.addtag_withtag(self.__oval) + + +class Pile: + + """A group of graphical objects.""" + + def __init__(self, canvas, x, y, tag=None): + self.canvas = canvas + self.x = x + self.y = y + self.objects = [] + self.bottom = Bottom(self.canvas, self.x, self.y) + self.group = Group(self.canvas, tag=tag) + self.group.addtag_withtag(self.bottom.group) + self.bindhandlers() + + def bindhandlers(self): + self.group.bind('<1>', self.clickhandler) + self.group.bind('<Double-1>', self.doubleclickhandler) + + def add(self, object): + self.objects.append(object) + self.group.addtag_withtag(object.group) + self.position(object) + + def delete(self, object): + object.group.dtag(self.group) + self.objects.remove(object) + + def position(self, object): + object.tkraise() + i = self.objects.index(object) + object.moveto(self.x + i*4, self.y + i*8) + + def clickhandler(self, event): + pass + + def doubleclickhandler(self, event): + pass + + +class MovingPile(Pile): + + def bindhandlers(self): + Pile.bindhandlers(self) + self.group.bind('<B1-Motion>', self.motionhandler) + self.group.bind('<ButtonRelease-1>', self.releasehandler) + + movethis = None + + def clickhandler(self, event): + tags = self.canvas.gettags('current') + for i in range(len(self.objects)): + o = self.objects[i] + if o.group.tag in tags: + break + else: + self.movethis = None + return + self.movethis = self.objects[i:] + for o in self.movethis: + o.tkraise() + self.lastx = event.x + self.lasty = event.y + + doubleclickhandler = clickhandler + + def motionhandler(self, event): + if not self.movethis: + return + dx = event.x - self.lastx + dy = event.y - self.lasty + self.lastx = event.x + self.lasty = event.y + for o in self.movethis: + o.moveby(dx, dy) + + def releasehandler(self, event): + objects = self.movethis + if not objects: + return + self.movethis = None + self.finishmove(objects) + + def finishmove(self, objects): + for o in objects: + self.position(o) + + +class Pile1(MovingPile): + + x = 50 + y = 50 + tag = 'p1' + + def __init__(self, demo): + self.demo = demo + MovingPile.__init__(self, self.demo.canvas, self.x, self.y, self.tag) + + def doubleclickhandler(self, event): + try: + o = self.objects[-1] + except IndexError: + return + o.transfer(self.other()) + MovingPile.doubleclickhandler(self, event) + + def other(self): + return self.demo.p2 + + def finishmove(self, objects): + o = objects[0] + p = self.other() + x, y = o.x, o.y + if (x-p.x)**2 + (y-p.y)**2 < (x-self.x)**2 + (y-self.y)**2: + for o in objects: + o.transfer(p) + else: + MovingPile.finishmove(self, objects) + +class Pile2(Pile1): + + x = 150 + y = 50 + tag = 'p2' + + def other(self): + return self.demo.p1 + + +class Demo: + + def __init__(self, master): + self.master = master + self.canvas = Canvas(master, + width=200, height=200, + background='yellow', + relief=SUNKEN, borderwidth=2) + self.canvas.pack(expand=1, fill=BOTH) + self.p1 = Pile1(self) + self.p2 = Pile2(self) + o1 = Object(self.canvas, fill='red', text='o1') + o2 = Object(self.canvas, fill='green', text='o2') + o3 = Object(self.canvas, fill='light blue', text='o3') + o1.transfer(self.p1) + o2.transfer(self.p1) + o3.transfer(self.p2) + + +# Main function, run when invoked as a stand-alone Python program. + +def main(): + root = Tk() + demo = Demo(root) + root.protocol('WM_DELETE_WINDOW', root.quit) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/Demo/tkinter/guido/newmenubardemo.py b/Demo/tkinter/guido/newmenubardemo.py new file mode 100644 index 0000000..7e2d13a --- /dev/null +++ b/Demo/tkinter/guido/newmenubardemo.py @@ -0,0 +1,42 @@ +#! /usr/bin/env python + +"""Play with the new Tk 8.0 toplevel menu option.""" + +from Tkinter import * + +class App: + + def __init__(self, master): + self.master = master + + self.menubar = Menu(self.master) + + self.filemenu = Menu(self.menubar) + + self.filemenu.add_command(label="New") + self.filemenu.add_command(label="Open...") + self.filemenu.add_command(label="Close") + self.filemenu.add_separator() + self.filemenu.add_command(label="Quit", command=self.master.quit) + + self.editmenu = Menu(self.menubar) + + self.editmenu.add_command(label="Cut") + self.editmenu.add_command(label="Copy") + self.editmenu.add_command(label="Paste") + + self.menubar.add_cascade(label="File", menu=self.filemenu) + self.menubar.add_cascade(label="Edit", menu=self.editmenu) + + self.top = Toplevel(menu=self.menubar) + + # Rest of app goes here... + +def main(): + root = Tk() + root.withdraw() + app = App(root) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/Demo/tkinter/guido/regexdemo.py b/Demo/tkinter/guido/regexdemo.py new file mode 100644 index 0000000..89c9ee5 --- /dev/null +++ b/Demo/tkinter/guido/regexdemo.py @@ -0,0 +1,127 @@ +"""Basic regular expression demostration facility. + +This displays a window with two type-in boxes. In the top box, you enter a +regular expression. In the bottom box, you enter a string. The first +match in the string of the regular expression is highlighted with a yellow +background (or red if the match is empty -- then the character at the match +is highlighted). The highlighting is continuously updated. At the bottom +are a number of checkboxes which control the regular expression syntax used +(see the regex_syntax module for descriptions). When there's no match, or +when the regular expression is syntactically incorrect, an error message is +displayed. + +""" + +from Tkinter import * +import regex +import regex_syntax + +class RegexDemo: + + def __init__(self, master): + self.master = master + self.topframe = Frame(self.master) + self.topframe.pack(fill=X) + self.promptdisplay = Label(self.topframe, text="Enter a string:") + self.promptdisplay.pack(side=LEFT) + self.statusdisplay = Label(self.topframe, text="", anchor=W) + self.statusdisplay.pack(side=LEFT, fill=X) + self.regexdisplay = Entry(self.master) + self.regexdisplay.pack(fill=X) + self.regexdisplay.focus_set() + self.labeldisplay = Label(self.master, anchor=W, + text="Enter a string:") + self.labeldisplay.pack(fill=X) + self.labeldisplay.pack(fill=X) + self.stringdisplay = Text(self.master, width=60, height=4) + self.stringdisplay.pack(fill=BOTH, expand=1) + self.stringdisplay.tag_configure("hit", background="yellow") + self.addoptions() + self.regexdisplay.bind('<Key>', self.recompile) + self.stringdisplay.bind('<Key>', self.reevaluate) + self.compiled = None + self.recompile() + btags = self.regexdisplay.bindtags() + self.regexdisplay.bindtags(btags[1:] + btags[:1]) + btags = self.stringdisplay.bindtags() + self.stringdisplay.bindtags(btags[1:] + btags[:1]) + + def addoptions(self): + self.frames = [] + self.boxes = [] + self.vars = [] + for name in ( 'RE_NO_BK_PARENS', + 'RE_NO_BK_VBAR', + 'RE_BK_PLUS_QM', + 'RE_TIGHT_VBAR', + 'RE_NEWLINE_OR', + 'RE_CONTEXT_INDEP_OPS'): + if len(self.boxes) % 3 == 0: + frame = Frame(self.master) + frame.pack(fill=X) + self.frames.append(frame) + val = getattr(regex_syntax, name) + var = IntVar() + box = Checkbutton(frame, + variable=var, text=name, + offvalue=0, onvalue=val, + command=self.newsyntax) + box.pack(side=LEFT) + self.boxes.append(box) + self.vars.append(var) + + def newsyntax(self): + syntax = 0 + for var in self.vars: + syntax = syntax | var.get() + regex.set_syntax(syntax) + self.recompile() + + def recompile(self, event=None): + try: + self.compiled = regex.compile(self.regexdisplay.get()) + self.statusdisplay.config(text="") + except regex.error, msg: + self.compiled = None + self.statusdisplay.config(text="regex.error: %s" % str(msg)) + self.reevaluate() + + def reevaluate(self, event=None): + try: + self.stringdisplay.tag_remove("hit", "1.0", END) + except TclError: + pass + if not self.compiled: + return + text = self.stringdisplay.get("1.0", END) + i = self.compiled.search(text) + if i < 0: + self.statusdisplay.config(text="(no match)") + else: + self.statusdisplay.config(text="") + regs = self.compiled.regs + first, last = regs[0] + if last == first: + last = first+1 + self.stringdisplay.tag_configure("hit", + background="red") + else: + self.stringdisplay.tag_configure("hit", + background="yellow") + pfirst = "1.0 + %d chars" % first + plast = "1.0 + %d chars" % last + self.stringdisplay.tag_add("hit", pfirst, plast) + self.stringdisplay.yview_pickplace(pfirst) + + + +# Main function, run when invoked as a stand-alone Python program. + +def main(): + root = Tk() + demo = RegexDemo(root) + root.protocol('WM_DELETE_WINDOW', root.quit) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/Demo/tkinter/guido/sortvisu.py b/Demo/tkinter/guido/sortvisu.py new file mode 100644 index 0000000..6fe4002 --- /dev/null +++ b/Demo/tkinter/guido/sortvisu.py @@ -0,0 +1,634 @@ +#! /usr/bin/env python + +"""Sorting algorithms visualizer using Tkinter. + +This module is comprised of three ``components'': + +- an array visualizer with methods that implement basic sorting +operations (compare, swap) as well as methods for ``annotating'' the +sorting algorithm (e.g. to show the pivot element); + +- a number of sorting algorithms (currently quicksort, insertion sort, +selection sort and bubble sort, as well as a randomization function), +all using the array visualizer for its basic operations and with calls +to its annotation methods; + +- and a ``driver'' class which can be used as a Grail applet or as a +stand-alone application. + +""" + + +from Tkinter import * +from Canvas import Line, Rectangle +import random + + +XGRID = 10 +YGRID = 10 +WIDTH = 6 + + +class Array: + + def __init__(self, master, data=None): + self.master = master + self.frame = Frame(self.master) + self.frame.pack(fill=X) + self.label = Label(self.frame) + self.label.pack() + self.canvas = Canvas(self.frame) + self.canvas.pack() + self.report = Label(self.frame) + self.report.pack() + self.left = Line(self.canvas, 0, 0, 0, 0) + self.right = Line(self.canvas, 0, 0, 0, 0) + self.pivot = Line(self.canvas, 0, 0, 0, 0) + self.items = [] + self.size = self.maxvalue = 0 + if data: + self.setdata(data) + + def setdata(self, data): + olditems = self.items + self.items = [] + for item in olditems: + item.delete() + self.size = len(data) + self.maxvalue = max(data) + self.canvas.config(width=(self.size+1)*XGRID, + height=(self.maxvalue+1)*YGRID) + for i in range(self.size): + self.items.append(ArrayItem(self, i, data[i])) + self.reset("Sort demo, size %d" % self.size) + + speed = "normal" + + def setspeed(self, speed): + self.speed = speed + + def destroy(self): + self.frame.destroy() + + in_mainloop = 0 + stop_mainloop = 0 + + def cancel(self): + self.stop_mainloop = 1 + if self.in_mainloop: + self.master.quit() + + def step(self): + if self.in_mainloop: + self.master.quit() + + Cancelled = "Array.Cancelled" # Exception + + def wait(self, msecs): + if self.speed == "fastest": + msecs = 0 + elif self.speed == "fast": + msecs = msecs/10 + elif self.speed == "single-step": + msecs = 1000000000 + if not self.stop_mainloop: + self.master.update() + id = self.master.after(msecs, self.master.quit) + self.in_mainloop = 1 + self.master.mainloop() + self.master.after_cancel(id) + self.in_mainloop = 0 + if self.stop_mainloop: + self.stop_mainloop = 0 + self.message("Cancelled") + raise Array.Cancelled + + def getsize(self): + return self.size + + def show_partition(self, first, last): + for i in range(self.size): + item = self.items[i] + if first <= i < last: + item.item.config(fill='red') + else: + item.item.config(fill='orange') + self.hide_left_right_pivot() + + def hide_partition(self): + for i in range(self.size): + item = self.items[i] + item.item.config(fill='red') + self.hide_left_right_pivot() + + def show_left(self, left): + if not 0 <= left < self.size: + self.hide_left() + return + x1, y1, x2, y2 = self.items[left].position() +## top, bot = HIRO + self.left.coords([(x1-2, 0), (x1-2, 9999)]) + self.master.update() + + def show_right(self, right): + if not 0 <= right < self.size: + self.hide_right() + return + x1, y1, x2, y2 = self.items[right].position() + self.right.coords(((x2+2, 0), (x2+2, 9999))) + self.master.update() + + def hide_left_right_pivot(self): + self.hide_left() + self.hide_right() + self.hide_pivot() + + def hide_left(self): + self.left.coords(((0, 0), (0, 0))) + + def hide_right(self): + self.right.coords(((0, 0), (0, 0))) + + def show_pivot(self, pivot): + x1, y1, x2, y2 = self.items[pivot].position() + self.pivot.coords(((0, y1-2), (9999, y1-2))) + + def hide_pivot(self): + self.pivot.coords(((0, 0), (0, 0))) + + def swap(self, i, j): + if i == j: return + self.countswap() + item = self.items[i] + other = self.items[j] + self.items[i], self.items[j] = other, item + item.swapwith(other) + + def compare(self, i, j): + self.countcompare() + item = self.items[i] + other = self.items[j] + return item.compareto(other) + + def reset(self, msg): + self.ncompares = 0 + self.nswaps = 0 + self.message(msg) + self.updatereport() + self.hide_partition() + + def message(self, msg): + self.label.config(text=msg) + + def countswap(self): + self.nswaps = self.nswaps + 1 + self.updatereport() + + def countcompare(self): + self.ncompares = self.ncompares + 1 + self.updatereport() + + def updatereport(self): + text = "%d cmps, %d swaps" % (self.ncompares, self.nswaps) + self.report.config(text=text) + + +class ArrayItem: + + def __init__(self, array, index, value): + self.array = array + self.index = index + self.value = value + x1, y1, x2, y2 = self.position() + self.item = Rectangle(array.canvas, x1, y1, x2, y2, + fill='red', outline='black', width=1) + self.item.bind('<Button-1>', self.mouse_down) + self.item.bind('<Button1-Motion>', self.mouse_move) + self.item.bind('<ButtonRelease-1>', self.mouse_up) + + def delete(self): + item = self.item + self.array = None + self.item = None + item.delete() + + def mouse_down(self, event): + self.lastx = event.x + self.lasty = event.y + self.origx = event.x + self.origy = event.y + self.item.tkraise() + + def mouse_move(self, event): + self.item.move(event.x - self.lastx, event.y - self.lasty) + self.lastx = event.x + self.lasty = event.y + + def mouse_up(self, event): + i = self.nearestindex(event.x) + if i >= self.array.getsize(): + i = self.array.getsize() - 1 + if i < 0: + i = 0 + other = self.array.items[i] + here = self.index + self.array.items[here], self.array.items[i] = other, self + self.index = i + x1, y1, x2, y2 = self.position() + self.item.coords(((x1, y1), (x2, y2))) + other.setindex(here) + + def setindex(self, index): + nsteps = steps(self.index, index) + if not nsteps: return + if self.array.speed == "fastest": + nsteps = 0 + oldpts = self.position() + self.index = index + newpts = self.position() + trajectory = interpolate(oldpts, newpts, nsteps) + self.item.tkraise() + for pts in trajectory: + self.item.coords((pts[:2], pts[2:])) + self.array.wait(50) + + def swapwith(self, other): + nsteps = steps(self.index, other.index) + if not nsteps: return + if self.array.speed == "fastest": + nsteps = 0 + myoldpts = self.position() + otheroldpts = other.position() + self.index, other.index = other.index, self.index + mynewpts = self.position() + othernewpts = other.position() + myfill = self.item['fill'] + otherfill = other.item['fill'] + self.item.config(fill='green') + other.item.config(fill='yellow') + self.array.master.update() + if self.array.speed == "single-step": + self.item.coords((mynewpts[:2], mynewpts[2:])) + other.item.coords((othernewpts[:2], othernewpts[2:])) + self.array.master.update() + self.item.config(fill=myfill) + other.item.config(fill=otherfill) + self.array.wait(0) + return + mytrajectory = interpolate(myoldpts, mynewpts, nsteps) + othertrajectory = interpolate(otheroldpts, othernewpts, nsteps) + if self.value > other.value: + self.item.tkraise() + other.item.tkraise() + else: + other.item.tkraise() + self.item.tkraise() + try: + for i in range(len(mytrajectory)): + mypts = mytrajectory[i] + otherpts = othertrajectory[i] + self.item.coords((mypts[:2], mypts[2:])) + other.item.coords((otherpts[:2], otherpts[2:])) + self.array.wait(50) + finally: + mypts = mytrajectory[-1] + otherpts = othertrajectory[-1] + self.item.coords((mypts[:2], mypts[2:])) + other.item.coords((otherpts[:2], otherpts[2:])) + self.item.config(fill=myfill) + other.item.config(fill=otherfill) + + def compareto(self, other): + myfill = self.item['fill'] + otherfill = other.item['fill'] + outcome = cmp(self.value, other.value) + if outcome < 0: + myflash = 'white' + otherflash = 'black' + elif outcome > 0: + myflash = 'black' + otherflash = 'white' + else: + myflash = otherflash = 'grey' + try: + self.item.config(fill=myflash) + other.item.config(fill=otherflash) + self.array.wait(500) + finally: + self.item.config(fill=myfill) + other.item.config(fill=otherfill) + return outcome + + def position(self): + x1 = (self.index+1)*XGRID - WIDTH/2 + x2 = x1+WIDTH + y2 = (self.array.maxvalue+1)*YGRID + y1 = y2 - (self.value)*YGRID + return x1, y1, x2, y2 + + def nearestindex(self, x): + return int(round(float(x)/XGRID)) - 1 + + +# Subroutines that don't need an object + +def steps(here, there): + nsteps = abs(here - there) + if nsteps <= 3: + nsteps = nsteps * 3 + elif nsteps <= 5: + nsteps = nsteps * 2 + elif nsteps > 10: + nsteps = 10 + return nsteps + +def interpolate(oldpts, newpts, n): + if len(oldpts) != len(newpts): + raise ValueError, "can't interpolate arrays of different length" + pts = [0]*len(oldpts) + res = [tuple(oldpts)] + for i in range(1, n): + for k in range(len(pts)): + pts[k] = oldpts[k] + (newpts[k] - oldpts[k])*i/n + res.append(tuple(pts)) + res.append(tuple(newpts)) + return res + + +# Various (un)sorting algorithms + +def uniform(array): + size = array.getsize() + array.setdata([(size+1)/2] * size) + array.reset("Uniform data, size %d" % size) + +def distinct(array): + size = array.getsize() + array.setdata(range(1, size+1)) + array.reset("Distinct data, size %d" % size) + +def randomize(array): + array.reset("Randomizing") + n = array.getsize() + for i in range(n): + j = random.randint(0, n-1) + array.swap(i, j) + array.message("Randomized") + +def insertionsort(array): + size = array.getsize() + array.reset("Insertion sort") + for i in range(1, size): + j = i-1 + while j >= 0: + if array.compare(j, j+1) <= 0: + break + array.swap(j, j+1) + j = j-1 + array.message("Sorted") + +def selectionsort(array): + size = array.getsize() + array.reset("Selection sort") + try: + for i in range(size): + array.show_partition(i, size) + for j in range(i+1, size): + if array.compare(i, j) > 0: + array.swap(i, j) + array.message("Sorted") + finally: + array.hide_partition() + +def bubblesort(array): + size = array.getsize() + array.reset("Bubble sort") + for i in range(size): + for j in range(1, size): + if array.compare(j-1, j) > 0: + array.swap(j-1, j) + array.message("Sorted") + +def quicksort(array): + size = array.getsize() + array.reset("Quicksort") + try: + stack = [(0, size)] + while stack: + first, last = stack[-1] + del stack[-1] + array.show_partition(first, last) + if last-first < 5: + array.message("Insertion sort") + for i in range(first+1, last): + j = i-1 + while j >= first: + if array.compare(j, j+1) <= 0: + break + array.swap(j, j+1) + j = j-1 + continue + array.message("Choosing pivot") + j, i, k = first, (first+last)/2, last-1 + if array.compare(k, i) < 0: + array.swap(k, i) + if array.compare(k, j) < 0: + array.swap(k, j) + if array.compare(j, i) < 0: + array.swap(j, i) + pivot = j + array.show_pivot(pivot) + array.message("Pivot at left of partition") + array.wait(1000) + left = first + right = last + while 1: + array.message("Sweep right pointer") + right = right-1 + array.show_right(right) + while right > first and array.compare(right, pivot) >= 0: + right = right-1 + array.show_right(right) + array.message("Sweep left pointer") + left = left+1 + array.show_left(left) + while left < last and array.compare(left, pivot) <= 0: + left = left+1 + array.show_left(left) + if left > right: + array.message("End of partition") + break + array.message("Swap items") + array.swap(left, right) + array.message("Swap pivot back") + array.swap(pivot, right) + n1 = right-first + n2 = last-left + if n1 > 1: stack.append((first, right)) + if n2 > 1: stack.append((left, last)) + array.message("Sorted") + finally: + array.hide_partition() + +def demosort(array): + while 1: + for alg in [quicksort, insertionsort, selectionsort, bubblesort]: + randomize(array) + alg(array) + + +# Sort demo class -- usable as a Grail applet + +class SortDemo: + + def __init__(self, master, size=15): + self.master = master + self.size = size + self.busy = 0 + self.array = Array(self.master) + + self.botframe = Frame(master) + self.botframe.pack(side=BOTTOM) + self.botleftframe = Frame(self.botframe) + self.botleftframe.pack(side=LEFT, fill=Y) + self.botrightframe = Frame(self.botframe) + self.botrightframe.pack(side=RIGHT, fill=Y) + + self.b_qsort = Button(self.botleftframe, + text="Quicksort", command=self.c_qsort) + self.b_qsort.pack(fill=X) + self.b_isort = Button(self.botleftframe, + text="Insertion sort", command=self.c_isort) + self.b_isort.pack(fill=X) + self.b_ssort = Button(self.botleftframe, + text="Selection sort", command=self.c_ssort) + self.b_ssort.pack(fill=X) + self.b_bsort = Button(self.botleftframe, + text="Bubble sort", command=self.c_bsort) + self.b_bsort.pack(fill=X) + + # Terrible hack to overcome limitation of OptionMenu... + class MyIntVar(IntVar): + def __init__(self, master, demo): + self.demo = demo + IntVar.__init__(self, master) + def set(self, value): + IntVar.set(self, value) + if str(value) != '0': + self.demo.resize(value) + + self.v_size = MyIntVar(self.master, self) + self.v_size.set(size) + sizes = [1, 2, 3, 4] + range(5, 55, 5) + if self.size not in sizes: + sizes.append(self.size) + sizes.sort() + self.m_size = apply(OptionMenu, + (self.botleftframe, self.v_size) + tuple(sizes)) + self.m_size.pack(fill=X) + + self.v_speed = StringVar(self.master) + self.v_speed.set("normal") + self.m_speed = OptionMenu(self.botleftframe, self.v_speed, + "single-step", "normal", "fast", "fastest") + self.m_speed.pack(fill=X) + + self.b_step = Button(self.botleftframe, + text="Step", command=self.c_step) + self.b_step.pack(fill=X) + + self.b_randomize = Button(self.botrightframe, + text="Randomize", command=self.c_randomize) + self.b_randomize.pack(fill=X) + self.b_uniform = Button(self.botrightframe, + text="Uniform", command=self.c_uniform) + self.b_uniform.pack(fill=X) + self.b_distinct = Button(self.botrightframe, + text="Distinct", command=self.c_distinct) + self.b_distinct.pack(fill=X) + self.b_demo = Button(self.botrightframe, + text="Demo", command=self.c_demo) + self.b_demo.pack(fill=X) + self.b_cancel = Button(self.botrightframe, + text="Cancel", command=self.c_cancel) + self.b_cancel.pack(fill=X) + self.b_cancel.config(state=DISABLED) + self.b_quit = Button(self.botrightframe, + text="Quit", command=self.c_quit) + self.b_quit.pack(fill=X) + + def resize(self, newsize): + if self.busy: + self.master.bell() + return + self.size = newsize + self.array.setdata(range(1, self.size+1)) + + def c_qsort(self): + self.run(quicksort) + + def c_isort(self): + self.run(insertionsort) + + def c_ssort(self): + self.run(selectionsort) + + def c_bsort(self): + self.run(bubblesort) + + def c_demo(self): + self.run(demosort) + + def c_randomize(self): + self.run(randomize) + + def c_uniform(self): + self.run(uniform) + + def c_distinct(self): + self.run(distinct) + + def run(self, func): + if self.busy: + self.master.bell() + return + self.busy = 1 + self.array.setspeed(self.v_speed.get()) + self.b_cancel.config(state=NORMAL) + try: + func(self.array) + except Array.Cancelled: + pass + self.b_cancel.config(state=DISABLED) + self.busy = 0 + + def c_cancel(self): + if not self.busy: + self.master.bell() + return + self.array.cancel() + + def c_step(self): + if not self.busy: + self.master.bell() + return + self.v_speed.set("single-step") + self.array.setspeed("single-step") + self.array.step() + + def c_quit(self): + if self.busy: + self.array.cancel() + self.master.after_idle(self.master.quit) + + +# Main program -- for stand-alone operation outside Grail + +def main(): + root = Tk() + demo = SortDemo(root) + root.protocol('WM_DELETE_WINDOW', demo.c_quit) + root.mainloop() + +if __name__ == '__main__': + main() |