summaryrefslogtreecommitdiff
path: root/sys/src/cmd/python/Demo/tkinter/guido
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
committercinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
commit458120dd40db6b4df55a4e96b650e16798ef06a0 (patch)
tree8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/python/Demo/tkinter/guido
parent3a742c699f6806c1145aea5149bf15de15a0afd7 (diff)
add hg and python
Diffstat (limited to 'sys/src/cmd/python/Demo/tkinter/guido')
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py452
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/ManPage.py220
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py143
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py151
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/brownian.py50
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/canvasevents.py244
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/dialog.py109
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/electrons.py91
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/hanoi.py154
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/hello.py17
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/imagedraw.py23
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/imageview.py12
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/kill.py98
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/listtree.py37
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/mbox.py285
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py47
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py27
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/paint.py60
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/rmt.py159
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/solitaire.py637
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/sortvisu.py634
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/ss1.py845
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/svkill.py128
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/switch.py55
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/tkman.py267
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/wish.py27
26 files changed, 4972 insertions, 0 deletions
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py b/sys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py
new file mode 100755
index 000000000..86333adc7
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py
@@ -0,0 +1,452 @@
+
+# The options of a widget are described by the following attributes
+# of the Pack and Widget dialogs:
+#
+# Dialog.current: {name: value}
+# -- changes during Widget's lifetime
+#
+# Dialog.options: {name: (default, klass)}
+# -- depends on widget class only
+#
+# Dialog.classes: {klass: (v0, v1, v2, ...) | 'boolean' | 'other'}
+# -- totally static, though different between PackDialog and WidgetDialog
+# (but even that could be unified)
+
+from Tkinter import *
+
+class Option:
+
+ varclass = StringVar # May be overridden
+
+ def __init__(self, dialog, option):
+ self.dialog = dialog
+ self.option = option
+ self.master = dialog.top
+ self.default, self.klass = dialog.options[option]
+ self.var = self.varclass(self.master)
+ self.frame = Frame(self.master)
+ self.frame.pack(fill=X)
+ self.label = Label(self.frame, text=(option + ":"))
+ self.label.pack(side=LEFT)
+ self.update()
+ self.addoption()
+
+ def refresh(self):
+ self.dialog.refresh()
+ self.update()
+
+ def update(self):
+ try:
+ self.current = self.dialog.current[self.option]
+ except KeyError:
+ self.current = self.default
+ self.var.set(self.current)
+
+ def set(self, e=None): # Should be overridden
+ pass
+
+class BooleanOption(Option):
+
+ varclass = BooleanVar
+
+ def addoption(self):
+ self.button = Checkbutton(self.frame,
+ text='on/off',
+ onvalue=1,
+ offvalue=0,
+ variable=self.var,
+ relief=RAISED,
+ borderwidth=2,
+ command=self.set)
+ self.button.pack(side=RIGHT)
+
+class EnumOption(Option):
+
+ def addoption(self):
+ self.button = Menubutton(self.frame,
+ textvariable=self.var,
+ relief=RAISED, borderwidth=2)
+ self.button.pack(side=RIGHT)
+ self.menu = Menu(self.button)
+ self.button['menu'] = self.menu
+ for v in self.dialog.classes[self.klass]:
+ self.menu.add_radiobutton(
+ label=v,
+ variable=self.var,
+ value=v,
+ command=self.set)
+
+class StringOption(Option):
+
+ def addoption(self):
+ self.entry = Entry(self.frame,
+ textvariable=self.var,
+ width=10,
+ relief=SUNKEN,
+ borderwidth=2)
+ self.entry.pack(side=RIGHT, fill=X, expand=1)
+ self.entry.bind('<Return>', self.set)
+
+class ReadonlyOption(Option):
+
+ def addoption(self):
+ self.label = Label(self.frame, textvariable=self.var,
+ anchor=E)
+ self.label.pack(side=RIGHT)
+
+class Dialog:
+
+ def __init__(self, master):
+ self.master = master
+ self.fixclasses()
+ self.refresh()
+ self.top = Toplevel(self.master)
+ self.top.title(self.__class__.__name__)
+ self.top.minsize(1, 1)
+ self.addchoices()
+
+ def refresh(self): pass # Must override
+
+ def fixclasses(self): pass # May override
+
+ def addchoices(self):
+ self.choices = {}
+ list = []
+ for k, dc in self.options.items():
+ list.append((k, dc))
+ list.sort()
+ for k, (d, c) in list:
+ try:
+ cl = self.classes[c]
+ except KeyError:
+ cl = 'unknown'
+ if type(cl) == TupleType:
+ cl = self.enumoption
+ elif cl == 'boolean':
+ cl = self.booleanoption
+ elif cl == 'readonly':
+ cl = self.readonlyoption
+ else:
+ cl = self.stringoption
+ self.choices[k] = cl(self, k)
+
+ # Must override:
+ options = {}
+ classes = {}
+
+ # May override:
+ booleanoption = BooleanOption
+ stringoption = StringOption
+ enumoption = EnumOption
+ readonlyoption = ReadonlyOption
+
+class PackDialog(Dialog):
+
+ def __init__(self, widget):
+ self.widget = widget
+ Dialog.__init__(self, widget)
+
+ def refresh(self):
+ self.current = self.widget.info()
+ self.current['.class'] = self.widget.winfo_class()
+ self.current['.name'] = self.widget._w
+
+ class packoption: # Mix-in class
+ def set(self, e=None):
+ self.current = self.var.get()
+ try:
+ apply(self.dialog.widget.pack, (),
+ {self.option: self.current})
+ except TclError, msg:
+ print msg
+ self.refresh()
+
+ class booleanoption(packoption, BooleanOption): pass
+ class enumoption(packoption, EnumOption): pass
+ class stringoption(packoption, StringOption): pass
+ class readonlyoption(packoption, ReadonlyOption): pass
+
+ options = {
+ '.class': (None, 'Class'),
+ '.name': (None, 'Name'),
+ 'after': (None, 'Widget'),
+ 'anchor': ('center', 'Anchor'),
+ 'before': (None, 'Widget'),
+ 'expand': ('no', 'Boolean'),
+ 'fill': ('none', 'Fill'),
+ 'in': (None, 'Widget'),
+ 'ipadx': (0, 'Pad'),
+ 'ipady': (0, 'Pad'),
+ 'padx': (0, 'Pad'),
+ 'pady': (0, 'Pad'),
+ 'side': ('top', 'Side'),
+ }
+
+ classes = {
+ 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER),
+ 'Boolean': 'boolean',
+ 'Class': 'readonly',
+ 'Expand': 'boolean',
+ 'Fill': (NONE, X, Y, BOTH),
+ 'Name': 'readonly',
+ 'Pad': 'pixel',
+ 'Side': (TOP, RIGHT, BOTTOM, LEFT),
+ 'Widget': 'readonly',
+ }
+
+class RemotePackDialog(PackDialog):
+
+ def __init__(self, master, app, widget):
+ self.master = master
+ self.app = app
+ self.widget = widget
+ self.refresh()
+ self.top = Toplevel(self.master)
+ self.top.title(self.app + ' PackDialog')
+ self.top.minsize(1, 1)
+ self.addchoices()
+
+ def refresh(self):
+ try:
+ words = self.master.tk.splitlist(
+ self.master.send(self.app,
+ 'pack',
+ 'info',
+ self.widget))
+ except TclError, msg:
+ print msg
+ return
+ dict = {}
+ for i in range(0, len(words), 2):
+ key = words[i][1:]
+ value = words[i+1]
+ dict[key] = value
+ dict['.class'] = self.master.send(self.app,
+ 'winfo',
+ 'class',
+ self.widget)
+ dict['.name'] = self.widget
+ self.current = dict
+
+ class remotepackoption: # Mix-in class
+ def set(self, e=None):
+ self.current = self.var.get()
+ try:
+ self.dialog.master.send(
+ self.dialog.app,
+ 'pack',
+ 'config',
+ self.dialog.widget,
+ '-'+self.option,
+ self.dialog.master.tk.merge(
+ self.current))
+ except TclError, msg:
+ print msg
+ self.refresh()
+
+ class booleanoption(remotepackoption, BooleanOption): pass
+ class enumoption(remotepackoption, EnumOption): pass
+ class stringoption(remotepackoption, StringOption): pass
+ class readonlyoption(remotepackoption, ReadonlyOption): pass
+
+class WidgetDialog(Dialog):
+
+ def __init__(self, widget):
+ self.widget = widget
+ self.klass = widget.winfo_class()
+ Dialog.__init__(self, widget)
+
+ def fixclasses(self):
+ if self.addclasses.has_key(self.klass):
+ classes = {}
+ for c in (self.classes,
+ self.addclasses[self.klass]):
+ for k in c.keys():
+ classes[k] = c[k]
+ self.classes = classes
+
+ def refresh(self):
+ self.configuration = self.widget.config()
+ self.update()
+ self.current['.class'] = self.widget.winfo_class()
+ self.current['.name'] = self.widget._w
+
+ def update(self):
+ self.current = {}
+ self.options = {}
+ for k, v in self.configuration.items():
+ if len(v) > 4:
+ self.current[k] = v[4]
+ self.options[k] = v[3], v[2] # default, klass
+ self.options['.class'] = (None, 'Class')
+ self.options['.name'] = (None, 'Name')
+
+ class widgetoption: # Mix-in class
+ def set(self, e=None):
+ self.current = self.var.get()
+ try:
+ self.dialog.widget[self.option] = self.current
+ except TclError, msg:
+ print msg
+ self.refresh()
+
+ class booleanoption(widgetoption, BooleanOption): pass
+ class enumoption(widgetoption, EnumOption): pass
+ class stringoption(widgetoption, StringOption): pass
+ class readonlyoption(widgetoption, ReadonlyOption): pass
+
+ # Universal classes
+ classes = {
+ 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER),
+ 'Aspect': 'integer',
+ 'Background': 'color',
+ 'Bitmap': 'bitmap',
+ 'BorderWidth': 'pixel',
+ 'Class': 'readonly',
+ 'CloseEnough': 'double',
+ 'Command': 'command',
+ 'Confine': 'boolean',
+ 'Cursor': 'cursor',
+ 'CursorWidth': 'pixel',
+ 'DisabledForeground': 'color',
+ 'ExportSelection': 'boolean',
+ 'Font': 'font',
+ 'Foreground': 'color',
+ 'From': 'integer',
+ 'Geometry': 'geometry',
+ 'Height': 'pixel',
+ 'InsertWidth': 'time',
+ 'Justify': (LEFT, CENTER, RIGHT),
+ 'Label': 'string',
+ 'Length': 'pixel',
+ 'MenuName': 'widget',
+ 'Name': 'readonly',
+ 'OffTime': 'time',
+ 'OnTime': 'time',
+ 'Orient': (HORIZONTAL, VERTICAL),
+ 'Pad': 'pixel',
+ 'Relief': (RAISED, SUNKEN, FLAT, RIDGE, GROOVE),
+ 'RepeatDelay': 'time',
+ 'RepeatInterval': 'time',
+ 'ScrollCommand': 'command',
+ 'ScrollIncrement': 'pixel',
+ 'ScrollRegion': 'rectangle',
+ 'ShowValue': 'boolean',
+ 'SetGrid': 'boolean',
+ 'Sliderforeground': 'color',
+ 'SliderLength': 'pixel',
+ 'Text': 'string',
+ 'TickInterval': 'integer',
+ 'To': 'integer',
+ 'Underline': 'index',
+ 'Variable': 'variable',
+ 'Value': 'string',
+ 'Width': 'pixel',
+ 'Wrap': (NONE, CHAR, WORD),
+ }
+
+ # Classes that (may) differ per widget type
+ _tristate = {'State': (NORMAL, ACTIVE, DISABLED)}
+ _bistate = {'State': (NORMAL, DISABLED)}
+ addclasses = {
+ 'Button': _tristate,
+ 'Radiobutton': _tristate,
+ 'Checkbutton': _tristate,
+ 'Entry': _bistate,
+ 'Text': _bistate,
+ 'Menubutton': _tristate,
+ 'Slider': _bistate,
+ }
+
+class RemoteWidgetDialog(WidgetDialog):
+
+ def __init__(self, master, app, widget):
+ self.app = app
+ self.widget = widget
+ self.klass = master.send(self.app,
+ 'winfo',
+ 'class',
+ self.widget)
+ Dialog.__init__(self, master)
+
+ def refresh(self):
+ try:
+ items = self.master.tk.splitlist(
+ self.master.send(self.app,
+ self.widget,
+ 'config'))
+ except TclError, msg:
+ print msg
+ return
+ dict = {}
+ for item in items:
+ words = self.master.tk.splitlist(item)
+ key = words[0][1:]
+ value = (key,) + words[1:]
+ dict[key] = value
+ self.configuration = dict
+ self.update()
+ self.current['.class'] = self.klass
+ self.current['.name'] = self.widget
+
+ class remotewidgetoption: # Mix-in class
+ def set(self, e=None):
+ self.current = self.var.get()
+ try:
+ self.dialog.master.send(
+ self.dialog.app,
+ self.dialog.widget,
+ 'config',
+ '-'+self.option,
+ self.current)
+ except TclError, msg:
+ print msg
+ self.refresh()
+
+ class booleanoption(remotewidgetoption, BooleanOption): pass
+ class enumoption(remotewidgetoption, EnumOption): pass
+ class stringoption(remotewidgetoption, StringOption): pass
+ class readonlyoption(remotewidgetoption, ReadonlyOption): pass
+
+def test():
+ import sys
+ root = Tk()
+ root.minsize(1, 1)
+ if sys.argv[1:]:
+ remotetest(root, sys.argv[1])
+ else:
+ frame = Frame(root, name='frame')
+ frame.pack(expand=1, fill=BOTH)
+ button = Button(frame, name='button', text='button')
+ button.pack(expand=1)
+ canvas = Canvas(frame, name='canvas')
+ canvas.pack()
+ fpd = PackDialog(frame)
+ fwd = WidgetDialog(frame)
+ bpd = PackDialog(button)
+ bwd = WidgetDialog(button)
+ cpd = PackDialog(canvas)
+ cwd = WidgetDialog(canvas)
+ root.mainloop()
+
+def remotetest(root, app):
+ from listtree import listtree
+ list = listtree(root, app)
+ list.bind('<Any-Double-1>', opendialogs)
+ list.app = app # Pass it on to handler
+
+def opendialogs(e):
+ import string
+ list = e.widget
+ sel = list.curselection()
+ for i in sel:
+ item = list.get(i)
+ widget = string.split(item)[0]
+ RemoteWidgetDialog(list, list.app, widget)
+ if widget == '.': continue
+ try:
+ RemotePackDialog(list, list.app, widget)
+ except TclError, msg:
+ print msg
+
+test()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/ManPage.py b/sys/src/cmd/python/Demo/tkinter/guido/ManPage.py
new file mode 100755
index 000000000..de3117b35
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/ManPage.py
@@ -0,0 +1,220 @@
+# Widget to display a man page
+
+import re
+from Tkinter import *
+from Tkinter import _tkinter
+from ScrolledText import ScrolledText
+
+# XXX These fonts may have to be changed to match your system
+BOLDFONT = '*-Courier-Bold-R-Normal-*-120-*'
+ITALICFONT = '*-Courier-Medium-O-Normal-*-120-*'
+
+# XXX Recognizing footers is system dependent
+# (This one works for IRIX 5.2 and Solaris 2.2)
+footerprog = re.compile(
+ '^ Page [1-9][0-9]*[ \t]+\|^.*Last change:.*[1-9][0-9]*\n')
+emptyprog = re.compile('^[ \t]*\n')
+ulprog = re.compile('^[ \t]*[Xv!_][Xv!_ \t]*\n')
+
+# Basic Man Page class -- does not disable editing
+class EditableManPage(ScrolledText):
+
+ # Initialize instance
+ def __init__(self, master=None, **cnf):
+ # Initialize base class
+ apply(ScrolledText.__init__, (self, master), cnf)
+
+ # Define tags for formatting styles
+ self.tag_config('X', underline=1)
+ self.tag_config('!', font=BOLDFONT)
+ self.tag_config('_', font=ITALICFONT)
+
+ # Set state to idle
+ self.fp = None
+ self.lineno = 0
+
+ # Test whether we are busy parsing a file
+ def busy(self):
+ return self.fp != None
+
+ # Ensure we're not busy
+ def kill(self):
+ if self.busy():
+ self._endparser()
+
+ # Parse a file, in the background
+ def asyncparsefile(self, fp):
+ self._startparser(fp)
+ self.tk.createfilehandler(fp, _tkinter.READABLE,
+ self._filehandler)
+
+ parsefile = asyncparsefile # Alias
+
+ # I/O handler used by background parsing
+ def _filehandler(self, fp, mask):
+ nextline = self.fp.readline()
+ if not nextline:
+ self._endparser()
+ return
+ self._parseline(nextline)
+
+ # Parse a file, now (cannot be aborted)
+ def syncparsefile(self, fp):
+ from select import select
+ def avail(fp=fp, tout=0.0, select=select):
+ return select([fp], [], [], tout)[0]
+ height = self.getint(self['height'])
+ self._startparser(fp)
+ while 1:
+ nextline = fp.readline()
+ if not nextline:
+ break
+ self._parseline(nextline)
+ self._endparser()
+
+ # Initialize parsing from a particular file -- must not be busy
+ def _startparser(self, fp):
+ if self.busy():
+ raise RuntimeError, 'startparser: still busy'
+ fp.fileno() # Test for file-ness
+ self.fp = fp
+ self.lineno = 0
+ self.ok = 0
+ self.empty = 0
+ self.buffer = None
+ savestate = self['state']
+ self['state'] = NORMAL
+ self.delete('1.0', END)
+ self['state'] = savestate
+
+ # End parsing -- must be busy, need not be at EOF
+ def _endparser(self):
+ if not self.busy():
+ raise RuntimeError, 'endparser: not busy'
+ if self.buffer:
+ self._parseline('')
+ try:
+ self.tk.deletefilehandler(self.fp)
+ except TclError, msg:
+ pass
+ self.fp.close()
+ self.fp = None
+ del self.ok, self.empty, self.buffer
+
+ # Parse a single line
+ def _parseline(self, nextline):
+ if not self.buffer:
+ # Save this line -- we need one line read-ahead
+ self.buffer = nextline
+ return
+ if emptyprog.match(self.buffer) >= 0:
+ # Buffered line was empty -- set a flag
+ self.empty = 1
+ self.buffer = nextline
+ return
+ textline = self.buffer
+ if ulprog.match(nextline) >= 0:
+ # Next line is properties for buffered line
+ propline = nextline
+ self.buffer = None
+ else:
+ # Next line is read-ahead
+ propline = None
+ self.buffer = nextline
+ if not self.ok:
+ # First non blank line after footer must be header
+ # -- skip that too
+ self.ok = 1
+ self.empty = 0
+ return
+ if footerprog.match(textline) >= 0:
+ # Footer -- start skipping until next non-blank line
+ self.ok = 0
+ self.empty = 0
+ return
+ savestate = self['state']
+ self['state'] = NORMAL
+ if TkVersion >= 4.0:
+ self.mark_set('insert', 'end-1c')
+ else:
+ self.mark_set('insert', END)
+ if self.empty:
+ # One or more previous lines were empty
+ # -- insert one blank line in the text
+ self._insert_prop('\n')
+ self.lineno = self.lineno + 1
+ self.empty = 0
+ if not propline:
+ # No properties
+ self._insert_prop(textline)
+ else:
+ # Search for properties
+ p = ''
+ j = 0
+ for i in range(min(len(propline), len(textline))):
+ if propline[i] != p:
+ if j < i:
+ self._insert_prop(textline[j:i], p)
+ j = i
+ p = propline[i]
+ self._insert_prop(textline[j:])
+ self.lineno = self.lineno + 1
+ self['state'] = savestate
+
+ # Insert a string at the end, with at most one property (tag)
+ def _insert_prop(self, str, prop = ' '):
+ here = self.index(AtInsert())
+ self.insert(AtInsert(), str)
+ if TkVersion <= 4.0:
+ tags = self.tag_names(here)
+ for tag in tags:
+ self.tag_remove(tag, here, AtInsert())
+ if prop != ' ':
+ self.tag_add(prop, here, AtInsert())
+
+# Readonly Man Page class -- disables editing, otherwise the same
+class ReadonlyManPage(EditableManPage):
+
+ # Initialize instance
+ def __init__(self, master=None, **cnf):
+ cnf['state'] = DISABLED
+ apply(EditableManPage.__init__, (self, master), cnf)
+
+# Alias
+ManPage = ReadonlyManPage
+
+# Test program.
+# usage: ManPage [manpage]; or ManPage [-f] file
+# -f means that the file is nroff -man output run through ul -i
+def test():
+ import os
+ import sys
+ # XXX This directory may be different on your system
+ MANDIR = '/usr/local/man/mann'
+ DEFAULTPAGE = 'Tcl'
+ formatted = 0
+ if sys.argv[1:] and sys.argv[1] == '-f':
+ formatted = 1
+ del sys.argv[1]
+ if sys.argv[1:]:
+ name = sys.argv[1]
+ else:
+ name = DEFAULTPAGE
+ if not formatted:
+ if name[-2:-1] != '.':
+ name = name + '.n'
+ name = os.path.join(MANDIR, name)
+ root = Tk()
+ root.minsize(1, 1)
+ manpage = ManPage(root, relief=SUNKEN, borderwidth=2)
+ manpage.pack(expand=1, fill=BOTH)
+ if formatted:
+ fp = open(name, 'r')
+ else:
+ fp = os.popen('nroff -man %s | ul -i' % name, 'r')
+ manpage.parsefile(fp)
+ root.mainloop()
+
+# Run the test program when called as a script
+if __name__ == '__main__':
+ test()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py b/sys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py
new file mode 100755
index 000000000..749442589
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py
@@ -0,0 +1,143 @@
+#! /usr/bin/env python
+
+# View a single MIME multipart message.
+# Display each part as a box.
+
+import string
+from types import *
+from Tkinter import *
+from ScrolledText import ScrolledText
+
+class MimeViewer:
+ def __init__(self, parent, title, msg):
+ self.title = title
+ self.msg = msg
+ self.frame = Frame(parent, {'relief': 'raised', 'bd': 2})
+ self.frame.packing = {'expand': 0, 'fill': 'both'}
+ self.button = Checkbutton(self.frame,
+ {'text': title,
+ 'command': self.toggle})
+ self.button.pack({'anchor': 'w'})
+ headertext = msg.getheadertext(
+ lambda x: x != 'received' and x[:5] != 'x400-')
+ height = countlines(headertext, 4)
+ if height:
+ self.htext = ScrolledText(self.frame,
+ {'height': height,
+ 'width': 80,
+ 'wrap': 'none',
+ 'relief': 'raised',
+ 'bd': 2})
+ self.htext.packing = {'expand': 1, 'fill': 'both',
+ 'after': self.button}
+ self.htext.insert('end', headertext)
+ else:
+ self.htext = Frame(self.frame,
+ {'relief': 'raised', 'bd': 2})
+ self.htext.packing = {'side': 'top',
+ 'ipady': 2,
+ 'fill': 'x',
+ 'after': self.button}
+ body = msg.getbody()
+ if type(body) == StringType:
+ self.pad = None
+ height = countlines(body, 10)
+ if height:
+ self.btext = ScrolledText(self.frame,
+ {'height': height,
+ 'width': 80,
+ 'wrap': 'none',
+ 'relief': 'raised',
+ 'bd': 2})
+ self.btext.packing = {'expand': 1,
+ 'fill': 'both'}
+ self.btext.insert('end', body)
+ else:
+ self.btext = None
+ self.parts = None
+ else:
+ self.pad = Frame(self.frame,
+ {'relief': 'flat', 'bd': 2})
+ self.pad.packing = {'side': 'left', 'ipadx': 10,
+ 'fill': 'y', 'after': self.htext}
+ self.parts = []
+ for i in range(len(body)):
+ p = MimeViewer(self.frame,
+ '%s.%d' % (title, i+1),
+ body[i])
+ self.parts.append(p)
+ self.btext = None
+ self.collapsed = 1
+ def pack(self):
+ self.frame.pack(self.frame.packing)
+ def destroy(self):
+ self.frame.destroy()
+ def show(self):
+ if self.collapsed:
+ self.button.invoke()
+ def toggle(self):
+ if self.collapsed:
+ self.explode()
+ else:
+ self.collapse()
+ def collapse(self):
+ self.collapsed = 1
+ for comp in self.htext, self.btext, self.pad:
+ if comp:
+ comp.forget()
+ if self.parts:
+ for part in self.parts:
+ part.frame.forget()
+ self.frame.pack({'expand': 0})
+ def explode(self):
+ self.collapsed = 0
+ for comp in self.htext, self.btext, self.pad:
+ if comp: comp.pack(comp.packing)
+ if self.parts:
+ for part in self.parts:
+ part.pack()
+ self.frame.pack({'expand': 1})
+
+def countlines(str, limit):
+ i = 0
+ n = 0
+ while n < limit:
+ i = string.find(str, '\n', i)
+ if i < 0: break
+ n = n+1
+ i = i+1
+ return n
+
+def main():
+ import sys
+ import getopt
+ import mhlib
+ opts, args = getopt.getopt(sys.argv[1:], '')
+ for o, a in opts:
+ pass
+ message = None
+ folder = 'inbox'
+ for arg in args:
+ if arg[:1] == '+':
+ folder = arg[1:]
+ else:
+ message = string.atoi(arg)
+
+ mh = mhlib.MH()
+ f = mh.openfolder(folder)
+ if not message:
+ message = f.getcurrent()
+ m = f.openmessage(message)
+
+ root = Tk()
+ tk = root.tk
+
+ top = MimeViewer(root, '+%s/%d' % (folder, message), m)
+ top.pack()
+ top.show()
+
+ root.minsize(1, 1)
+
+ tk.mainloop()
+
+if __name__ == '__main__': main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py b/sys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py
new file mode 100755
index 000000000..609101bc8
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py
@@ -0,0 +1,151 @@
+import os
+import sys
+import string
+from Tkinter import *
+from ScrolledText import ScrolledText
+from Dialog import Dialog
+import signal
+
+BUFSIZE = 512
+
+class ShellWindow(ScrolledText):
+
+ def __init__(self, master=None, shell=None, **cnf):
+ if not shell:
+ try:
+ shell = os.environ['SHELL']
+ except KeyError:
+ shell = '/bin/sh'
+ shell = shell + ' -i'
+ args = string.split(shell)
+ shell = args[0]
+
+ apply(ScrolledText.__init__, (self, master), cnf)
+ self.pos = '1.0'
+ self.bind('<Return>', self.inputhandler)
+ self.bind('<Control-c>', self.sigint)
+ self.bind('<Control-t>', self.sigterm)
+ self.bind('<Control-k>', self.sigkill)
+ self.bind('<Control-d>', self.sendeof)
+
+ self.pid, self.fromchild, self.tochild = spawn(shell, args)
+ self.tk.createfilehandler(self.fromchild, READABLE,
+ self.outputhandler)
+
+ def outputhandler(self, file, mask):
+ data = os.read(file, BUFSIZE)
+ if not data:
+ self.tk.deletefilehandler(file)
+ pid, sts = os.waitpid(self.pid, 0)
+ print 'pid', pid, 'status', sts
+ self.pid = None
+ detail = sts>>8
+ cause = sts & 0xff
+ if cause == 0:
+ msg = "exit status %d" % detail
+ else:
+ msg = "killed by signal %d" % (cause & 0x7f)
+ if cause & 0x80:
+ msg = msg + " -- core dumped"
+ Dialog(self.master,
+ text=msg,
+ title="Exit status",
+ bitmap='warning',
+ default=0,
+ strings=('OK',))
+ return
+ self.insert(END, data)
+ self.pos = self.index("end - 1 char")
+ self.yview_pickplace(END)
+
+ def inputhandler(self, *args):
+ if not self.pid:
+ self.no_process()
+ return "break"
+ self.insert(END, "\n")
+ line = self.get(self.pos, "end - 1 char")
+ self.pos = self.index(END)
+ os.write(self.tochild, line)
+ return "break"
+
+ def sendeof(self, *args):
+ if not self.pid:
+ self.no_process()
+ return "break"
+ os.close(self.tochild)
+ return "break"
+
+ def sendsig(self, sig):
+ if not self.pid:
+ self.no_process()
+ return "break"
+ os.kill(self.pid, sig)
+ return "break"
+
+ def sigint(self, *args):
+ return self.sendsig(signal.SIGINT)
+
+ def sigquit(self, *args):
+ return self.sendsig(signal.SIGQUIT)
+
+ def sigterm(self, *args):
+ return self.sendsig(signal.SIGTERM)
+
+ def sigkill(self, *args):
+ return self.sendsig(signal.SIGKILL)
+
+ def no_process(self):
+ Dialog(self.master,
+ text="No active process",
+ title="No process",
+ bitmap='error',
+ default=0,
+ strings=('OK',))
+
+MAXFD = 100 # Max number of file descriptors (os.getdtablesize()???)
+
+def spawn(prog, args):
+ p2cread, p2cwrite = os.pipe()
+ c2pread, c2pwrite = os.pipe()
+ pid = os.fork()
+ if pid == 0:
+ # Child
+ for i in 0, 1, 2:
+ try:
+ os.close(i)
+ except os.error:
+ pass
+ if os.dup(p2cread) <> 0:
+ sys.stderr.write('popen2: bad read dup\n')
+ if os.dup(c2pwrite) <> 1:
+ sys.stderr.write('popen2: bad write dup\n')
+ if os.dup(c2pwrite) <> 2:
+ sys.stderr.write('popen2: bad write dup\n')
+ for i in range(3, MAXFD):
+ try:
+ os.close(i)
+ except:
+ pass
+ try:
+ os.execvp(prog, args)
+ finally:
+ sys.stderr.write('execvp failed\n')
+ os._exit(1)
+ os.close(p2cread)
+ os.close(c2pwrite)
+ return pid, c2pread, p2cwrite
+
+def test():
+ shell = string.join(sys.argv[1:])
+ root = Tk()
+ root.minsize(1, 1)
+ if shell:
+ w = ShellWindow(root, shell=shell)
+ else:
+ w = ShellWindow(root)
+ w.pack(expand=1, fill=BOTH)
+ w.focus_set()
+ w.tk.mainloop()
+
+if __name__ == '__main__':
+ test()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/brownian.py b/sys/src/cmd/python/Demo/tkinter/guido/brownian.py
new file mode 100644
index 000000000..8007f141c
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/brownian.py
@@ -0,0 +1,50 @@
+# Brownian motion -- an example of a multi-threaded Tkinter program.
+
+from Tkinter import *
+import random
+import threading
+import time
+import sys
+
+WIDTH = 400
+HEIGHT = 300
+SIGMA = 10
+BUZZ = 2
+RADIUS = 2
+LAMBDA = 10
+FILL = 'red'
+
+stop = 0 # Set when main loop exits
+
+def particle(canvas):
+ r = RADIUS
+ x = random.gauss(WIDTH/2.0, SIGMA)
+ y = random.gauss(HEIGHT/2.0, SIGMA)
+ p = canvas.create_oval(x-r, y-r, x+r, y+r, fill=FILL)
+ while not stop:
+ dx = random.gauss(0, BUZZ)
+ dy = random.gauss(0, BUZZ)
+ dt = random.expovariate(LAMBDA)
+ try:
+ canvas.move(p, dx, dy)
+ except TclError:
+ break
+ time.sleep(dt)
+
+def main():
+ global stop
+ root = Tk()
+ canvas = Canvas(root, width=WIDTH, height=HEIGHT)
+ canvas.pack(fill='both', expand=1)
+ np = 30
+ if sys.argv[1:]:
+ np = int(sys.argv[1])
+ for i in range(np):
+ t = threading.Thread(target=particle, args=(canvas,))
+ t.start()
+ try:
+ root.mainloop()
+ finally:
+ stop = 1
+
+main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/canvasevents.py b/sys/src/cmd/python/Demo/tkinter/guido/canvasevents.py
new file mode 100644
index 000000000..74ed76f61
--- /dev/null
+++ b/sys/src/cmd/python/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/sys/src/cmd/python/Demo/tkinter/guido/dialog.py b/sys/src/cmd/python/Demo/tkinter/guido/dialog.py
new file mode 100755
index 000000000..50d84b9a3
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/dialog.py
@@ -0,0 +1,109 @@
+#! /usr/bin/env python
+
+# A Python function that generates dialog boxes with a text message,
+# optional bitmap, and any number of buttons.
+# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.2-3, pp. 269-270.
+
+from Tkinter import *
+import sys
+
+
+def dialog(master, title, text, bitmap, default, *args):
+
+ # 1. Create the top-level window and divide it into top
+ # and bottom parts.
+
+ w = Toplevel(master, class_='Dialog')
+ w.title(title)
+ w.iconname('Dialog')
+
+ top = Frame(w, relief=RAISED, borderwidth=1)
+ top.pack(side=TOP, fill=BOTH)
+ bot = Frame(w, relief=RAISED, borderwidth=1)
+ bot.pack(side=BOTTOM, fill=BOTH)
+
+ # 2. Fill the top part with the bitmap and message.
+
+ msg = Message(top, width='3i', text=text,
+ font='-Adobe-Times-Medium-R-Normal-*-180-*')
+ msg.pack(side=RIGHT, expand=1, fill=BOTH, padx='3m', pady='3m')
+ if bitmap:
+ bm = Label(top, bitmap=bitmap)
+ bm.pack(side=LEFT, padx='3m', pady='3m')
+
+ # 3. Create a row of buttons at the bottom of the dialog.
+
+ var = IntVar()
+ buttons = []
+ i = 0
+ for but in args:
+ b = Button(bot, text=but, command=lambda v=var,i=i: v.set(i))
+ buttons.append(b)
+ if i == default:
+ bd = Frame(bot, relief=SUNKEN, borderwidth=1)
+ bd.pack(side=LEFT, expand=1, padx='3m', pady='2m')
+ b.lift()
+ b.pack (in_=bd, side=LEFT,
+ padx='2m', pady='2m', ipadx='2m', ipady='1m')
+ else:
+ b.pack (side=LEFT, expand=1,
+ padx='3m', pady='3m', ipadx='2m', ipady='1m')
+ i = i+1
+
+ # 4. Set up a binding for <Return>, if there's a default,
+ # set a grab, and claim the focus too.
+
+ if default >= 0:
+ w.bind('<Return>',
+ lambda e, b=buttons[default], v=var, i=default:
+ (b.flash(),
+ v.set(i)))
+
+ oldFocus = w.focus_get()
+ w.grab_set()
+ w.focus_set()
+
+ # 5. Wait for the user to respond, then restore the focus
+ # and return the index of the selected button.
+
+ w.waitvar(var)
+ w.destroy()
+ if oldFocus: oldFocus.focus_set()
+ return var.get()
+
+# The rest is the test program.
+
+def go():
+ i = dialog(mainWidget,
+ 'Not Responding',
+ "The file server isn't responding right now; "
+ "I'll keep trying.",
+ '',
+ -1,
+ 'OK')
+ print 'pressed button', i
+ i = dialog(mainWidget,
+ 'File Modified',
+ 'File "tcl.h" has been modified since '
+ 'the last time it was saved. '
+ 'Do you want to save it before exiting the application?',
+ 'warning',
+ 0,
+ 'Save File',
+ 'Discard Changes',
+ 'Return To Editor')
+ print 'pressed button', i
+
+def test():
+ import sys
+ global mainWidget
+ mainWidget = Frame()
+ Pack.config(mainWidget)
+ start = Button(mainWidget, text='Press Here To Start', command=go)
+ start.pack()
+ endit = Button(mainWidget, text="Exit", command=sys.exit)
+ endit.pack(fill=BOTH)
+ mainWidget.mainloop()
+
+if __name__ == '__main__':
+ test()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/electrons.py b/sys/src/cmd/python/Demo/tkinter/guido/electrons.py
new file mode 100755
index 000000000..fdc558f88
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/electrons.py
@@ -0,0 +1,91 @@
+#! /usr/bin/env python
+
+# Simulate "electrons" migrating across the screen.
+# An optional bitmap file in can be in the background.
+#
+# Usage: electrons [n [bitmapfile]]
+#
+# n is the number of electrons to animate; default is 30.
+#
+# The bitmap file can be any X11 bitmap file (look in
+# /usr/include/X11/bitmaps for samples); it is displayed as the
+# background of the animation. Default is no bitmap.
+
+from Tkinter import *
+import random
+
+
+# The graphical interface
+class Electrons:
+
+ # Create our objects
+ def __init__(self, n, bitmap = None):
+ self.n = n
+ self.tk = tk = Tk()
+ self.canvas = c = Canvas(tk)
+ c.pack()
+ width, height = tk.getint(c['width']), tk.getint(c['height'])
+
+ # Add background bitmap
+ if bitmap:
+ self.bitmap = c.create_bitmap(width/2, height/2,
+ bitmap=bitmap,
+ foreground='blue')
+
+ self.pieces = []
+ x1, y1, x2, y2 = 10,70,14,74
+ for i in range(n):
+ p = c.create_oval(x1, y1, x2, y2, fill='red')
+ self.pieces.append(p)
+ y1, y2 = y1 +2, y2 + 2
+ self.tk.update()
+
+ def random_move(self, n):
+ c = self.canvas
+ for p in self.pieces:
+ x = random.choice(range(-2,4))
+ y = random.choice(range(-3,4))
+ c.move(p, x, y)
+ self.tk.update()
+
+ # Run -- allow 500 movemens
+ def run(self):
+ try:
+ for i in range(500):
+ self.random_move(self.n)
+ except TclError:
+ try:
+ self.tk.destroy()
+ except TclError:
+ pass
+
+
+# Main program
+def main():
+ import sys, string
+
+ # First argument is number of electrons, default 30
+ if sys.argv[1:]:
+ n = string.atoi(sys.argv[1])
+ else:
+ n = 30
+
+ # Second argument is bitmap file, default none
+ if sys.argv[2:]:
+ bitmap = sys.argv[2]
+ # Reverse meaning of leading '@' compared to Tk
+ if bitmap[0] == '@': bitmap = bitmap[1:]
+ else: bitmap = '@' + bitmap
+ else:
+ bitmap = None
+
+ # Create the graphical objects...
+ h = Electrons(n, bitmap)
+
+ # ...and run!
+ h.run()
+
+
+# Call main when run as script
+if __name__ == '__main__':
+ main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/hanoi.py b/sys/src/cmd/python/Demo/tkinter/guido/hanoi.py
new file mode 100755
index 000000000..078c24611
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/hanoi.py
@@ -0,0 +1,154 @@
+# Animated Towers of Hanoi using Tk with optional bitmap file in
+# background.
+#
+# Usage: tkhanoi [n [bitmapfile]]
+#
+# n is the number of pieces to animate; default is 4, maximum 15.
+#
+# The bitmap file can be any X11 bitmap file (look in
+# /usr/include/X11/bitmaps for samples); it is displayed as the
+# background of the animation. Default is no bitmap.
+
+# This uses Steen Lumholt's Tk interface
+from Tkinter import *
+
+
+# Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c
+# as temporary. For each move, call report()
+def hanoi(n, a, b, c, report):
+ if n <= 0: return
+ hanoi(n-1, a, c, b, report)
+ report(n, a, b)
+ hanoi(n-1, c, b, a, report)
+
+
+# The graphical interface
+class Tkhanoi:
+
+ # Create our objects
+ def __init__(self, n, bitmap = None):
+ self.n = n
+ self.tk = tk = Tk()
+ self.canvas = c = Canvas(tk)
+ c.pack()
+ width, height = tk.getint(c['width']), tk.getint(c['height'])
+
+ # Add background bitmap
+ if bitmap:
+ self.bitmap = c.create_bitmap(width/2, height/2,
+ bitmap=bitmap,
+ foreground='blue')
+
+ # Generate pegs
+ pegwidth = 10
+ pegheight = height/2
+ pegdist = width/3
+ x1, y1 = (pegdist-pegwidth)/2, height*1/3
+ x2, y2 = x1+pegwidth, y1+pegheight
+ self.pegs = []
+ p = c.create_rectangle(x1, y1, x2, y2, fill='black')
+ self.pegs.append(p)
+ x1, x2 = x1+pegdist, x2+pegdist
+ p = c.create_rectangle(x1, y1, x2, y2, fill='black')
+ self.pegs.append(p)
+ x1, x2 = x1+pegdist, x2+pegdist
+ p = c.create_rectangle(x1, y1, x2, y2, fill='black')
+ self.pegs.append(p)
+ self.tk.update()
+
+ # Generate pieces
+ pieceheight = pegheight/16
+ maxpiecewidth = pegdist*2/3
+ minpiecewidth = 2*pegwidth
+ self.pegstate = [[], [], []]
+ self.pieces = {}
+ x1, y1 = (pegdist-maxpiecewidth)/2, y2-pieceheight-2
+ x2, y2 = x1+maxpiecewidth, y1+pieceheight
+ dx = (maxpiecewidth-minpiecewidth) / (2*max(1, n-1))
+ for i in range(n, 0, -1):
+ p = c.create_rectangle(x1, y1, x2, y2, fill='red')
+ self.pieces[i] = p
+ self.pegstate[0].append(i)
+ x1, x2 = x1 + dx, x2-dx
+ y1, y2 = y1 - pieceheight-2, y2-pieceheight-2
+ self.tk.update()
+ self.tk.after(25)
+
+ # Run -- never returns
+ def run(self):
+ while 1:
+ hanoi(self.n, 0, 1, 2, self.report)
+ hanoi(self.n, 1, 2, 0, self.report)
+ hanoi(self.n, 2, 0, 1, self.report)
+ hanoi(self.n, 0, 2, 1, self.report)
+ hanoi(self.n, 2, 1, 0, self.report)
+ hanoi(self.n, 1, 0, 2, self.report)
+
+ # Reporting callback for the actual hanoi function
+ def report(self, i, a, b):
+ if self.pegstate[a][-1] != i: raise RuntimeError # Assertion
+ del self.pegstate[a][-1]
+ p = self.pieces[i]
+ c = self.canvas
+
+ # Lift the piece above peg a
+ ax1, ay1, ax2, ay2 = c.bbox(self.pegs[a])
+ while 1:
+ x1, y1, x2, y2 = c.bbox(p)
+ if y2 < ay1: break
+ c.move(p, 0, -1)
+ self.tk.update()
+
+ # Move it towards peg b
+ bx1, by1, bx2, by2 = c.bbox(self.pegs[b])
+ newcenter = (bx1+bx2)/2
+ while 1:
+ x1, y1, x2, y2 = c.bbox(p)
+ center = (x1+x2)/2
+ if center == newcenter: break
+ if center > newcenter: c.move(p, -1, 0)
+ else: c.move(p, 1, 0)
+ self.tk.update()
+
+ # Move it down on top of the previous piece
+ pieceheight = y2-y1
+ newbottom = by2 - pieceheight*len(self.pegstate[b]) - 2
+ while 1:
+ x1, y1, x2, y2 = c.bbox(p)
+ if y2 >= newbottom: break
+ c.move(p, 0, 1)
+ self.tk.update()
+
+ # Update peg state
+ self.pegstate[b].append(i)
+
+
+# Main program
+def main():
+ import sys, string
+
+ # First argument is number of pegs, default 4
+ if sys.argv[1:]:
+ n = string.atoi(sys.argv[1])
+ else:
+ n = 4
+
+ # Second argument is bitmap file, default none
+ if sys.argv[2:]:
+ bitmap = sys.argv[2]
+ # Reverse meaning of leading '@' compared to Tk
+ if bitmap[0] == '@': bitmap = bitmap[1:]
+ else: bitmap = '@' + bitmap
+ else:
+ bitmap = None
+
+ # Create the graphical objects...
+ h = Tkhanoi(n, bitmap)
+
+ # ...and run!
+ h.run()
+
+
+# Call main when run as script
+if __name__ == '__main__':
+ main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/hello.py b/sys/src/cmd/python/Demo/tkinter/guido/hello.py
new file mode 100755
index 000000000..358a7eceb
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/hello.py
@@ -0,0 +1,17 @@
+# Display hello, world in a button; clicking it quits the program
+
+import sys
+from Tkinter import *
+
+def main():
+ root = Tk()
+ button = Button(root)
+ button['text'] = 'Hello, world'
+ button['command'] = quit_callback # See below
+ button.pack()
+ root.mainloop()
+
+def quit_callback():
+ sys.exit(0)
+
+main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/imagedraw.py b/sys/src/cmd/python/Demo/tkinter/guido/imagedraw.py
new file mode 100755
index 000000000..d3dba4565
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/imagedraw.py
@@ -0,0 +1,23 @@
+"""Draw on top of an image"""
+
+from Tkinter import *
+import sys
+
+def main():
+ filename = sys.argv[1]
+ root = Tk()
+ img = PhotoImage(file=filename)
+ w, h = img.width(), img.height()
+ canv = Canvas(root, width=w, height=h)
+ canv.create_image(0, 0, anchor=NW, image=img)
+ canv.pack()
+ canv.bind('<Button-1>', blob)
+ root.mainloop()
+
+def blob(event):
+ x, y = event.x, event.y
+ canv = event.widget
+ r = 5
+ canv.create_oval(x-r, y-r, x+r, y+r, fill='red', outline="")
+
+main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/imageview.py b/sys/src/cmd/python/Demo/tkinter/guido/imageview.py
new file mode 100755
index 000000000..d6efed0b2
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/imageview.py
@@ -0,0 +1,12 @@
+from Tkinter import *
+import sys
+
+def main():
+ filename = sys.argv[1]
+ root = Tk()
+ img = PhotoImage(file=filename)
+ label = Label(root, image=img)
+ label.pack()
+ root.mainloop()
+
+main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/kill.py b/sys/src/cmd/python/Demo/tkinter/guido/kill.py
new file mode 100755
index 000000000..e7df26121
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/kill.py
@@ -0,0 +1,98 @@
+#! /usr/bin/env python
+# Tkinter interface to Linux `kill' command.
+
+from Tkinter import *
+from string import splitfields
+from string import split
+import commands
+import os
+
+class BarButton(Menubutton):
+ def __init__(self, master=None, **cnf):
+ apply(Menubutton.__init__, (self, master), cnf)
+ self.pack(side=LEFT)
+ self.menu = Menu(self, name='menu')
+ self['menu'] = self.menu
+
+class Kill(Frame):
+ # List of (name, option, pid_column)
+ format_list = [('Default', '', 0),
+ ('Long', '-l', 2),
+ ('User', '-u', 1),
+ ('Jobs', '-j', 1),
+ ('Signal', '-s', 1),
+ ('Memory', '-m', 0),
+ ('VM', '-v', 0),
+ ('Hex', '-X', 0)]
+ def kill(self, selected):
+ c = self.format_list[self.format.get()][2]
+ pid = split(selected)[c]
+ os.system('kill -9 ' + pid)
+ self.do_update()
+ def do_update(self):
+ name, option, column = self.format_list[self.format.get()]
+ s = commands.getoutput('ps -w ' + option)
+ list = splitfields(s, '\n')
+ self.header.set(list[0])
+ del list[0]
+ y = self.frame.vscroll.get()[0]
+ self.frame.list.delete(0, AtEnd())
+ for line in list:
+ self.frame.list.insert(0, line)
+ self.frame.list.yview(int(y))
+ def do_motion(self, e):
+ e.widget.select_clear(0, END)
+ e.widget.select_set(e.widget.nearest(e.y))
+ def do_leave(self, e):
+ e.widget.select_clear(0, END)
+ def do_1(self, e):
+ self.kill(e.widget.get(e.widget.nearest(e.y)))
+ def __init__(self, master=None, **cnf):
+ Frame.__init__(self, master, cnf)
+ self.pack(expand=1, fill=BOTH)
+ self.bar = Frame(self, name='bar', relief=RAISED,
+ borderwidth=2)
+ self.bar.pack(fill=X)
+ self.bar.file = BarButton(self.bar, text='File')
+ self.bar.file.menu.add_command(
+ label='Quit', command=self.quit)
+ self.bar.view = BarButton(self.bar, text='View')
+ self.format = IntVar(self)
+ self.format.set(2)
+ for num in range(len(self.format_list)):
+ self.bar.view.menu.add_radiobutton(
+ label=self.format_list[num][0],
+ command=self.do_update,
+ variable=self.format,
+ value=num)
+ #self.bar.view.menu.add_separator()
+ #XXX ...
+ self.bar.tk_menuBar(self.bar.file, self.bar.view)
+ self.frame = Frame(self, relief=RAISED, borderwidth=2)
+ self.frame.pack(expand=1, fill=BOTH)
+ self.header = StringVar(self)
+ self.frame.label = Label(self.frame, relief=FLAT, anchor=NW,
+ borderwidth=0,
+ textvariable=self.header)
+ self.frame.label.pack(fill=X)
+ self.frame.vscroll = Scrollbar(self.frame, orient=VERTICAL)
+ self.frame.list = Listbox(self.frame, relief=SUNKEN,
+ selectbackground='#eed5b7',
+ selectborderwidth=0,
+ yscroll=self.frame.vscroll.set)
+ self.frame.vscroll['command'] = self.frame.list.yview
+ self.frame.vscroll.pack(side=RIGHT, fill=Y)
+ self.frame.list.pack(expand=1, fill=BOTH)
+ self.update = Button(self, text="Update",
+ command=self.do_update)
+ self.update.pack(expand=1, fill=X)
+ self.frame.list.bind('<Motion>', self.do_motion)
+ self.frame.list.bind('<Leave>', self.do_leave)
+ self.frame.list.bind('<1>', self.do_1)
+ self.do_update()
+
+if __name__ == '__main__':
+ kill = Kill(None, borderwidth=5)
+ kill.winfo_toplevel().title('Tkinter Process Killer')
+ kill.winfo_toplevel().minsize(1, 1)
+ kill.mainloop()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/listtree.py b/sys/src/cmd/python/Demo/tkinter/guido/listtree.py
new file mode 100755
index 000000000..d28ce49ef
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/listtree.py
@@ -0,0 +1,37 @@
+# List a remote app's widget tree (names and classes only)
+
+import sys
+import string
+
+from Tkinter import *
+
+def listtree(master, app):
+ list = Listbox(master, name='list')
+ list.pack(expand=1, fill=BOTH)
+ listnodes(list, app, '.', 0)
+ return list
+
+def listnodes(list, app, widget, level):
+ klass = list.send(app, 'winfo', 'class', widget)
+## i = string.rindex(widget, '.')
+## list.insert(END, '%s%s (%s)' % ((level-1)*'. ', widget[i:], klass))
+ list.insert(END, '%s (%s)' % (widget, klass))
+ children = list.tk.splitlist(
+ list.send(app, 'winfo', 'children', widget))
+ for c in children:
+ listnodes(list, app, c, level+1)
+
+def main():
+ if not sys.argv[1:]:
+ sys.stderr.write('Usage: listtree appname\n')
+ sys.exit(2)
+ app = sys.argv[1]
+ tk = Tk()
+ tk.minsize(1, 1)
+ f = Frame(tk, name='f')
+ f.pack(expand=1, fill=BOTH)
+ list = listtree(f, app)
+ tk.mainloop()
+
+if __name__ == '__main__':
+ main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/mbox.py b/sys/src/cmd/python/Demo/tkinter/guido/mbox.py
new file mode 100755
index 000000000..3c36d8899
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/mbox.py
@@ -0,0 +1,285 @@
+#! /usr/bin/env python
+
+# Scan MH folder, display results in window
+
+import os
+import sys
+import re
+import getopt
+import string
+import mhlib
+
+from Tkinter import *
+
+from dialog import dialog
+
+mailbox = os.environ['HOME'] + '/Mail'
+
+def main():
+ global root, tk, top, mid, bot
+ global folderbox, foldermenu, scanbox, scanmenu, viewer
+ global folder, seq
+ global mh, mhf
+
+ # Parse command line options
+
+ folder = 'inbox'
+ seq = 'all'
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], '')
+ except getopt.error, msg:
+ print msg
+ sys.exit(2)
+ for arg in args:
+ if arg[:1] == '+':
+ folder = arg[1:]
+ else:
+ seq = arg
+
+ # Initialize MH
+
+ mh = mhlib.MH()
+ mhf = mh.openfolder(folder)
+
+ # Build widget hierarchy
+
+ root = Tk()
+ tk = root.tk
+
+ top = Frame(root)
+ top.pack({'expand': 1, 'fill': 'both'})
+
+ # Build right part: folder list
+
+ right = Frame(top)
+ right.pack({'fill': 'y', 'side': 'right'})
+
+ folderbar = Scrollbar(right, {'relief': 'sunken', 'bd': 2})
+ folderbar.pack({'fill': 'y', 'side': 'right'})
+
+ folderbox = Listbox(right, {'exportselection': 0})
+ folderbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
+
+ foldermenu = Menu(root)
+ foldermenu.add('command',
+ {'label': 'Open Folder',
+ 'command': open_folder})
+ foldermenu.add('separator')
+ foldermenu.add('command',
+ {'label': 'Quit',
+ 'command': 'exit'})
+ foldermenu.bind('<ButtonRelease-3>', folder_unpost)
+
+ folderbox['yscrollcommand'] = (folderbar, 'set')
+ folderbar['command'] = (folderbox, 'yview')
+ folderbox.bind('<Double-1>', open_folder, 1)
+ folderbox.bind('<3>', folder_post)
+
+ # Build left part: scan list
+
+ left = Frame(top)
+ left.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
+
+ scanbar = Scrollbar(left, {'relief': 'sunken', 'bd': 2})
+ scanbar.pack({'fill': 'y', 'side': 'right'})
+
+ scanbox = Listbox(left, {'font': 'fixed'})
+ scanbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'})
+
+ scanmenu = Menu(root)
+ scanmenu.add('command',
+ {'label': 'Open Message',
+ 'command': open_message})
+ scanmenu.add('command',
+ {'label': 'Remove Message',
+ 'command': remove_message})
+ scanmenu.add('command',
+ {'label': 'Refile Message',
+ 'command': refile_message})
+ scanmenu.add('separator')
+ scanmenu.add('command',
+ {'label': 'Quit',
+ 'command': 'exit'})
+ scanmenu.bind('<ButtonRelease-3>', scan_unpost)
+
+ scanbox['yscrollcommand'] = (scanbar, 'set')
+ scanbar['command'] = (scanbox, 'yview')
+ scanbox.bind('<Double-1>', open_message)
+ scanbox.bind('<3>', scan_post)
+
+ # Separator between middle and bottom part
+
+ rule2 = Frame(root, {'bg': 'black'})
+ rule2.pack({'fill': 'x'})
+
+ # Build bottom part: current message
+
+ bot = Frame(root)
+ bot.pack({'expand': 1, 'fill': 'both'})
+ #
+ viewer = None
+
+ # Window manager commands
+
+ root.minsize(800, 1) # Make window resizable
+
+ # Fill folderbox with text
+
+ setfolders()
+
+ # Fill scanbox with text
+
+ rescan()
+
+ # Enter mainloop
+
+ root.mainloop()
+
+def folder_post(e):
+ x, y = e.x_root, e.y_root
+ foldermenu.post(x - 10, y - 10)
+ foldermenu.grab_set()
+
+def folder_unpost(e):
+ tk.call('update', 'idletasks')
+ foldermenu.grab_release()
+ foldermenu.unpost()
+ foldermenu.invoke('active')
+
+def scan_post(e):
+ x, y = e.x_root, e.y_root
+ scanmenu.post(x - 10, y - 10)
+ scanmenu.grab_set()
+
+def scan_unpost(e):
+ tk.call('update', 'idletasks')
+ scanmenu.grab_release()
+ scanmenu.unpost()
+ scanmenu.invoke('active')
+
+scanparser = re.compile('^ *([0-9]+)')
+
+def open_folder(e=None):
+ global folder, mhf
+ sel = folderbox.curselection()
+ if len(sel) != 1:
+ if len(sel) > 1:
+ msg = "Please open one folder at a time"
+ else:
+ msg = "Please select a folder to open"
+ dialog(root, "Can't Open Folder", msg, "", 0, "OK")
+ return
+ i = sel[0]
+ folder = folderbox.get(i)
+ mhf = mh.openfolder(folder)
+ rescan()
+
+def open_message(e=None):
+ global viewer
+ sel = scanbox.curselection()
+ if len(sel) != 1:
+ if len(sel) > 1:
+ msg = "Please open one message at a time"
+ else:
+ msg = "Please select a message to open"
+ dialog(root, "Can't Open Message", msg, "", 0, "OK")
+ return
+ cursor = scanbox['cursor']
+ scanbox['cursor'] = 'watch'
+ tk.call('update', 'idletasks')
+ i = sel[0]
+ line = scanbox.get(i)
+ if scanparser.match(line) >= 0:
+ num = string.atoi(scanparser.group(1))
+ m = mhf.openmessage(num)
+ if viewer: viewer.destroy()
+ from MimeViewer import MimeViewer
+ viewer = MimeViewer(bot, '+%s/%d' % (folder, num), m)
+ viewer.pack()
+ viewer.show()
+ scanbox['cursor'] = cursor
+
+def interestingheader(header):
+ return header != 'received'
+
+def remove_message(e=None):
+ itop = scanbox.nearest(0)
+ sel = scanbox.curselection()
+ if not sel:
+ dialog(root, "No Message To Remove",
+ "Please select a message to remove", "", 0, "OK")
+ return
+ todo = []
+ for i in sel:
+ line = scanbox.get(i)
+ if scanparser.match(line) >= 0:
+ todo.append(string.atoi(scanparser.group(1)))
+ mhf.removemessages(todo)
+ rescan()
+ fixfocus(min(todo), itop)
+
+lastrefile = ''
+tofolder = None
+def refile_message(e=None):
+ global lastrefile, tofolder
+ itop = scanbox.nearest(0)
+ sel = scanbox.curselection()
+ if not sel:
+ dialog(root, "No Message To Refile",
+ "Please select a message to refile", "", 0, "OK")
+ return
+ foldersel = folderbox.curselection()
+ if len(foldersel) != 1:
+ if not foldersel:
+ msg = "Please select a folder to refile to"
+ else:
+ msg = "Please select exactly one folder to refile to"
+ dialog(root, "No Folder To Refile", msg, "", 0, "OK")
+ return
+ refileto = folderbox.get(foldersel[0])
+ todo = []
+ for i in sel:
+ line = scanbox.get(i)
+ if scanparser.match(line) >= 0:
+ todo.append(string.atoi(scanparser.group(1)))
+ if lastrefile != refileto or not tofolder:
+ lastrefile = refileto
+ tofolder = None
+ tofolder = mh.openfolder(lastrefile)
+ mhf.refilemessages(todo, tofolder)
+ rescan()
+ fixfocus(min(todo), itop)
+
+def fixfocus(near, itop):
+ n = scanbox.size()
+ for i in range(n):
+ line = scanbox.get(repr(i))
+ if scanparser.match(line) >= 0:
+ num = string.atoi(scanparser.group(1))
+ if num >= near:
+ break
+ else:
+ i = 'end'
+ scanbox.select_from(i)
+ scanbox.yview(itop)
+
+def setfolders():
+ folderbox.delete(0, 'end')
+ for fn in mh.listallfolders():
+ folderbox.insert('end', fn)
+
+def rescan():
+ global viewer
+ if viewer:
+ viewer.destroy()
+ viewer = None
+ scanbox.delete(0, 'end')
+ for line in scanfolder(folder, seq):
+ scanbox.insert('end', line)
+
+def scanfolder(folder = 'inbox', sequence = 'all'):
+ return map(
+ lambda line: line[:-1],
+ os.popen('scan +%s %s' % (folder, sequence), 'r').readlines())
+
+main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py b/sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py
new file mode 100644
index 000000000..57bf13c44
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py
@@ -0,0 +1,47 @@
+#! /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.helpmenu = Menu(self.menubar, name='help')
+
+ self.helpmenu.add_command(label="About...")
+
+ self.menubar.add_cascade(label="File", menu=self.filemenu)
+ self.menubar.add_cascade(label="Edit", menu=self.editmenu)
+ self.menubar.add_cascade(label="Help", menu=self.helpmenu)
+
+ 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/sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py b/sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py
new file mode 100644
index 000000000..be9d3ac2a
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py
@@ -0,0 +1,27 @@
+# option menu sample (Fredrik Lundh, September 1997)
+
+from Tkinter import *
+
+root = Tk()
+
+#
+# standard usage
+
+var1 = StringVar()
+var1.set("One") # default selection
+
+menu1 = OptionMenu(root, var1, "One", "Two", "Three")
+menu1.pack()
+
+#
+# initialize from a sequence
+
+CHOICES = "Aah", "Bee", "Cee", "Dee", "Eff"
+
+var2 = StringVar()
+var2.set(CHOICES[0])
+
+menu2 = apply(OptionMenu, (root, var2) + tuple(CHOICES))
+menu2.pack()
+
+root.mainloop()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/paint.py b/sys/src/cmd/python/Demo/tkinter/guido/paint.py
new file mode 100644
index 000000000..d46e20b9a
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/paint.py
@@ -0,0 +1,60 @@
+""""Paint program by Dave Michell.
+
+Subject: tkinter "paint" example
+From: Dave Mitchell <davem@magnet.com>
+To: python-list@cwi.nl
+Date: Fri, 23 Jan 1998 12:18:05 -0500 (EST)
+
+ Not too long ago (last week maybe?) someone posted a request
+for an example of a paint program using Tkinter. Try as I might
+I can't seem to find it in the archive, so i'll just post mine
+here and hope that the person who requested it sees this!
+
+ All this does is put up a canvas and draw a smooth black line
+whenever you have the mouse button down, but hopefully it will
+be enough to start with.. It would be easy enough to add some
+options like other shapes or colors...
+
+ yours,
+ dave mitchell
+ davem@magnet.com
+"""
+
+from Tkinter import *
+
+"""paint.py: not exactly a paint program.. just a smooth line drawing demo."""
+
+b1 = "up"
+xold, yold = None, None
+
+def main():
+ root = Tk()
+ drawing_area = Canvas(root)
+ drawing_area.pack()
+ drawing_area.bind("<Motion>", motion)
+ drawing_area.bind("<ButtonPress-1>", b1down)
+ drawing_area.bind("<ButtonRelease-1>", b1up)
+ root.mainloop()
+
+def b1down(event):
+ global b1
+ b1 = "down" # you only want to draw when the button is down
+ # because "Motion" events happen -all the time-
+
+def b1up(event):
+ global b1, xold, yold
+ b1 = "up"
+ xold = None # reset the line when you let go of the button
+ yold = None
+
+def motion(event):
+ if b1 == "down":
+ global xold, yold
+ if xold != None and yold != None:
+ event.widget.create_line(xold,yold,event.x,event.y,smooth=TRUE)
+ # here's where you draw it. smooth. neat.
+ xold = event.x
+ yold = event.y
+
+if __name__ == "__main__":
+ main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/rmt.py b/sys/src/cmd/python/Demo/tkinter/guido/rmt.py
new file mode 100755
index 000000000..440650c9c
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/rmt.py
@@ -0,0 +1,159 @@
+#! /usr/bin/env python
+
+# A Python program implementing rmt, an application for remotely
+# controlling other Tk applications.
+# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.5-8, pp. 273-276.
+
+# Note that because of forward references in the original, we
+# sometimes delay bindings until after the corresponding procedure is
+# defined. We also introduce names for some unnamed code blocks in
+# the original because of restrictions on lambda forms in Python.
+
+# XXX This should be written in a more Python-like style!!!
+
+from Tkinter import *
+import sys
+
+# 1. Create basic application structure: menu bar on top of
+# text widget, scrollbar on right.
+
+root = Tk()
+tk = root.tk
+mBar = Frame(root, relief=RAISED, borderwidth=2)
+mBar.pack(fill=X)
+
+f = Frame(root)
+f.pack(expand=1, fill=BOTH)
+s = Scrollbar(f, relief=FLAT)
+s.pack(side=RIGHT, fill=Y)
+t = Text(f, relief=RAISED, borderwidth=2, yscrollcommand=s.set, setgrid=1)
+t.pack(side=LEFT, fill=BOTH, expand=1)
+t.tag_config('bold', font='-Adobe-Courier-Bold-R-Normal-*-120-*')
+s['command'] = t.yview
+
+root.title('Tk Remote Controller')
+root.iconname('Tk Remote')
+
+# 2. Create menu button and menus.
+
+file = Menubutton(mBar, text='File', underline=0)
+file.pack(side=LEFT)
+file_m = Menu(file)
+file['menu'] = file_m
+file_m_apps = Menu(file_m, tearoff=0)
+file_m.add_cascade(label='Select Application', underline=0,
+ menu=file_m_apps)
+file_m.add_command(label='Quit', underline=0, command=sys.exit)
+
+# 3. Create bindings for text widget to allow commands to be
+# entered and information to be selected. New characters
+# can only be added at the end of the text (can't ever move
+# insertion point).
+
+def single1(e):
+ x = e.x
+ y = e.y
+ t.setvar('tk_priv(selectMode)', 'char')
+ t.mark_set('anchor', At(x, y))
+ # Should focus W
+t.bind('<1>', single1)
+
+def double1(e):
+ x = e.x
+ y = e.y
+ t.setvar('tk_priv(selectMode)', 'word')
+ t.tk_textSelectTo(At(x, y))
+t.bind('<Double-1>', double1)
+
+def triple1(e):
+ x = e.x
+ y = e.y
+ t.setvar('tk_priv(selectMode)', 'line')
+ t.tk_textSelectTo(At(x, y))
+t.bind('<Triple-1>', triple1)
+
+def returnkey(e):
+ t.insert(AtInsert(), '\n')
+ invoke()
+t.bind('<Return>', returnkey)
+
+def controlv(e):
+ t.insert(AtInsert(), t.selection_get())
+ t.yview_pickplace(AtInsert())
+ if t.index(AtInsert())[-2:] == '.0':
+ invoke()
+t.bind('<Control-v>', controlv)
+
+# 4. Procedure to backspace over one character, as long as
+# the character isn't part of the prompt.
+
+def backspace(e):
+ if t.index('promptEnd') != t.index('insert - 1 char'):
+ t.delete('insert - 1 char', AtInsert())
+ t.yview_pickplace(AtInsert())
+t.bind('<BackSpace>', backspace)
+t.bind('<Control-h>', backspace)
+t.bind('<Delete>', backspace)
+
+
+# 5. Procedure that's invoked when return is typed: if
+# there's not yet a complete command (e.g. braces are open)
+# then do nothing. Otherwise, execute command (locally or
+# remotely), output the result or error message, and issue
+# a new prompt.
+
+def invoke():
+ cmd = t.get('promptEnd + 1 char', AtInsert())
+ if t.getboolean(tk.call('info', 'complete', cmd)): # XXX
+ if app == root.winfo_name():
+ msg = tk.call('eval', cmd) # XXX
+ else:
+ msg = t.send(app, cmd)
+ if msg:
+ t.insert(AtInsert(), msg + '\n')
+ prompt()
+ t.yview_pickplace(AtInsert())
+
+def prompt():
+ t.insert(AtInsert(), app + ': ')
+ t.mark_set('promptEnd', 'insert - 1 char')
+ t.tag_add('bold', 'insert linestart', 'promptEnd')
+
+# 6. Procedure to select a new application. Also changes
+# the prompt on the current command line to reflect the new
+# name.
+
+def newApp(appName):
+ global app
+ app = appName
+ t.delete('promptEnd linestart', 'promptEnd')
+ t.insert('promptEnd', appName + ':')
+ t.tag_add('bold', 'promptEnd linestart', 'promptEnd')
+
+def fillAppsMenu():
+ file_m_apps.add('command')
+ file_m_apps.delete(0, 'last')
+ names = root.winfo_interps()
+ names = map(None, names) # convert tuple to list
+ names.sort()
+ for name in names:
+ try:
+ root.send(name, 'winfo name .')
+ except TclError:
+ # Inoperative window -- ignore it
+ pass
+ else:
+ file_m_apps.add_command(
+ label=name,
+ command=lambda name=name: newApp(name))
+
+file_m_apps['postcommand'] = fillAppsMenu
+mBar.tk_menuBar(file)
+
+# 7. Miscellaneous initialization.
+
+app = root.winfo_name()
+prompt()
+t.focus()
+
+root.mainloop()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/solitaire.py b/sys/src/cmd/python/Demo/tkinter/guido/solitaire.py
new file mode 100755
index 000000000..50a8b2643
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/solitaire.py
@@ -0,0 +1,637 @@
+#! /usr/bin/env python
+
+"""Solitaire game, much like the one that comes with MS Windows.
+
+Limitations:
+
+- No cute graphical images for the playing cards faces or backs.
+- No scoring or timer.
+- No undo.
+- No option to turn 3 cards at a time.
+- No keyboard shortcuts.
+- Less fancy animation when you win.
+- The determination of which stack you drag to is more relaxed.
+
+Apology:
+
+I'm not much of a card player, so my terminology in these comments may
+at times be a little unusual. If you have suggestions, please let me
+know!
+
+"""
+
+# Imports
+
+import math
+import random
+
+from Tkinter import *
+from Canvas import Rectangle, CanvasText, Group, Window
+
+
+# Fix a bug in Canvas.Group as distributed in Python 1.4. The
+# distributed bind() method is broken. Rather than asking you to fix
+# the source, we fix it here by deriving a subclass:
+
+class Group(Group):
+ def bind(self, sequence=None, command=None):
+ return self.canvas.tag_bind(self.id, sequence, command)
+
+
+# Constants determining the size and lay-out of cards and stacks. We
+# work in a "grid" where each card/stack is surrounded by MARGIN
+# pixels of space on each side, so adjacent stacks are separated by
+# 2*MARGIN pixels. OFFSET is the offset used for displaying the
+# face down cards in the row stacks.
+
+CARDWIDTH = 100
+CARDHEIGHT = 150
+MARGIN = 10
+XSPACING = CARDWIDTH + 2*MARGIN
+YSPACING = CARDHEIGHT + 4*MARGIN
+OFFSET = 5
+
+# The background color, green to look like a playing table. The
+# standard green is way too bright, and dark green is way to dark, so
+# we use something in between. (There are a few more colors that
+# could be customized, but they are less controversial.)
+
+BACKGROUND = '#070'
+
+
+# Suits and colors. The values of the symbolic suit names are the
+# strings used to display them (you change these and VALNAMES to
+# internationalize the game). The COLOR dictionary maps suit names to
+# colors (red and black) which must be Tk color names. The keys() of
+# the COLOR dictionary conveniently provides us with a list of all
+# suits (in arbitrary order).
+
+HEARTS = 'Heart'
+DIAMONDS = 'Diamond'
+CLUBS = 'Club'
+SPADES = 'Spade'
+
+RED = 'red'
+BLACK = 'black'
+
+COLOR = {}
+for s in (HEARTS, DIAMONDS):
+ COLOR[s] = RED
+for s in (CLUBS, SPADES):
+ COLOR[s] = BLACK
+
+ALLSUITS = COLOR.keys()
+NSUITS = len(ALLSUITS)
+
+
+# Card values are 1-13. We also define symbolic names for the picture
+# cards. ALLVALUES is a list of all card values.
+
+ACE = 1
+JACK = 11
+QUEEN = 12
+KING = 13
+ALLVALUES = range(1, 14) # (one more than the highest value)
+NVALUES = len(ALLVALUES)
+
+
+# VALNAMES is a list that maps a card value to string. It contains a
+# dummy element at index 0 so it can be indexed directly with the card
+# value.
+
+VALNAMES = ["", "A"] + map(str, range(2, 11)) + ["J", "Q", "K"]
+
+
+# Solitaire constants. The only one I can think of is the number of
+# row stacks.
+
+NROWS = 7
+
+
+# The rest of the program consists of class definitions. These are
+# further described in their documentation strings.
+
+
+class Card:
+
+ """A playing card.
+
+ A card doesn't record to which stack it belongs; only the stack
+ records this (it turns out that we always know this from the
+ context, and this saves a ``double update'' with potential for
+ inconsistencies).
+
+ Public methods:
+
+ moveto(x, y) -- move the card to an absolute position
+ moveby(dx, dy) -- move the card by a relative offset
+ tkraise() -- raise the card to the top of its stack
+ showface(), showback() -- turn the card face up or down & raise it
+
+ Public read-only instance variables:
+
+ suit, value, color -- the card's suit, value and color
+ face_shown -- true when the card is shown face up, else false
+
+ Semi-public read-only instance variables (XXX should be made
+ private):
+
+ group -- the Canvas.Group representing the card
+ x, y -- the position of the card's top left corner
+
+ Private instance variables:
+
+ __back, __rect, __text -- the canvas items making up the card
+
+ (To show the card face up, the text item is placed in front of
+ rect and the back is placed behind it. To show it face down, this
+ is reversed. The card is created face down.)
+
+ """
+
+ def __init__(self, suit, value, canvas):
+ """Card constructor.
+
+ Arguments are the card's suit and value, and the canvas widget.
+
+ The card is created at position (0, 0), with its face down
+ (adding it to a stack will position it according to that
+ stack's rules).
+
+ """
+ self.suit = suit
+ self.value = value
+ self.color = COLOR[suit]
+ self.face_shown = 0
+
+ self.x = self.y = 0
+ self.group = Group(canvas)
+
+ text = "%s %s" % (VALNAMES[value], suit)
+ self.__text = CanvasText(canvas, CARDWIDTH/2, 0,
+ anchor=N, fill=self.color, text=text)
+ self.group.addtag_withtag(self.__text)
+
+ self.__rect = Rectangle(canvas, 0, 0, CARDWIDTH, CARDHEIGHT,
+ outline='black', fill='white')
+ self.group.addtag_withtag(self.__rect)
+
+ self.__back = Rectangle(canvas, MARGIN, MARGIN,
+ CARDWIDTH-MARGIN, CARDHEIGHT-MARGIN,
+ outline='black', fill='blue')
+ self.group.addtag_withtag(self.__back)
+
+ def __repr__(self):
+ """Return a string for debug print statements."""
+ return "Card(%r, %r)" % (self.suit, self.value)
+
+ def moveto(self, x, y):
+ """Move the card to absolute position (x, y)."""
+ self.moveby(x - self.x, y - self.y)
+
+ def moveby(self, dx, dy):
+ """Move the card by (dx, dy)."""
+ self.x = self.x + dx
+ self.y = self.y + dy
+ self.group.move(dx, dy)
+
+ def tkraise(self):
+ """Raise the card above all other objects in its canvas."""
+ self.group.tkraise()
+
+ def showface(self):
+ """Turn the card's face up."""
+ self.tkraise()
+ self.__rect.tkraise()
+ self.__text.tkraise()
+ self.face_shown = 1
+
+ def showback(self):
+ """Turn the card's face down."""
+ self.tkraise()
+ self.__rect.tkraise()
+ self.__back.tkraise()
+ self.face_shown = 0
+
+
+class Stack:
+
+ """A generic stack of cards.
+
+ This is used as a base class for all other stacks (e.g. the deck,
+ the suit stacks, and the row stacks).
+
+ Public methods:
+
+ add(card) -- add a card to the stack
+ delete(card) -- delete a card from the stack
+ showtop() -- show the top card (if any) face up
+ deal() -- delete and return the top card, or None if empty
+
+ Method that subclasses may override:
+
+ position(card) -- move the card to its proper (x, y) position
+
+ The default position() method places all cards at the stack's
+ own (x, y) position.
+
+ userclickhandler(), userdoubleclickhandler() -- called to do
+ subclass specific things on single and double clicks
+
+ The default user (single) click handler shows the top card
+ face up. The default user double click handler calls the user
+ single click handler.
+
+ usermovehandler(cards) -- called to complete a subpile move
+
+ The default user move handler moves all moved cards back to
+ their original position (by calling the position() method).
+
+ Private methods:
+
+ clickhandler(event), doubleclickhandler(event),
+ motionhandler(event), releasehandler(event) -- event handlers
+
+ The default event handlers turn the top card of the stack with
+ its face up on a (single or double) click, and also support
+ moving a subpile around.
+
+ startmoving(event) -- begin a move operation
+ finishmoving() -- finish a move operation
+
+ """
+
+ def __init__(self, x, y, game=None):
+ """Stack constructor.
+
+ Arguments are the stack's nominal x and y position (the top
+ left corner of the first card placed in the stack), and the
+ game object (which is used to get the canvas; subclasses use
+ the game object to find other stacks).
+
+ """
+ self.x = x
+ self.y = y
+ self.game = game
+ self.cards = []
+ self.group = Group(self.game.canvas)
+ self.group.bind('<1>', self.clickhandler)
+ self.group.bind('<Double-1>', self.doubleclickhandler)
+ self.group.bind('<B1-Motion>', self.motionhandler)
+ self.group.bind('<ButtonRelease-1>', self.releasehandler)
+ self.makebottom()
+
+ def makebottom(self):
+ pass
+
+ def __repr__(self):
+ """Return a string for debug print statements."""
+ return "%s(%d, %d)" % (self.__class__.__name__, self.x, self.y)
+
+ # Public methods
+
+ def add(self, card):
+ self.cards.append(card)
+ card.tkraise()
+ self.position(card)
+ self.group.addtag_withtag(card.group)
+
+ def delete(self, card):
+ self.cards.remove(card)
+ card.group.dtag(self.group)
+
+ def showtop(self):
+ if self.cards:
+ self.cards[-1].showface()
+
+ def deal(self):
+ if not self.cards:
+ return None
+ card = self.cards[-1]
+ self.delete(card)
+ return card
+
+ # Subclass overridable methods
+
+ def position(self, card):
+ card.moveto(self.x, self.y)
+
+ def userclickhandler(self):
+ self.showtop()
+
+ def userdoubleclickhandler(self):
+ self.userclickhandler()
+
+ def usermovehandler(self, cards):
+ for card in cards:
+ self.position(card)
+
+ # Event handlers
+
+ def clickhandler(self, event):
+ self.finishmoving() # In case we lost an event
+ self.userclickhandler()
+ self.startmoving(event)
+
+ def motionhandler(self, event):
+ self.keepmoving(event)
+
+ def releasehandler(self, event):
+ self.keepmoving(event)
+ self.finishmoving()
+
+ def doubleclickhandler(self, event):
+ self.finishmoving() # In case we lost an event
+ self.userdoubleclickhandler()
+ self.startmoving(event)
+
+ # Move internals
+
+ moving = None
+
+ def startmoving(self, event):
+ self.moving = None
+ tags = self.game.canvas.gettags('current')
+ for i in range(len(self.cards)):
+ card = self.cards[i]
+ if card.group.tag in tags:
+ break
+ else:
+ return
+ if not card.face_shown:
+ return
+ self.moving = self.cards[i:]
+ self.lastx = event.x
+ self.lasty = event.y
+ for card in self.moving:
+ card.tkraise()
+
+ def keepmoving(self, event):
+ if not self.moving:
+ return
+ dx = event.x - self.lastx
+ dy = event.y - self.lasty
+ self.lastx = event.x
+ self.lasty = event.y
+ if dx or dy:
+ for card in self.moving:
+ card.moveby(dx, dy)
+
+ def finishmoving(self):
+ cards = self.moving
+ self.moving = None
+ if cards:
+ self.usermovehandler(cards)
+
+
+class Deck(Stack):
+
+ """The deck is a stack with support for shuffling.
+
+ New methods:
+
+ fill() -- create the playing cards
+ shuffle() -- shuffle the playing cards
+
+ A single click moves the top card to the game's open deck and
+ moves it face up; if we're out of cards, it moves the open deck
+ back to the deck.
+
+ """
+
+ def makebottom(self):
+ bottom = Rectangle(self.game.canvas,
+ self.x, self.y,
+ self.x+CARDWIDTH, self.y+CARDHEIGHT,
+ outline='black', fill=BACKGROUND)
+ self.group.addtag_withtag(bottom)
+
+ def fill(self):
+ for suit in ALLSUITS:
+ for value in ALLVALUES:
+ self.add(Card(suit, value, self.game.canvas))
+
+ def shuffle(self):
+ n = len(self.cards)
+ newcards = []
+ for i in randperm(n):
+ newcards.append(self.cards[i])
+ self.cards = newcards
+
+ def userclickhandler(self):
+ opendeck = self.game.opendeck
+ card = self.deal()
+ if not card:
+ while 1:
+ card = opendeck.deal()
+ if not card:
+ break
+ self.add(card)
+ card.showback()
+ else:
+ self.game.opendeck.add(card)
+ card.showface()
+
+
+def randperm(n):
+ """Function returning a random permutation of range(n)."""
+ r = range(n)
+ x = []
+ while r:
+ i = random.choice(r)
+ x.append(i)
+ r.remove(i)
+ return x
+
+
+class OpenStack(Stack):
+
+ def acceptable(self, cards):
+ return 0
+
+ def usermovehandler(self, cards):
+ card = cards[0]
+ stack = self.game.closeststack(card)
+ if not stack or stack is self or not stack.acceptable(cards):
+ Stack.usermovehandler(self, cards)
+ else:
+ for card in cards:
+ self.delete(card)
+ stack.add(card)
+ self.game.wincheck()
+
+ def userdoubleclickhandler(self):
+ if not self.cards:
+ return
+ card = self.cards[-1]
+ if not card.face_shown:
+ self.userclickhandler()
+ return
+ for s in self.game.suits:
+ if s.acceptable([card]):
+ self.delete(card)
+ s.add(card)
+ self.game.wincheck()
+ break
+
+
+class SuitStack(OpenStack):
+
+ def makebottom(self):
+ bottom = Rectangle(self.game.canvas,
+ self.x, self.y,
+ self.x+CARDWIDTH, self.y+CARDHEIGHT,
+ outline='black', fill='')
+
+ def userclickhandler(self):
+ pass
+
+ def userdoubleclickhandler(self):
+ pass
+
+ def acceptable(self, cards):
+ if len(cards) != 1:
+ return 0
+ card = cards[0]
+ if not self.cards:
+ return card.value == ACE
+ topcard = self.cards[-1]
+ return card.suit == topcard.suit and card.value == topcard.value + 1
+
+
+class RowStack(OpenStack):
+
+ def acceptable(self, cards):
+ card = cards[0]
+ if not self.cards:
+ return card.value == KING
+ topcard = self.cards[-1]
+ if not topcard.face_shown:
+ return 0
+ return card.color != topcard.color and card.value == topcard.value - 1
+
+ def position(self, card):
+ y = self.y
+ for c in self.cards:
+ if c == card:
+ break
+ if c.face_shown:
+ y = y + 2*MARGIN
+ else:
+ y = y + OFFSET
+ card.moveto(self.x, y)
+
+
+class Solitaire:
+
+ def __init__(self, master):
+ self.master = master
+
+ self.canvas = Canvas(self.master,
+ background=BACKGROUND,
+ highlightthickness=0,
+ width=NROWS*XSPACING,
+ height=3*YSPACING + 20 + MARGIN)
+ self.canvas.pack(fill=BOTH, expand=TRUE)
+
+ self.dealbutton = Button(self.canvas,
+ text="Deal",
+ highlightthickness=0,
+ background=BACKGROUND,
+ activebackground="green",
+ command=self.deal)
+ Window(self.canvas, MARGIN, 3*YSPACING + 20,
+ window=self.dealbutton, anchor=SW)
+
+ x = MARGIN
+ y = MARGIN
+
+ self.deck = Deck(x, y, self)
+
+ x = x + XSPACING
+ self.opendeck = OpenStack(x, y, self)
+
+ x = x + XSPACING
+ self.suits = []
+ for i in range(NSUITS):
+ x = x + XSPACING
+ self.suits.append(SuitStack(x, y, self))
+
+ x = MARGIN
+ y = y + YSPACING
+
+ self.rows = []
+ for i in range(NROWS):
+ self.rows.append(RowStack(x, y, self))
+ x = x + XSPACING
+
+ self.openstacks = [self.opendeck] + self.suits + self.rows
+
+ self.deck.fill()
+ self.deal()
+
+ def wincheck(self):
+ for s in self.suits:
+ if len(s.cards) != NVALUES:
+ return
+ self.win()
+ self.deal()
+
+ def win(self):
+ """Stupid animation when you win."""
+ cards = []
+ for s in self.openstacks:
+ cards = cards + s.cards
+ while cards:
+ card = random.choice(cards)
+ cards.remove(card)
+ self.animatedmoveto(card, self.deck)
+
+ def animatedmoveto(self, card, dest):
+ for i in range(10, 0, -1):
+ dx, dy = (dest.x-card.x)/i, (dest.y-card.y)/i
+ card.moveby(dx, dy)
+ self.master.update_idletasks()
+
+ def closeststack(self, card):
+ closest = None
+ cdist = 999999999
+ # Since we only compare distances,
+ # we don't bother to take the square root.
+ for stack in self.openstacks:
+ dist = (stack.x - card.x)**2 + (stack.y - card.y)**2
+ if dist < cdist:
+ closest = stack
+ cdist = dist
+ return closest
+
+ def deal(self):
+ self.reset()
+ self.deck.shuffle()
+ for i in range(NROWS):
+ for r in self.rows[i:]:
+ card = self.deck.deal()
+ r.add(card)
+ for r in self.rows:
+ r.showtop()
+
+ def reset(self):
+ for stack in self.openstacks:
+ while 1:
+ card = stack.deal()
+ if not card:
+ break
+ self.deck.add(card)
+ card.showback()
+
+
+# Main function, run when invoked as a stand-alone Python program.
+
+def main():
+ root = Tk()
+ game = Solitaire(root)
+ root.protocol('WM_DELETE_WINDOW', root.quit)
+ root.mainloop()
+
+if __name__ == '__main__':
+ main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/sortvisu.py b/sys/src/cmd/python/Demo/tkinter/guido/sortvisu.py
new file mode 100644
index 000000000..f18f2c116
--- /dev/null
+++ b/sys/src/cmd/python/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()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/ss1.py b/sys/src/cmd/python/Demo/tkinter/guido/ss1.py
new file mode 100644
index 000000000..89354753e
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/ss1.py
@@ -0,0 +1,845 @@
+"""SS1 -- a spreadsheet."""
+
+import os
+import re
+import sys
+import cgi
+import rexec
+from xml.parsers import expat
+
+LEFT, CENTER, RIGHT = "LEFT", "CENTER", "RIGHT"
+
+def ljust(x, n):
+ return x.ljust(n)
+def center(x, n):
+ return x.center(n)
+def rjust(x, n):
+ return x.rjust(n)
+align2action = {LEFT: ljust, CENTER: center, RIGHT: rjust}
+
+align2xml = {LEFT: "left", CENTER: "center", RIGHT: "right"}
+xml2align = {"left": LEFT, "center": CENTER, "right": RIGHT}
+
+align2anchor = {LEFT: "w", CENTER: "center", RIGHT: "e"}
+
+def sum(seq):
+ total = 0
+ for x in seq:
+ if x is not None:
+ total += x
+ return total
+
+class Sheet:
+
+ def __init__(self):
+ self.cells = {} # {(x, y): cell, ...}
+ self.rexec = rexec.RExec()
+ m = self.rexec.add_module('__main__')
+ m.cell = self.cellvalue
+ m.cells = self.multicellvalue
+ m.sum = sum
+
+ def cellvalue(self, x, y):
+ cell = self.getcell(x, y)
+ if hasattr(cell, 'recalc'):
+ return cell.recalc(self.rexec)
+ else:
+ return cell
+
+ def multicellvalue(self, x1, y1, x2, y2):
+ if x1 > x2:
+ x1, x2 = x2, x1
+ if y1 > y2:
+ y1, y2 = y2, y1
+ seq = []
+ for y in range(y1, y2+1):
+ for x in range(x1, x2+1):
+ seq.append(self.cellvalue(x, y))
+ return seq
+
+ def getcell(self, x, y):
+ return self.cells.get((x, y))
+
+ def setcell(self, x, y, cell):
+ assert x > 0 and y > 0
+ assert isinstance(cell, BaseCell)
+ self.cells[x, y] = cell
+
+ def clearcell(self, x, y):
+ try:
+ del self.cells[x, y]
+ except KeyError:
+ pass
+
+ def clearcells(self, x1, y1, x2, y2):
+ for xy in self.selectcells(x1, y1, x2, y2):
+ del self.cells[xy]
+
+ def clearrows(self, y1, y2):
+ self.clearcells(0, y1, sys.maxint, y2)
+
+ def clearcolumns(self, x1, x2):
+ self.clearcells(x1, 0, x2, sys.maxint)
+
+ def selectcells(self, x1, y1, x2, y2):
+ if x1 > x2:
+ x1, x2 = x2, x1
+ if y1 > y2:
+ y1, y2 = y2, y1
+ return [(x, y) for x, y in self.cells
+ if x1 <= x <= x2 and y1 <= y <= y2]
+
+ def movecells(self, x1, y1, x2, y2, dx, dy):
+ if dx == 0 and dy == 0:
+ return
+ if x1 > x2:
+ x1, x2 = x2, x1
+ if y1 > y2:
+ y1, y2 = y2, y1
+ assert x1+dx > 0 and y1+dy > 0
+ new = {}
+ for x, y in self.cells:
+ cell = self.cells[x, y]
+ if hasattr(cell, 'renumber'):
+ cell = cell.renumber(x1, y1, x2, y2, dx, dy)
+ if x1 <= x <= x2 and y1 <= y <= y2:
+ x += dx
+ y += dy
+ new[x, y] = cell
+ self.cells = new
+
+ def insertrows(self, y, n):
+ assert n > 0
+ self.movecells(0, y, sys.maxint, sys.maxint, 0, n)
+
+ def deleterows(self, y1, y2):
+ if y1 > y2:
+ y1, y2 = y2, y1
+ self.clearrows(y1, y2)
+ self.movecells(0, y2+1, sys.maxint, sys.maxint, 0, y1-y2-1)
+
+ def insertcolumns(self, x, n):
+ assert n > 0
+ self.movecells(x, 0, sys.maxint, sys.maxint, n, 0)
+
+ def deletecolumns(self, x1, x2):
+ if x1 > x2:
+ x1, x2 = x2, x1
+ self.clearcells(x1, x2)
+ self.movecells(x2+1, 0, sys.maxint, sys.maxint, x1-x2-1, 0)
+
+ def getsize(self):
+ maxx = maxy = 0
+ for x, y in self.cells:
+ maxx = max(maxx, x)
+ maxy = max(maxy, y)
+ return maxx, maxy
+
+ def reset(self):
+ for cell in self.cells.itervalues():
+ if hasattr(cell, 'reset'):
+ cell.reset()
+
+ def recalc(self):
+ self.reset()
+ for cell in self.cells.itervalues():
+ if hasattr(cell, 'recalc'):
+ cell.recalc(self.rexec)
+
+ def display(self):
+ maxx, maxy = self.getsize()
+ width, height = maxx+1, maxy+1
+ colwidth = [1] * width
+ full = {}
+ # Add column heading labels in row 0
+ for x in range(1, width):
+ full[x, 0] = text, alignment = colnum2name(x), RIGHT
+ colwidth[x] = max(colwidth[x], len(text))
+ # Add row labels in column 0
+ for y in range(1, height):
+ full[0, y] = text, alignment = str(y), RIGHT
+ colwidth[0] = max(colwidth[0], len(text))
+ # Add sheet cells in columns with x>0 and y>0
+ for (x, y), cell in self.cells.iteritems():
+ if x <= 0 or y <= 0:
+ continue
+ if hasattr(cell, 'recalc'):
+ cell.recalc(self.rexec)
+ if hasattr(cell, 'format'):
+ text, alignment = cell.format()
+ assert isinstance(text, str)
+ assert alignment in (LEFT, CENTER, RIGHT)
+ else:
+ text = str(cell)
+ if isinstance(cell, str):
+ alignment = LEFT
+ else:
+ alignment = RIGHT
+ full[x, y] = (text, alignment)
+ colwidth[x] = max(colwidth[x], len(text))
+ # Calculate the horizontal separator line (dashes and dots)
+ sep = ""
+ for x in range(width):
+ if sep:
+ sep += "+"
+ sep += "-"*colwidth[x]
+ # Now print The full grid
+ for y in range(height):
+ line = ""
+ for x in range(width):
+ text, alignment = full.get((x, y)) or ("", LEFT)
+ text = align2action[alignment](text, colwidth[x])
+ if line:
+ line += '|'
+ line += text
+ print line
+ if y == 0:
+ print sep
+
+ def xml(self):
+ out = ['<spreadsheet>']
+ for (x, y), cell in self.cells.iteritems():
+ if hasattr(cell, 'xml'):
+ cellxml = cell.xml()
+ else:
+ cellxml = '<value>%s</value>' % cgi.escape(cell)
+ out.append('<cell row="%s" col="%s">\n %s\n</cell>' %
+ (y, x, cellxml))
+ out.append('</spreadsheet>')
+ return '\n'.join(out)
+
+ def save(self, filename):
+ text = self.xml()
+ f = open(filename, "w")
+ f.write(text)
+ if text and not text.endswith('\n'):
+ f.write('\n')
+ f.close()
+
+ def load(self, filename):
+ f = open(filename, 'r')
+ SheetParser(self).parsefile(f)
+ f.close()
+
+class SheetParser:
+
+ def __init__(self, sheet):
+ self.sheet = sheet
+
+ def parsefile(self, f):
+ parser = expat.ParserCreate()
+ parser.StartElementHandler = self.startelement
+ parser.EndElementHandler = self.endelement
+ parser.CharacterDataHandler = self.data
+ parser.ParseFile(f)
+
+ def startelement(self, tag, attrs):
+ method = getattr(self, 'start_'+tag, None)
+ if method:
+ for key, value in attrs.iteritems():
+ attrs[key] = str(value) # XXX Convert Unicode to 8-bit
+ method(attrs)
+ self.texts = []
+
+ def data(self, text):
+ text = str(text) # XXX Convert Unicode to 8-bit
+ self.texts.append(text)
+
+ def endelement(self, tag):
+ method = getattr(self, 'end_'+tag, None)
+ if method:
+ method("".join(self.texts))
+
+ def start_cell(self, attrs):
+ self.y = int(attrs.get("row"))
+ self.x = int(attrs.get("col"))
+
+ def start_value(self, attrs):
+ self.fmt = attrs.get('format')
+ self.alignment = xml2align.get(attrs.get('align'))
+
+ start_formula = start_value
+
+ def end_int(self, text):
+ try:
+ self.value = int(text)
+ except:
+ self.value = None
+
+ def end_long(self, text):
+ try:
+ self.value = long(text)
+ except:
+ self.value = None
+
+ def end_double(self, text):
+ try:
+ self.value = float(text)
+ except:
+ self.value = None
+
+ def end_complex(self, text):
+ try:
+ self.value = complex(text)
+ except:
+ self.value = None
+
+ def end_string(self, text):
+ try:
+ self.value = text
+ except:
+ self.value = None
+
+ def end_value(self, text):
+ if isinstance(self.value, BaseCell):
+ self.cell = self.value
+ elif isinstance(self.value, str):
+ self.cell = StringCell(self.value,
+ self.fmt or "%s",
+ self.alignment or LEFT)
+ else:
+ self.cell = NumericCell(self.value,
+ self.fmt or "%s",
+ self.alignment or RIGHT)
+
+ def end_formula(self, text):
+ self.cell = FormulaCell(text,
+ self.fmt or "%s",
+ self.alignment or RIGHT)
+
+ def end_cell(self, text):
+ self.sheet.setcell(self.x, self.y, self.cell)
+
+class BaseCell:
+ __init__ = None # Must provide
+ """Abstract base class for sheet cells.
+
+ Subclasses may but needn't provide the following APIs:
+
+ cell.reset() -- prepare for recalculation
+ cell.recalc(rexec) -> value -- recalculate formula
+ cell.format() -> (value, alignment) -- return formatted value
+ cell.xml() -> string -- return XML
+ """
+
+class NumericCell(BaseCell):
+
+ def __init__(self, value, fmt="%s", alignment=RIGHT):
+ assert isinstance(value, (int, long, float, complex))
+ assert alignment in (LEFT, CENTER, RIGHT)
+ self.value = value
+ self.fmt = fmt
+ self.alignment = alignment
+
+ def recalc(self, rexec):
+ return self.value
+
+ def format(self):
+ try:
+ text = self.fmt % self.value
+ except:
+ text = str(self.value)
+ return text, self.alignment
+
+ def xml(self):
+ method = getattr(self, '_xml_' + type(self.value).__name__)
+ return '<value align="%s" format="%s">%s</value>' % (
+ align2xml[self.alignment],
+ self.fmt,
+ method())
+
+ def _xml_int(self):
+ if -2**31 <= self.value < 2**31:
+ return '<int>%s</int>' % self.value
+ else:
+ return self._xml_long()
+
+ def _xml_long(self):
+ return '<long>%s</long>' % self.value
+
+ def _xml_float(self):
+ return '<double>%s</double>' % repr(self.value)
+
+ def _xml_complex(self):
+ return '<complex>%s</double>' % repr(self.value)
+
+class StringCell(BaseCell):
+
+ def __init__(self, text, fmt="%s", alignment=LEFT):
+ assert isinstance(text, (str, unicode))
+ assert alignment in (LEFT, CENTER, RIGHT)
+ self.text = text
+ self.fmt = fmt
+ self.alignment = alignment
+
+ def recalc(self, rexec):
+ return self.text
+
+ def format(self):
+ return self.text, self.alignment
+
+ def xml(self):
+ s = '<value align="%s" format="%s"><string>%s</string></value>'
+ return s % (
+ align2xml[self.alignment],
+ self.fmt,
+ cgi.escape(self.text))
+
+class FormulaCell(BaseCell):
+
+ def __init__(self, formula, fmt="%s", alignment=RIGHT):
+ assert alignment in (LEFT, CENTER, RIGHT)
+ self.formula = formula
+ self.translated = translate(self.formula)
+ self.fmt = fmt
+ self.alignment = alignment
+ self.reset()
+
+ def reset(self):
+ self.value = None
+
+ def recalc(self, rexec):
+ if self.value is None:
+ try:
+ # A hack to evaluate expressions using true division
+ rexec.r_exec("from __future__ import division\n" +
+ "__value__ = eval(%s)" % repr(self.translated))
+ self.value = rexec.r_eval("__value__")
+ except:
+ exc = sys.exc_info()[0]
+ if hasattr(exc, "__name__"):
+ self.value = exc.__name__
+ else:
+ self.value = str(exc)
+ return self.value
+
+ def format(self):
+ try:
+ text = self.fmt % self.value
+ except:
+ text = str(self.value)
+ return text, self.alignment
+
+ def xml(self):
+ return '<formula align="%s" format="%s">%s</formula>' % (
+ align2xml[self.alignment],
+ self.fmt,
+ self.formula)
+
+ def renumber(self, x1, y1, x2, y2, dx, dy):
+ out = []
+ for part in re.split('(\w+)', self.formula):
+ m = re.match('^([A-Z]+)([1-9][0-9]*)$', part)
+ if m is not None:
+ sx, sy = m.groups()
+ x = colname2num(sx)
+ y = int(sy)
+ if x1 <= x <= x2 and y1 <= y <= y2:
+ part = cellname(x+dx, y+dy)
+ out.append(part)
+ return FormulaCell("".join(out), self.fmt, self.alignment)
+
+def translate(formula):
+ """Translate a formula containing fancy cell names to valid Python code.
+
+ Examples:
+ B4 -> cell(2, 4)
+ B4:Z100 -> cells(2, 4, 26, 100)
+ """
+ out = []
+ for part in re.split(r"(\w+(?::\w+)?)", formula):
+ m = re.match(r"^([A-Z]+)([1-9][0-9]*)(?::([A-Z]+)([1-9][0-9]*))?$", part)
+ if m is None:
+ out.append(part)
+ else:
+ x1, y1, x2, y2 = m.groups()
+ x1 = colname2num(x1)
+ if x2 is None:
+ s = "cell(%s, %s)" % (x1, y1)
+ else:
+ x2 = colname2num(x2)
+ s = "cells(%s, %s, %s, %s)" % (x1, y1, x2, y2)
+ out.append(s)
+ return "".join(out)
+
+def cellname(x, y):
+ "Translate a cell coordinate to a fancy cell name (e.g. (1, 1)->'A1')."
+ assert x > 0 # Column 0 has an empty name, so can't use that
+ return colnum2name(x) + str(y)
+
+def colname2num(s):
+ "Translate a column name to number (e.g. 'A'->1, 'Z'->26, 'AA'->27)."
+ s = s.upper()
+ n = 0
+ for c in s:
+ assert 'A' <= c <= 'Z'
+ n = n*26 + ord(c) - ord('A') + 1
+ return n
+
+def colnum2name(n):
+ "Translate a column number to name (e.g. 1->'A', etc.)."
+ assert n > 0
+ s = ""
+ while n:
+ n, m = divmod(n-1, 26)
+ s = chr(m+ord('A')) + s
+ return s
+
+import Tkinter as Tk
+
+class SheetGUI:
+
+ """Beginnings of a GUI for a spreadsheet.
+
+ TO DO:
+ - clear multiple cells
+ - Insert, clear, remove rows or columns
+ - Show new contents while typing
+ - Scroll bars
+ - Grow grid when window is grown
+ - Proper menus
+ - Undo, redo
+ - Cut, copy and paste
+ - Formatting and alignment
+ """
+
+ def __init__(self, filename="sheet1.xml", rows=10, columns=5):
+ """Constructor.
+
+ Load the sheet from the filename argument.
+ Set up the Tk widget tree.
+ """
+ # Create and load the sheet
+ self.filename = filename
+ self.sheet = Sheet()
+ if os.path.isfile(filename):
+ self.sheet.load(filename)
+ # Calculate the needed grid size
+ maxx, maxy = self.sheet.getsize()
+ rows = max(rows, maxy)
+ columns = max(columns, maxx)
+ # Create the widgets
+ self.root = Tk.Tk()
+ self.root.wm_title("Spreadsheet: %s" % self.filename)
+ self.beacon = Tk.Label(self.root, text="A1",
+ font=('helvetica', 16, 'bold'))
+ self.entry = Tk.Entry(self.root)
+ self.savebutton = Tk.Button(self.root, text="Save",
+ command=self.save)
+ self.cellgrid = Tk.Frame(self.root)
+ # Configure the widget lay-out
+ self.cellgrid.pack(side="bottom", expand=1, fill="both")
+ self.beacon.pack(side="left")
+ self.savebutton.pack(side="right")
+ self.entry.pack(side="left", expand=1, fill="x")
+ # Bind some events
+ self.entry.bind("<Return>", self.return_event)
+ self.entry.bind("<Shift-Return>", self.shift_return_event)
+ self.entry.bind("<Tab>", self.tab_event)
+ self.entry.bind("<Shift-Tab>", self.shift_tab_event)
+ self.entry.bind("<Delete>", self.delete_event)
+ self.entry.bind("<Escape>", self.escape_event)
+ # Now create the cell grid
+ self.makegrid(rows, columns)
+ # Select the top-left cell
+ self.currentxy = None
+ self.cornerxy = None
+ self.setcurrent(1, 1)
+ # Copy the sheet cells to the GUI cells
+ self.sync()
+
+ def delete_event(self, event):
+ if self.cornerxy != self.currentxy and self.cornerxy is not None:
+ self.sheet.clearcells(*(self.currentxy + self.cornerxy))
+ else:
+ self.sheet.clearcell(*self.currentxy)
+ self.sync()
+ self.entry.delete(0, 'end')
+ return "break"
+
+ def escape_event(self, event):
+ x, y = self.currentxy
+ self.load_entry(x, y)
+
+ def load_entry(self, x, y):
+ cell = self.sheet.getcell(x, y)
+ if cell is None:
+ text = ""
+ elif isinstance(cell, FormulaCell):
+ text = '=' + cell.formula
+ else:
+ text, alignment = cell.format()
+ self.entry.delete(0, 'end')
+ self.entry.insert(0, text)
+ self.entry.selection_range(0, 'end')
+
+ def makegrid(self, rows, columns):
+ """Helper to create the grid of GUI cells.
+
+ The edge (x==0 or y==0) is filled with labels; the rest is real cells.
+ """
+ self.rows = rows
+ self.columns = columns
+ self.gridcells = {}
+ # Create the top left corner cell (which selects all)
+ cell = Tk.Label(self.cellgrid, relief='raised')
+ cell.grid_configure(column=0, row=0, sticky='NSWE')
+ cell.bind("<ButtonPress-1>", self.selectall)
+ # Create the top row of labels, and confiure the grid columns
+ for x in range(1, columns+1):
+ self.cellgrid.grid_columnconfigure(x, minsize=64)
+ cell = Tk.Label(self.cellgrid, text=colnum2name(x), relief='raised')
+ cell.grid_configure(column=x, row=0, sticky='WE')
+ self.gridcells[x, 0] = cell
+ cell.__x = x
+ cell.__y = 0
+ cell.bind("<ButtonPress-1>", self.selectcolumn)
+ cell.bind("<B1-Motion>", self.extendcolumn)
+ cell.bind("<ButtonRelease-1>", self.extendcolumn)
+ cell.bind("<Shift-Button-1>", self.extendcolumn)
+ # Create the leftmost column of labels
+ for y in range(1, rows+1):
+ cell = Tk.Label(self.cellgrid, text=str(y), relief='raised')
+ cell.grid_configure(column=0, row=y, sticky='WE')
+ self.gridcells[0, y] = cell
+ cell.__x = 0
+ cell.__y = y
+ cell.bind("<ButtonPress-1>", self.selectrow)
+ cell.bind("<B1-Motion>", self.extendrow)
+ cell.bind("<ButtonRelease-1>", self.extendrow)
+ cell.bind("<Shift-Button-1>", self.extendrow)
+ # Create the real cells
+ for x in range(1, columns+1):
+ for y in range(1, rows+1):
+ cell = Tk.Label(self.cellgrid, relief='sunken',
+ bg='white', fg='black')
+ cell.grid_configure(column=x, row=y, sticky='NSWE')
+ self.gridcells[x, y] = cell
+ cell.__x = x
+ cell.__y = y
+ # Bind mouse events
+ cell.bind("<ButtonPress-1>", self.press)
+ cell.bind("<B1-Motion>", self.motion)
+ cell.bind("<ButtonRelease-1>", self.release)
+ cell.bind("<Shift-Button-1>", self.release)
+
+ def selectall(self, event):
+ self.setcurrent(1, 1)
+ self.setcorner(sys.maxint, sys.maxint)
+
+ def selectcolumn(self, event):
+ x, y = self.whichxy(event)
+ self.setcurrent(x, 1)
+ self.setcorner(x, sys.maxint)
+
+ def extendcolumn(self, event):
+ x, y = self.whichxy(event)
+ if x > 0:
+ self.setcurrent(self.currentxy[0], 1)
+ self.setcorner(x, sys.maxint)
+
+ def selectrow(self, event):
+ x, y = self.whichxy(event)
+ self.setcurrent(1, y)
+ self.setcorner(sys.maxint, y)
+
+ def extendrow(self, event):
+ x, y = self.whichxy(event)
+ if y > 0:
+ self.setcurrent(1, self.currentxy[1])
+ self.setcorner(sys.maxint, y)
+
+ def press(self, event):
+ x, y = self.whichxy(event)
+ if x > 0 and y > 0:
+ self.setcurrent(x, y)
+
+ def motion(self, event):
+ x, y = self.whichxy(event)
+ if x > 0 and y > 0:
+ self.setcorner(x, y)
+
+ release = motion
+
+ def whichxy(self, event):
+ w = self.cellgrid.winfo_containing(event.x_root, event.y_root)
+ if w is not None and isinstance(w, Tk.Label):
+ try:
+ return w.__x, w.__y
+ except AttributeError:
+ pass
+ return 0, 0
+
+ def save(self):
+ self.sheet.save(self.filename)
+
+ def setcurrent(self, x, y):
+ "Make (x, y) the current cell."
+ if self.currentxy is not None:
+ self.change_cell()
+ self.clearfocus()
+ self.beacon['text'] = cellname(x, y)
+ self.load_entry(x, y)
+ self.entry.focus_set()
+ self.currentxy = x, y
+ self.cornerxy = None
+ gridcell = self.gridcells.get(self.currentxy)
+ if gridcell is not None:
+ gridcell['bg'] = 'yellow'
+
+ def setcorner(self, x, y):
+ if self.currentxy is None or self.currentxy == (x, y):
+ self.setcurrent(x, y)
+ return
+ self.clearfocus()
+ self.cornerxy = x, y
+ x1, y1 = self.currentxy
+ x2, y2 = self.cornerxy or self.currentxy
+ if x1 > x2:
+ x1, x2 = x2, x1
+ if y1 > y2:
+ y1, y2 = y2, y1
+ for (x, y), cell in self.gridcells.iteritems():
+ if x1 <= x <= x2 and y1 <= y <= y2:
+ cell['bg'] = 'lightBlue'
+ gridcell = self.gridcells.get(self.currentxy)
+ if gridcell is not None:
+ gridcell['bg'] = 'yellow'
+ self.setbeacon(x1, y1, x2, y2)
+
+ def setbeacon(self, x1, y1, x2, y2):
+ if x1 == y1 == 1 and x2 == y2 == sys.maxint:
+ name = ":"
+ elif (x1, x2) == (1, sys.maxint):
+ if y1 == y2:
+ name = "%d" % y1
+ else:
+ name = "%d:%d" % (y1, y2)
+ elif (y1, y2) == (1, sys.maxint):
+ if x1 == x2:
+ name = "%s" % colnum2name(x1)
+ else:
+ name = "%s:%s" % (colnum2name(x1), colnum2name(x2))
+ else:
+ name1 = cellname(*self.currentxy)
+ name2 = cellname(*self.cornerxy)
+ name = "%s:%s" % (name1, name2)
+ self.beacon['text'] = name
+
+
+ def clearfocus(self):
+ if self.currentxy is not None:
+ x1, y1 = self.currentxy
+ x2, y2 = self.cornerxy or self.currentxy
+ if x1 > x2:
+ x1, x2 = x2, x1
+ if y1 > y2:
+ y1, y2 = y2, y1
+ for (x, y), cell in self.gridcells.iteritems():
+ if x1 <= x <= x2 and y1 <= y <= y2:
+ cell['bg'] = 'white'
+
+ def return_event(self, event):
+ "Callback for the Return key."
+ self.change_cell()
+ x, y = self.currentxy
+ self.setcurrent(x, y+1)
+ return "break"
+
+ def shift_return_event(self, event):
+ "Callback for the Return key with Shift modifier."
+ self.change_cell()
+ x, y = self.currentxy
+ self.setcurrent(x, max(1, y-1))
+ return "break"
+
+ def tab_event(self, event):
+ "Callback for the Tab key."
+ self.change_cell()
+ x, y = self.currentxy
+ self.setcurrent(x+1, y)
+ return "break"
+
+ def shift_tab_event(self, event):
+ "Callback for the Tab key with Shift modifier."
+ self.change_cell()
+ x, y = self.currentxy
+ self.setcurrent(max(1, x-1), y)
+ return "break"
+
+ def change_cell(self):
+ "Set the current cell from the entry widget."
+ x, y = self.currentxy
+ text = self.entry.get()
+ cell = None
+ if text.startswith('='):
+ cell = FormulaCell(text[1:])
+ else:
+ for cls in int, long, float, complex:
+ try:
+ value = cls(text)
+ except:
+ continue
+ else:
+ cell = NumericCell(value)
+ break
+ if cell is None and text:
+ cell = StringCell(text)
+ if cell is None:
+ self.sheet.clearcell(x, y)
+ else:
+ self.sheet.setcell(x, y, cell)
+ self.sync()
+
+ def sync(self):
+ "Fill the GUI cells from the sheet cells."
+ self.sheet.recalc()
+ for (x, y), gridcell in self.gridcells.iteritems():
+ if x == 0 or y == 0:
+ continue
+ cell = self.sheet.getcell(x, y)
+ if cell is None:
+ gridcell['text'] = ""
+ else:
+ if hasattr(cell, 'format'):
+ text, alignment = cell.format()
+ else:
+ text, alignment = str(cell), LEFT
+ gridcell['text'] = text
+ gridcell['anchor'] = align2anchor[alignment]
+
+
+def test_basic():
+ "Basic non-gui self-test."
+ import os
+ a = Sheet()
+ for x in range(1, 11):
+ for y in range(1, 11):
+ if x == 1:
+ cell = NumericCell(y)
+ elif y == 1:
+ cell = NumericCell(x)
+ else:
+ c1 = cellname(x, 1)
+ c2 = cellname(1, y)
+ formula = "%s*%s" % (c1, c2)
+ cell = FormulaCell(formula)
+ a.setcell(x, y, cell)
+## if os.path.isfile("sheet1.xml"):
+## print "Loading from sheet1.xml"
+## a.load("sheet1.xml")
+ a.display()
+ a.save("sheet1.xml")
+
+def test_gui():
+ "GUI test."
+ if sys.argv[1:]:
+ filename = sys.argv[1]
+ else:
+ filename = "sheet1.xml"
+ g = SheetGUI(filename)
+ g.root.mainloop()
+
+if __name__ == '__main__':
+ #test_basic()
+ test_gui()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/svkill.py b/sys/src/cmd/python/Demo/tkinter/guido/svkill.py
new file mode 100755
index 000000000..69f7f3b16
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/svkill.py
@@ -0,0 +1,128 @@
+#! /usr/bin/env python
+
+# Tkinter interface to SYSV `ps' and `kill' commands.
+
+from Tkinter import *
+
+if TkVersion < 4.0:
+ raise ImportError, "This version of svkill requires Tk 4.0 or later"
+
+from string import splitfields
+from string import split
+import commands
+import os
+
+user = os.environ['LOGNAME']
+
+class BarButton(Menubutton):
+ def __init__(self, master=None, **cnf):
+ apply(Menubutton.__init__, (self, master), cnf)
+ self.pack(side=LEFT)
+ self.menu = Menu(self, name='menu')
+ self['menu'] = self.menu
+
+class Kill(Frame):
+ # List of (name, option, pid_column)
+ view_list = [
+ ('Default', ''),
+ ('Every (-e)', '-e'),
+ ('Non process group leaders (-d)', '-d'),
+ ('Non leaders with tty (-a)', '-a'),
+ ('For this user (-u %s)' % user, '-u %s' % user),
+ ]
+ format_list = [
+ ('Default', '', 0),
+ ('Long (-l)', '-l', 3),
+ ('Full (-f)', '-f', 1),
+ ('Full Long (-f -l)', '-l -f', 3),
+ ('Session and group ID (-j)', '-j', 0),
+ ('Scheduler properties (-c)', '-c', 0),
+ ]
+ def kill(self, selected):
+ c = self.format_list[self.format.get()][2]
+ pid = split(selected)[c]
+ os.system('kill -9 ' + pid)
+ self.do_update()
+ def do_update(self):
+ format = self.format_list[self.format.get()][1]
+ view = self.view_list[self.view.get()][1]
+ s = commands.getoutput('ps %s %s' % (view, format))
+ list = splitfields(s, '\n')
+ self.header.set(list[0] + ' ')
+ del list[0]
+ self.frame.list.delete(0, AtEnd())
+ for line in list:
+ self.frame.list.insert(0, line)
+ def do_motion(self, e):
+ e.widget.select_clear('0', 'end')
+ e.widget.select_set(e.widget.nearest(e.y))
+ def do_leave(self, e):
+ e.widget.select_clear('0', 'end')
+ def do_1(self, e):
+ self.kill(e.widget.get(e.widget.nearest(e.y)))
+ def __init__(self, master=None, **cnf):
+ apply(Frame.__init__, (self, master), cnf)
+ self.pack(expand=1, fill=BOTH)
+ self.bar = Frame(self, name='bar', relief=RAISED,
+ borderwidth=2)
+ self.bar.pack(fill=X)
+ self.bar.file = BarButton(self.bar, text='File')
+ self.bar.file.menu.add_command(
+ label='Quit', command=self.quit)
+ self.bar.view = BarButton(self.bar, text='View')
+ self.bar.format = BarButton(self.bar, text='Format')
+ self.view = IntVar(self)
+ self.view.set(0)
+ self.format = IntVar(self)
+ self.format.set(0)
+ for num in range(len(self.view_list)):
+ label, option = self.view_list[num]
+ self.bar.view.menu.add_radiobutton(
+ label=label,
+ command=self.do_update,
+ variable=self.view,
+ value=num)
+ for num in range(len(self.format_list)):
+ label, option, col = self.format_list[num]
+ self.bar.format.menu.add_radiobutton(
+ label=label,
+ command=self.do_update,
+ variable=self.format,
+ value=num)
+ self.bar.tk_menuBar(self.bar.file,
+ self.bar.view,
+ self.bar.format)
+ self.frame = Frame(self, relief=RAISED, borderwidth=2)
+ self.frame.pack(expand=1, fill=BOTH)
+ self.header = StringVar(self)
+ self.frame.label = Label(
+ self.frame, relief=FLAT, anchor=NW, borderwidth=0,
+ font='*-Courier-Bold-R-Normal-*-120-*',
+ textvariable=self.header)
+ self.frame.label.pack(fill=Y, anchor=W)
+ self.frame.vscroll = Scrollbar(self.frame, orient=VERTICAL)
+ self.frame.list = Listbox(
+ self.frame,
+ relief=SUNKEN,
+ font='*-Courier-Medium-R-Normal-*-120-*',
+ width=40, height=10,
+ selectbackground='#eed5b7',
+ selectborderwidth=0,
+ selectmode=BROWSE,
+ yscroll=self.frame.vscroll.set)
+ self.frame.vscroll['command'] = self.frame.list.yview
+ self.frame.vscroll.pack(side=RIGHT, fill=Y)
+ self.frame.list.pack(expand=1, fill=BOTH)
+ self.update = Button(self, text='Update',
+ command=self.do_update)
+ self.update.pack(fill=X)
+ self.frame.list.bind('<Motion>', self.do_motion)
+ self.frame.list.bind('<Leave>', self.do_leave)
+ self.frame.list.bind('<1>', self.do_1)
+ self.do_update()
+
+if __name__ == '__main__':
+ kill = Kill(None, borderwidth=5)
+ kill.winfo_toplevel().title('Tkinter Process Killer (SYSV)')
+ kill.winfo_toplevel().minsize(1, 1)
+ kill.mainloop()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/switch.py b/sys/src/cmd/python/Demo/tkinter/guido/switch.py
new file mode 100644
index 000000000..3b58f7ce4
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/switch.py
@@ -0,0 +1,55 @@
+# Show how to do switchable panels.
+
+from Tkinter import *
+
+class App:
+
+ def __init__(self, top=None, master=None):
+ if top is None:
+ if master is None:
+ top = Tk()
+ else:
+ top = Toplevel(master)
+ self.top = top
+ self.buttonframe = Frame(top)
+ self.buttonframe.pack()
+ self.panelframe = Frame(top, borderwidth=2, relief=GROOVE)
+ self.panelframe.pack(expand=1, fill=BOTH)
+ self.panels = {}
+ self.curpanel = None
+
+ def addpanel(self, name, klass):
+ button = Button(self.buttonframe, text=name,
+ command=lambda self=self, name=name: self.show(name))
+ button.pack(side=LEFT)
+ frame = Frame(self.panelframe)
+ instance = klass(frame)
+ self.panels[name] = (button, frame, instance)
+ if self.curpanel is None:
+ self.show(name)
+
+ def show(self, name):
+ (button, frame, instance) = self.panels[name]
+ if self.curpanel:
+ self.curpanel.pack_forget()
+ self.curpanel = frame
+ frame.pack(expand=1, fill="both")
+
+class LabelPanel:
+ def __init__(self, frame):
+ self.label = Label(frame, text="Hello world")
+ self.label.pack()
+
+class ButtonPanel:
+ def __init__(self, frame):
+ self.button = Button(frame, text="Press me")
+ self.button.pack()
+
+def main():
+ app = App()
+ app.addpanel("label", LabelPanel)
+ app.addpanel("button", ButtonPanel)
+ app.top.mainloop()
+
+if __name__ == '__main__':
+ main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/tkman.py b/sys/src/cmd/python/Demo/tkinter/guido/tkman.py
new file mode 100755
index 000000000..6b0b64118
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/tkman.py
@@ -0,0 +1,267 @@
+#! /usr/bin/env python
+
+# Tk man page browser -- currently only shows the Tcl/Tk man pages
+
+import sys
+import os
+import string
+import re
+from Tkinter import *
+from ManPage import ManPage
+
+MANNDIRLIST = ['/depot/sundry/man/mann','/usr/local/man/mann']
+MAN3DIRLIST = ['/depot/sundry/man/man3','/usr/local/man/man3']
+
+foundmanndir = 0
+for dir in MANNDIRLIST:
+ if os.path.exists(dir):
+ MANNDIR = dir
+ foundmanndir = 1
+
+foundman3dir = 0
+for dir in MAN3DIRLIST:
+ if os.path.exists(dir):
+ MAN3DIR = dir
+ foundman3dir = 1
+
+if not foundmanndir or not foundman3dir:
+ sys.stderr.write('\n')
+ if not foundmanndir:
+ msg = """\
+Failed to find mann directory.
+Please add the correct entry to the MANNDIRLIST
+at the top of %s script.""" % \
+sys.argv[0]
+ sys.stderr.write("%s\n\n" % msg)
+ if not foundman3dir:
+ msg = """\
+Failed to find man3 directory.
+Please add the correct entry to the MAN3DIRLIST
+at the top of %s script.""" % \
+sys.argv[0]
+ sys.stderr.write("%s\n\n" % msg)
+ sys.exit(1)
+
+del foundmanndir
+del foundman3dir
+
+def listmanpages(mandir):
+ files = os.listdir(mandir)
+ names = []
+ for file in files:
+ if file[-2:-1] == '.' and (file[-1] in 'ln123456789'):
+ names.append(file[:-2])
+ names.sort()
+ return names
+
+class SelectionBox:
+
+ def __init__(self, master=None):
+ self.choices = []
+
+ self.frame = Frame(master, name="frame")
+ self.frame.pack(expand=1, fill=BOTH)
+ self.master = self.frame.master
+ self.subframe = Frame(self.frame, name="subframe")
+ self.subframe.pack(expand=0, fill=BOTH)
+ self.leftsubframe = Frame(self.subframe, name='leftsubframe')
+ self.leftsubframe.pack(side=LEFT, expand=1, fill=BOTH)
+ self.rightsubframe = Frame(self.subframe, name='rightsubframe')
+ self.rightsubframe.pack(side=RIGHT, expand=1, fill=BOTH)
+ self.chaptervar = StringVar(master)
+ self.chapter = Menubutton(self.rightsubframe, name='chapter',
+ text='Directory', relief=RAISED,
+ borderwidth=2)
+ self.chapter.pack(side=TOP)
+ self.chaptermenu = Menu(self.chapter, name='chaptermenu')
+ self.chaptermenu.add_radiobutton(label='C functions',
+ value=MAN3DIR,
+ variable=self.chaptervar,
+ command=self.newchapter)
+ self.chaptermenu.add_radiobutton(label='Tcl/Tk functions',
+ value=MANNDIR,
+ variable=self.chaptervar,
+ command=self.newchapter)
+ self.chapter['menu'] = self.chaptermenu
+ self.listbox = Listbox(self.rightsubframe, name='listbox',
+ relief=SUNKEN, borderwidth=2,
+ width=20, height=5)
+ self.listbox.pack(expand=1, fill=BOTH)
+ self.l1 = Button(self.leftsubframe, name='l1',
+ text='Display manual page named:',
+ command=self.entry_cb)
+ self.l1.pack(side=TOP)
+ self.entry = Entry(self.leftsubframe, name='entry',
+ relief=SUNKEN, borderwidth=2,
+ width=20)
+ self.entry.pack(expand=0, fill=X)
+ self.l2frame = Frame(self.leftsubframe, name='l2frame')
+ self.l2frame.pack(expand=0, fill=NONE)
+ self.l2 = Button(self.l2frame, name='l2',
+ text='Search regexp:',
+ command=self.search_cb)
+ self.l2.pack(side=LEFT)
+ self.casevar = BooleanVar()
+ self.casesense = Checkbutton(self.l2frame, name='casesense',
+ text='Case sensitive',
+ variable=self.casevar,
+ relief=FLAT)
+ self.casesense.pack(side=LEFT)
+ self.search = Entry(self.leftsubframe, name='search',
+ relief=SUNKEN, borderwidth=2,
+ width=20)
+ self.search.pack(expand=0, fill=X)
+ self.title = Label(self.leftsubframe, name='title',
+ text='(none)')
+ self.title.pack(side=BOTTOM)
+ self.text = ManPage(self.frame, name='text',
+ relief=SUNKEN, borderwidth=2,
+ wrap=NONE, width=72,
+ selectbackground='pink')
+ self.text.pack(expand=1, fill=BOTH)
+
+ self.entry.bind('<Return>', self.entry_cb)
+ self.search.bind('<Return>', self.search_cb)
+ self.listbox.bind('<Double-1>', self.listbox_cb)
+
+ self.entry.bind('<Tab>', self.entry_tab)
+ self.search.bind('<Tab>', self.search_tab)
+ self.text.bind('<Tab>', self.text_tab)
+
+ self.entry.focus_set()
+
+ self.chaptervar.set(MANNDIR)
+ self.newchapter()
+
+ def newchapter(self):
+ mandir = self.chaptervar.get()
+ self.choices = []
+ self.addlist(listmanpages(mandir))
+
+ def addchoice(self, choice):
+ if choice not in self.choices:
+ self.choices.append(choice)
+ self.choices.sort()
+ self.update()
+
+ def addlist(self, list):
+ self.choices[len(self.choices):] = list
+ self.choices.sort()
+ self.update()
+
+ def entry_cb(self, *e):
+ self.update()
+
+ def listbox_cb(self, e):
+ selection = self.listbox.curselection()
+ if selection and len(selection) == 1:
+ name = self.listbox.get(selection[0])
+ self.show_page(name)
+
+ def search_cb(self, *e):
+ self.search_string(self.search.get())
+
+ def entry_tab(self, e):
+ self.search.focus_set()
+
+ def search_tab(self, e):
+ self.entry.focus_set()
+
+ def text_tab(self, e):
+ self.entry.focus_set()
+
+ def updatelist(self):
+ key = self.entry.get()
+ ok = filter(lambda name, key=key, n=len(key): name[:n]==key,
+ self.choices)
+ if not ok:
+ self.frame.bell()
+ self.listbox.delete(0, AtEnd())
+ exactmatch = 0
+ for item in ok:
+ if item == key: exactmatch = 1
+ self.listbox.insert(AtEnd(), item)
+ if exactmatch:
+ return key
+ n = self.listbox.size()
+ if n == 1:
+ return self.listbox.get(0)
+ # Else return None, meaning not a unique selection
+
+ def update(self):
+ name = self.updatelist()
+ if name:
+ self.show_page(name)
+ self.entry.delete(0, AtEnd())
+ self.updatelist()
+
+ def show_page(self, name):
+ file = '%s/%s.?' % (self.chaptervar.get(), name)
+ fp = os.popen('nroff -man %s | ul -i' % file, 'r')
+ self.text.kill()
+ self.title['text'] = name
+ self.text.parsefile(fp)
+
+ def search_string(self, search):
+ if not search:
+ self.frame.bell()
+ print 'Empty search string'
+ return
+ if not self.casevar.get():
+ map = re.IGNORECASE
+ else:
+ map = None
+ try:
+ if map:
+ prog = re.compile(search, map)
+ else:
+ prog = re.compile(search)
+ except re.error, msg:
+ self.frame.bell()
+ print 'Regex error:', msg
+ return
+ here = self.text.index(AtInsert())
+ lineno = string.atoi(here[:string.find(here, '.')])
+ end = self.text.index(AtEnd())
+ endlineno = string.atoi(end[:string.find(end, '.')])
+ wraplineno = lineno
+ found = 0
+ while 1:
+ lineno = lineno + 1
+ if lineno > endlineno:
+ if wraplineno <= 0:
+ break
+ endlineno = wraplineno
+ lineno = 0
+ wraplineno = 0
+ line = self.text.get('%d.0 linestart' % lineno,
+ '%d.0 lineend' % lineno)
+ i = prog.search(line)
+ if i >= 0:
+ found = 1
+ n = max(1, len(prog.group(0)))
+ try:
+ self.text.tag_remove('sel',
+ AtSelFirst(),
+ AtSelLast())
+ except TclError:
+ pass
+ self.text.tag_add('sel',
+ '%d.%d' % (lineno, i),
+ '%d.%d' % (lineno, i+n))
+ self.text.mark_set(AtInsert(),
+ '%d.%d' % (lineno, i))
+ self.text.yview_pickplace(AtInsert())
+ break
+ if not found:
+ self.frame.bell()
+
+def main():
+ root = Tk()
+ sb = SelectionBox(root)
+ if sys.argv[1:]:
+ sb.show_page(sys.argv[1])
+ root.minsize(1, 1)
+ root.mainloop()
+
+main()
diff --git a/sys/src/cmd/python/Demo/tkinter/guido/wish.py b/sys/src/cmd/python/Demo/tkinter/guido/wish.py
new file mode 100755
index 000000000..0a61ad88d
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tkinter/guido/wish.py
@@ -0,0 +1,27 @@
+# This is about all it requires to write a wish shell in Python!
+
+import _tkinter
+import os
+
+tk = _tkinter.create(os.environ['DISPLAY'], 'wish', 'Tk', 1)
+tk.call('update')
+
+cmd = ''
+
+while 1:
+ if cmd: prompt = ''
+ else: prompt = '% '
+ try:
+ line = raw_input(prompt)
+ except EOFError:
+ break
+ cmd = cmd + (line + '\n')
+ if tk.getboolean(tk.call('info', 'complete', cmd)):
+ tk.record(line)
+ try:
+ result = tk.call('eval', cmd)
+ except _tkinter.TclError, msg:
+ print 'TclError:', msg
+ else:
+ if result: print result
+ cmd = ''