summaryrefslogtreecommitdiff
path: root/uitools
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2019-11-02 10:54:13 +0200
committerLars Wirzenius <liw@liw.fi>2019-11-02 10:54:13 +0200
commit2828885db093be86ef5b2c58f5c05ac3c4ed3664 (patch)
tree8185b82968b220aa543936e45be34fc239a5c8ea /uitools
downloadslime-2828885db093be86ef5b2c58f5c05ac3c4ed3664.tar.gz
Import historical filesHEADmaster
Diffstat (limited to 'uitools')
-rw-r--r--uitools/Alerts.py206
-rw-r--r--uitools/ButtonBox.py77
-rw-r--r--uitools/Cursors.py104
-rw-r--r--uitools/KWDict.py35
-rw-r--r--uitools/LabelEntry.py154
-rw-r--r--uitools/Menu.py245
-rw-r--r--uitools/ProgressBar.py70
-rw-r--r--uitools/README186
-rw-r--r--uitools/Scrolled.py184
-rw-r--r--uitools/Shells.py315
-rw-r--r--uitools/StdDialog.py136
-rw-r--r--uitools/TkDlgWrapper.py268
12 files changed, 1980 insertions, 0 deletions
diff --git a/uitools/Alerts.py b/uitools/Alerts.py
new file mode 100644
index 0000000..ef8c8e4
--- /dev/null
+++ b/uitools/Alerts.py
@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+######################################################################
+# This module provides some standard Alert dialogs.
+# This is a blatant imitation -- er, flattery -- of the tkdialogs
+# package, with extensions.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: Alerts.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+import Shells, StdDialog, ProgressBar
+
+######################################################################
+# This is yer basic Alert type.
+######################################################################
+class T(Shells.Modal):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master, title="Alert", msg="[your message here]"):
+ Shells.Modal.__init__(self, master)
+ self.controls = StdDialog.Controls(self)
+ self.msg = msg
+ self.top.title(title)
+ self.justify = "center"
+
+ frame = self.frame = self.controls.frame
+
+ bm = self.bitmap = Tk.Label(frame)
+ bm.pack(side='left')
+
+ l = self.msgLabel = Tk.Label(frame, text=msg)
+ l.pack(side='top', fill='x', expand='yes')
+
+ self.btnBox = self.controls.btnBox
+
+ # For easier override/extend in subclasses
+ show = Shells.Modal.show
+
+ ##################################################################
+ # Standard callbacks for the various types of buttons.
+ ##################################################################
+ def okCB(self, event=None):
+ self.result = 1
+ self.terminate()
+
+ def cancelCB(self, event=None):
+ self.result = 0
+ self.terminate()
+
+
+######################################################################
+# And now for some common alerts:
+######################################################################
+
+######################################################################
+# Display an error message.
+######################################################################
+class Error(T):
+ def __init__(self, master, title="Error", msg="Error"):
+ T.__init__(self, master, title, msg)
+ self.bitmap['bitmap'] = 'error'
+ self.btnBox.addButtons(["OK", self.okCB])
+
+ # For easier override/extend in subclasses
+ show = T.show
+
+
+######################################################################
+# Display a warning.
+######################################################################
+class Warning(T):
+ def __init__(self, master, title="Warning", msg="Warning",
+ okLabel="Yes", cancelLabel="No"):
+ T.__init__(self, master, title, msg)
+ self.bitmap['bitmap'] = 'warning'
+ self.btnBox.addButtons([okLabel, self.okCB],
+ [cancelLabel, self.cancelCB])
+
+ # For easier override/extend in subclasses
+ show = T.show
+
+
+######################################################################
+# Display a question.
+######################################################################
+class Question(T):
+ def __init__(self, master, title="Question", msg="?",
+ okLabel="Yes", cancelLabel="No"):
+ T.__init__(self, master, title, msg)
+ self.bitmap['bitmap'] = 'question'
+ self.btnBox.addButtons([okLabel, self.okCB],
+ [cancelLabel, self.cancelCB])
+
+ # For easier override/extend in subclasses
+ show = T.show
+
+
+######################################################################
+# Display some information.
+######################################################################
+class Info(T):
+ def __init__(self, master, title="Info", msg="I",
+ okLabel="OK"):
+ T.__init__(self, master, title, msg)
+ self.bitmap['bitmap'] = 'info'
+ self.btnBox.addButtons([okLabel, self.okCB])
+
+ # For easier override/extend in subclasses
+ show = T.show
+
+
+######################################################################
+# Display instructions for the user.
+######################################################################
+class Instruction(T):
+ def __init__(self, master, title="Instruction", msg="",
+ okLabel="Continue", cancelLabel="Cancel"):
+ T.__init__(self, master, title, msg)
+ self.bitmap.destroy()
+ self.btnBox.addButtons([okLabel, self.okCB],
+ [cancelLabel, self.cancelCB])
+
+ # For easier override/extend in subclasses
+ show = T.show
+
+
+######################################################################
+# A progress dialog shows progress, i.e. percent-completion of a task.
+######################################################################
+class Progress(T):
+ def __init__(self, master, title="Progress", msg="",
+ cancelLabel="Cancel"):
+ T.__init__(self, master, title, msg)
+ self.bitmap.destroy()
+ self.progressBar = ProgressBar.T(self.frame)
+ self.btnBox.addButtons([cancelLabel, self.cancelCB])
+ self.progressBar.frame.pack(fill='x')
+
+ # For easier overriding/extension in subclasses:
+ show = T.show
+
+ ##################################################################
+ # Update the progress slider.
+ # percentComplete should be in the range 0..100, inclusive.
+ ##################################################################
+ def update(self, percentComplete):
+ self.progressBar.update(percentComplete)
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ f = Shells.Main()
+ l = Tk.Label(f, text="I'm just here for looks,\nreally.")
+ l.pack()
+ f.pack()
+ f.master.geometry("+100+100")
+ e = Error(f, msg="An unrecoverable error\nhas occurred.")
+ print "Error:", e.show()
+ w = Warning(f, msg="Since that error occurred, do you want to go on?")
+ print "Warning:", w.show()
+ print "Question:", Question(f, msg="No, I mean\ndo you really want\n"
+ "to go on?").show()
+ print "Info:", Info(f, msg="This parrot is dead.").show()
+ print "Instruction:", Instruction(f,
+ msg='Insert a blank tape and press '
+ '"Continue".').show()
+
+ class ProgressTest(Progress):
+ def __init__(self, master, title="Progress", msg=""):
+ Progress.__init__(self, master, title, msg)
+ self.value = 0
+
+ def show(self):
+ # Start the timer before showing, because Progress.show
+ # won't return until after the user has dismissed the dialog.
+ self.timerID = self.top.after(500, self.update)
+ result = Progress.show(self)
+ self.top.after_cancel(self.timerID)
+ return result
+
+ def update(self, event=None):
+ self.value = self.value + 1
+ Progress.update(self, self.value)
+ if self.value < 100:
+ self.timerID = self.top.after(40, self.update)
+ else:
+ self.result = 1
+ self.terminate()
+
+
+ print "Progress:", ProgressTest(f,
+ msg="Reading Enc. Britannica").show()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/ButtonBox.py b/uitools/ButtonBox.py
new file mode 100644
index 0000000..456b3d5
--- /dev/null
+++ b/uitools/ButtonBox.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+######################################################################
+# This module provides a composite widget for dialog buttons.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: ButtonBox.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+import KWDict
+
+######################################################################
+# This class provides a horizontal array of std dialog buttons.
+######################################################################
+class T:
+ ##################################################################
+ # Add buttons to self.
+ # argtuple should be a tuple of lists or tuples
+ # specifying the label and (optionally) command for each button
+ # to be added to the frame.
+ ##################################################################
+ def _addButtons(self, argtuple):
+ Button = Tk.Button
+ f = self.frame
+ maxWidth = self.maxWidth
+ for btnInfo in argtuple:
+ label, cmd = btnInfo[0], None
+ if len(btnInfo) > 1:
+ cmd = btnInfo[1]
+ btn = Button(f, KWDict.dict(text=label, command=cmd))
+ btn.pack(side='left', padx='2m', expand='yes')
+ self.buttons[label] = btn
+ maxWidth = max(maxWidth, len(label))
+
+ self.maxWidth = maxWidth
+ for button in self.buttons.values():
+ button['width'] = maxWidth
+
+ ##################################################################
+ # Initialize a new instance.
+ # The optional arguments should each be lists or tuples
+ # specifying the label and (optionally) command for each button
+ # to be added to the frame.
+ ##################################################################
+ def __init__(self, master, *args):
+ self.master = master
+ f = self.frame = Tk.Frame(master, relief='raised', bd=1)
+
+ self.buttons = {}
+ self.maxWidth = 6
+ self._addButtons(args)
+ Tk.Pack.config(f, side='bottom', fill='x')
+
+ ##################################################################
+ # Add buttons to self.
+ # The optional arguments should each be lists or tuples
+ # specifying the label and (optionally) command for each button
+ # to be added to the frame.
+ ##################################################################
+ def addButtons(self, *args):
+ self._addButtons(args)
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ return
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/Cursors.py b/uitools/Cursors.py
new file mode 100644
index 0000000..647b81c
--- /dev/null
+++ b/uitools/Cursors.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+######################################################################
+# This module defines commonly-used CDE cursors.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: Cursors.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter
+
+# If you're running Solaris >= 2.4, you might want to try these
+# cursors:
+#busyCursorTuple = ('@/usr/dt/include/bitmaps/xm_hour16',
+# '/usr/dt/include/bitmaps/xm_hour16m',
+# 'black', 'white')
+#noEnterCursorTuple = ('@/usr/dt/include/bitmaps/xm_noenter16',
+# '/usr/dt/include/bitmaps/xm_noenter16m',
+# 'black', 'white')
+#
+# Clients can specify any of these cursors for CDE-style cursoring.
+#busyCursor = "%s %s %s %s" % busyCursorTuple
+#noEnterCursor = "%s %s %s %s" % noEnterCursorTuple
+
+busyCursor = "watch"
+noEnterCursor = "X_cursor red white"
+
+# _instances records all instances of Mixin. (See below.)
+_instances = {}
+
+######################################################################
+# This mixin class gives the ability to push and pop cursors on a
+# widget.
+######################################################################
+class Mixin:
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self):
+ # Register self to participate in cursoring. Whenever other
+ # Mixin instances pushOtherCursors(), self will get a new
+ # cursor.
+ _instances[self] = self
+ self.cursorStack = []
+
+ ##################################################################
+ # Destroy self. (Is this the right way to do this?)
+ ##################################################################
+ def destroy(self):
+ del _instances[self] # De-register self
+
+ ##################################################################
+ # Set a cursor on self.
+ ##################################################################
+ def setCursor(self, cursor):
+ # Why not just self.config(cursor=cursor)?
+ # I originally wanted to be able to mix this in to class Tk.
+ apply(self.tk.call, (self._w, "configure", "-cursor", cursor))
+
+ ##################################################################
+ # Push a cursor on self. (Doesn't affect other CursorMixin
+ # instances.)
+ ##################################################################
+ def pushCursor(self, newCursor):
+ self.setCursor(newCursor)
+ self.cursorStack.append(newCursor)
+
+ ##################################################################
+ # Pop a cursor off self.
+ ##################################################################
+ def popCursor(self):
+ stack = self.cursorStack
+ # Pop.
+ if stack:
+ del stack[-1]
+ # Re-install the old cursor, or else the default cursor.
+ if stack:
+ self.setCursor(stack[-1])
+ else:
+ self.setCursor('')
+
+ ##################################################################
+ # Push a cursor on all other registered instances.
+ # This is useful e.g. when displaying a modal dialog, and
+ # displaying busy cursors in other dialogs and toplevels.
+ ##################################################################
+ def pushOtherCursors(self, newCursor):
+ for w in _instances.keys():
+ if w != self:
+ w.pushCursor(newCursor)
+
+ ##################################################################
+ # Pop a cursor from all other registered instances.
+ ##################################################################
+ def popOtherCursors(self):
+ for w in _instances.keys():
+ if w != self:
+ w.popCursor()
+
diff --git a/uitools/KWDict.py b/uitools/KWDict.py
new file mode 100644
index 0000000..3fddac0
--- /dev/null
+++ b/uitools/KWDict.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+######################################################################
+# This module provides a means of converting a keyword argument list
+# to a dictionary. Thank you, "Internet Programming With Python".
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: KWDict.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+######################################################################
+# Build a dictionary using keyword notation. Include only items
+# whose values are not None.
+######################################################################
+def dict(**kw):
+ result = {}
+ for k, v in kw.items():
+ if v != None:
+ result[k] = v
+ return result
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ return
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/LabelEntry.py b/uitools/LabelEntry.py
new file mode 100644
index 0000000..203427a
--- /dev/null
+++ b/uitools/LabelEntry.py
@@ -0,0 +1,154 @@
+######################################################################
+# This class provides an Entry with a label on its left side.
+# The text of the label can be set by means of the 'text'
+# configuration key.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: LabelEntry.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+
+######################################################################
+# A MultiColEntry is a label together with one or more entries.
+# Clients have direct access to the constituent widgets:
+# frame -- the frame containing the other widgets.
+# label -- the label appearing on the left side
+# entries -- The entries arrayed across from left to right
+######################################################################
+class MultiColEntry:
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None, columns=2, entryClass=Tk.Entry):
+ f = self.frame = Tk.Frame(master)
+ l = self.label = Tk.Label(f, anchor='ne')
+ l.pack(side='left')
+ self.entries = []
+ for i in range(columns):
+ e = entryClass(f)
+ self.entries.append(e)
+ e.pack(side='left', fill='x', expand='yes')
+
+ ##################################################################
+ # Set the values of one or more entry fields.
+ # It's the caller's responsibility not to exceed the number of
+ # entries in self.
+ ##################################################################
+ def setValues(self, startIndex=0, *newValues):
+ i = startIndex
+ for v in newValues:
+ e = self.entries[i]
+ e.delete('0', 'end')
+ e.insert('end', v)
+
+
+######################################################################
+# A labelled entry is a MultiColEntry with only one entry column.
+# The instance variable "entry" provides a more-convenient way to
+# refer to the sole entry widget.
+######################################################################
+class LabelEntry(MultiColEntry):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None, entryClass=Tk.Entry):
+ MultiColEntry.__init__(self, master, 1, entryClass)
+ self.entry = self.entries[0]
+
+ ##################################################################
+ # Set the whole value of an Entry.
+ ##################################################################
+ def setValue(self, newValue):
+ self.entry.delete(0, 'end')
+ self.entry.insert('end', newValue)
+
+ ##################################################################
+ # Delete LabelEntry text.
+ ##################################################################
+ def delete(self, first, last=None):
+ self.entry.delete(first, last)
+
+ ##################################################################
+ # Insert LabelEntry text.
+ ##################################################################
+ def insert(self, index, string):
+ self.entry.insert(index, string)
+
+######################################################################
+# A ROLabelEntry is read-only, so far as the user is concerned.
+######################################################################
+class ROLabelEntry(LabelEntry):
+ def __init__(self, master=None):
+ LabelEntry.__init__(self, master, Tk.Entry)
+ self.entry['state'] = 'disabled'
+
+ ##################################################################
+ # Delete text from the entry.
+ ##################################################################
+ def delete(self, first, last=None):
+ self.entry['state'] = 'normal'
+ self.entry.delete(first, last)
+ self.entry['state'] = 'disabled'
+
+ ##################################################################
+ # Insert text into the entry.
+ ##################################################################
+ def insert(self, index, string):
+ self.entry['state'] = 'normal'
+ self.entry.insert(index, string)
+ self.entry['state'] = 'disabled'
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ f = Tk.Frame()
+
+ le = LabelEntry(f)
+ le.label['text'] = "New Value:"
+ newValue = "This is the new value."
+ le.entry['width'] = len(newValue)
+ le.insert('end', newValue)
+
+ readOnly = ROLabelEntry(f)
+ readOnly.label['text'] = 'Label:'
+ readOnly.entry['relief'] = 'groove'
+ for w in [readOnly.label, readOnly.entry]:
+ w['width'] = 40
+
+ readOnly.insert('0', 'This is the value')
+
+ mc = MultiColEntry(f, 8)
+ mc.label['text'] = "Multi:"
+ map(lambda e: e.config(width=8), mc.entries)
+
+ for w in [le, readOnly, mc]:
+ w.label['width'] = 12
+ w.frame.pack(fill='x')
+
+ f.update()
+ f.pack()
+ f.wait_visibility()
+ f.after(2000, readOnly.insert, 'end', ' after 2 seconds')
+
+ ##################################################################
+ # Insert text in a multi-column widget.
+ ##################################################################
+ def insertMulti(mc):
+ map(lambda e: e.insert('end', 'This is ' + `e`), mc.entries)
+
+ mc.setValues(2, "This is entry 2.", "This is entry 3.", "This is entry 4.")
+
+ f.after(4000, insertMulti, mc)
+ f.mainloop()
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/Menu.py b/uitools/Menu.py
new file mode 100644
index 0000000..8b64615
--- /dev/null
+++ b/uitools/Menu.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python
+######################################################################
+# Provide classes to simplify creating menubars, pulldown menus, etc.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: Menu.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+import KWDict, string
+
+######################################################################
+# Convert a string of the form "&Something" to a tuple:
+# ("Something", 0)
+# indicating that the first letter of "Something" should be
+# underlined. If no "&" occurs inside the string, the second tuple
+# item will be None.
+######################################################################
+def findUnderline(title):
+ ul = None
+ i = string.find(title, "&")
+ if i >= 0:
+ ul = i
+ title = title[:ul] + title[ul+1:]
+ return title, ul
+
+
+######################################################################
+# This class represents a menubar, w. default relief, etc.
+######################################################################
+class Menubar(Tk.Frame):
+ ##################################################################
+ # Create a new instance.
+ ##################################################################
+ def __init__(self, master=None, **kw):
+ opts = {'relief':'raised', 'bd':2}
+ for k,v in kw.items():
+ opts[k] = v
+ apply(Tk.Frame.__init__, (self, master), opts)
+ Tk.Pack.config(self, side='top', fill='x')
+
+
+######################################################################
+# This class represents a menu item in a Tk menu.
+######################################################################
+class Item:
+ ##################################################################
+ # Create a new instance.
+ # master is the pane to which to append the new item.
+ # title is the text which will appear inside the menu item. It
+ # also determines what sort of menu item will be created. See
+ # below for details.
+ # cmd is the optional callback for this menu item.
+ # items specifies any items to be displayed in cascaded menus.
+ ##################################################################
+ def __init__(self, master, title, cmd=None, *items):
+ self.master = master
+ self.title = title
+
+ if title[0] == "-":
+ master.add_separator()
+ self.index = master.index('last')
+ return
+
+ submenu = None
+ createFn = master.add_command
+ prefix = title[0:2]
+ if prefix == "c_":
+ createFn = master.add_checkbutton
+ elif prefix == "r_":
+ createFn = master.add_radiobutton
+ elif prefix == "m_":
+ createFn = master.add_cascade
+ submenu = Pane(master)
+ apply(submenu.addItems, items)
+ else:
+ title = "x_" + title
+
+ title, ul = findUnderline(title[2:])
+
+ # XXX no means is provided to associate a Tk variable with a
+ # radio button or a check button.
+ apply(createFn, (), KWDict.dict(label=title, command=cmd,
+ underline=ul, menu=submenu))
+ self.index = master.index('last')
+
+ ##################################################################
+ # Enable or disable an item.
+ ##################################################################
+ def enable(self, doEnable=1):
+ if doEnable:
+ newstate = 'normal'
+ else:
+ newstate = 'disabled'
+
+ try:
+ # NOTE: This won't work for tear-off entries, or for separators.
+ self.master.tk.call(self.master._w, 'entryconfigure', self.index,
+ '-state', newstate)
+ except Tk.TclError: pass
+
+######################################################################
+# This class represents a pulldown menu.
+######################################################################
+class Pulldown:
+ ##################################################################
+ # Create a new Pulldown menu.
+ # Each additional item should be a list consisting of menu
+ # item string and optional callback. Menu item strings are
+ # interpreted as indicated in parseMenuStr().
+ # Returns the menu button which posts the new menu.
+ ##################################################################
+ def __init__(self, master, title, *items, **kw):
+ self.master = master
+
+ packside='left'
+ if kw and 'side' in kw.keys():
+ packside=kw['side']
+
+ # Create the menu button.
+ title, ul = findUnderline(title)
+ btnKeywords = KWDict.dict(text=title, underline=ul)
+ btn = apply(Tk.Menubutton, (master,), btnKeywords)
+
+ # Create the menu pane and associate it with the button.
+ menu = btn.menu = Pane(btn)
+ btn['menu'] = menu
+ apply(menu.addItems, items)
+
+ btn.pack(side=packside, padx='2m')
+
+
+######################################################################
+# This class extends the Tkinter Menu class with convenience methods
+# for enabling/disabling menu items and for support of popup menus.
+######################################################################
+class Pane(Tk.Menu):
+ ##################################################################
+ # Init a new instance.
+ ##################################################################
+ def __init__(self, master=None, **kw):
+ apply(Tk.Menu.__init__, (self, master), kw)
+ self.items = []
+
+ ##################################################################
+ # Add items to a menu Pane.
+ ##################################################################
+ def addItems(self, *newItems):
+ # Create a menu item for each described item.
+ for item in newItems:
+ self.items.append(apply(Item, (self,) + tuple(item)))
+
+ ##################################################################
+ # Enable or disable a menu item.
+ # If enable is non-zero, then the menu item is enabled; otherwise
+ # it is disabled.
+ ##################################################################
+ def enable(self, index, enable=1):
+ self.items[index].enable(enable)
+
+ ##################################################################
+ # Pop up a menu pane --
+ # useful when Pane is being used as a popup menu.
+ ##################################################################
+ def tk_popup(self, event=None):
+ x, y = 0, 0
+ if event:
+ x, y = event.x_root, event.y_root
+ self.tk.call('tk_popup', self._w, x, y)
+
+
+######################################################################
+# Mainline for testing
+######################################################################
+def main():
+ import TkDlgWrapper; TkDlg=TkDlgWrapper
+
+ # To demonstrate, here's a simple file editor.
+ f = Tk.Frame()
+ m = Menubar(f)
+ t = Tk.Text(f)
+
+ # Here are the file types which the open/save dialogs will recognize.
+ filetypes = (("Python", ".py"), ("Text", ".txt"), ("All Files", "*"))
+ openFile = TkDlg.OpenFile(f, defaultextension=".py",
+ filetypes=filetypes)
+ saveFile = TkDlg.SaveFile(f, defaultextension=".py",
+ filetypes=filetypes)
+
+ # Here's an example of a popup menu. It should appear when
+ # you click mouse button 3 inside the text area.
+ textPopup = Pane(f, tearoff='0')
+ textPopup.addItems(["Cut"], ["Copy"], ["Paste"], ["Clear"])
+ # Initially, disable all of the items. Enable them when a file
+ # is actually opened.
+ for item in textPopup.items:
+ item.enable(0)
+ t.bind("<3>", textPopup.tk_popup)
+
+ def openAFile(openDlg = openFile, text=t, popup=textPopup):
+ filename = openDlg.show()
+ if filename:
+ text.delete('1.0', 'end')
+ inf = open(filename)
+ text.insert('end', inf.read())
+ inf.close()
+ text.winfo_toplevel().title("Menu Example:%s" % filename)
+ # Enable the popup menu items.
+ for item in popup.items:
+ item.enable(1)
+
+ def saveAFile(saveDlg = saveFile, text=t):
+ filename = saveDlg.show()
+ if filename:
+ outf = open(filename, "w")
+ outf.write(text.get('1.0', 'end'))
+ outf.close()
+ text.winfo_toplevel().title("Menu Example:%s" % filename)
+
+ # Keyboard menu traversal appears to be broken under Tk 4.2 Try using
+ # keyboard traversal in the tk4.2 library's demo subdirectory...
+ fileMenu = Pulldown(m, "&File",
+ ["&Open...", openAFile],
+ ["Save &As...", saveAFile],
+ ["----"],
+ ["E&xit", m.quit])
+
+ helpMenu = Pulldown(m, "&Help",
+ ["&About"],
+ side='right')
+
+ m.pack()
+ t.pack()
+ f.pack()
+ f.master.title("Menu Example")
+ f.mainloop()
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/ProgressBar.py b/uitools/ProgressBar.py
new file mode 100644
index 0000000..0098ead
--- /dev/null
+++ b/uitools/ProgressBar.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+######################################################################
+# This module provides a progress bar, analogous to the progress
+# sliders in Win 3.1 or the progress slider in Netscape Nav.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: ProgressBar.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+
+######################################################################
+# This class provides a progress slider embedded within a Tk frame.
+######################################################################
+class T:
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None, height=25, fillColor="blue"):
+ # Put the sunken relief on the frame, not the canvas.
+ # This way, if some subclass decides to embed a window within
+ # the canvas, the window won't ever draw itself into the
+ # relief area.
+ self.master = master
+ self.frame = Tk.Frame(master, relief='sunken', bd=2)
+ self.canvas = Tk.Canvas(self.frame, height=height, bd=0,
+ highlightthickness=0)
+ self.scale = self.canvas.create_rectangle(-10, -10, 0, height,
+ fill=fillColor)
+ self.canvas.pack(side='top', fill='x', expand='no')
+
+ ##################################################################
+ # Specify the completion value for self.
+ # percentComplete must be in the range 0..100, inclusive.
+ ##################################################################
+ def update(self, percentComplete):
+ c = self.canvas
+ width, height = c.winfo_reqwidth(), c.winfo_reqheight()
+ c.coords(self.scale, -10, -10,
+ percentComplete * width / 100.0, height)
+ c.update_idletasks()
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ class Test:
+ def __init__(self):
+ self.progress = T()
+ self.fraction = 0
+ self.progress.frame.pack()
+ self.progress.frame.after(1000, self.update)
+
+ def update(self, event=None):
+ self.fraction = self.fraction + 1
+ self.progress.update(self.fraction)
+ self.progress.frame.after(30, self.update)
+
+ t = Test()
+ Tk.mainloop()
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/README b/uitools/README
new file mode 100644
index 0000000..61471d1
--- /dev/null
+++ b/uitools/README
@@ -0,0 +1,186 @@
+________________________________________________________________________
+About UITools
+
+ There's been talk recently in comp.lang.python of the need
+ for a set of "standard" composite widget classes to complement
+ Tkinter.py. Here's my attempt to get the ball rolling.
+
+ This library consists of several modules which implement various
+ Python composite widget ("metawidget") classes. Most of these
+ classes are derived from code which I've been using for the past
+ year or so, but which I haven't heretofore organized.
+
+ Most modules have a "principal export". That is, they define
+ one main class along with zero or more derived/supporting
+ classes. The principal export is usually a class named "T".
+ This implies that these modules should be imported using
+ "import <module>" rather than "from <module> import *".
+
+ Classes which represent dialogs do not include "Dialog" or "Dlg"
+ in the class names. For example, Alerts.py exports several
+ alert dialog classes having names like Error, Warning, Question,
+ etc; Shells.py exports classes Main, Toplevel, NonModal and
+ Modal.
+
+ I omitted the "Dialog" suffix on the grounds that it was
+ redundant. Please let me know if you think this was a mistake.
+
+ Most modules include their own unit-test code, exported as
+ function main(). If a module is imported as a program's main
+ module, its main() function is automatically invoked. The
+ main() functions serve not only as rudimentary unit-tests, but
+ also as examples of how to use the classes defined in their
+ containing modules. For example, Menu.main() fires up a
+ simple-minded text editor, equipped with both Pulldown and Popup
+ menus.
+
+ Here's hoping this stuff is useful. At the least, I hope it
+ catalyzes contributions of other Python-based composite widget
+ classes.
+
+
+ -- Mitch Chapman
+ mchapman@erinet.com
+ 1 December 1996
+
+________________________________________________________________________
+Constituent Modules
+
+
+Alerts.py
+ Provides various error/warning/info dialogs. This module is
+ inspired by the alerts.py module of the tkdialogs archive.
+
+ The principal exported class, T, is a generic alert dialog.
+ Exported subclasses include:
+ Error -- for displaying error messages
+ Warning -- for displaying warnings
+ Question -- yes/no question presentation
+ Info -- displays informational messages
+ Instruction -- asks the user to perform a task and
+ press "Continue"
+ Progress -- shows the progress of some operation
+
+ButtonBox.py
+ Provides a frame which packs buttons horizontally.
+ Buttons can be accessed from the .buttons member dictionary,
+ using the labels by which the buttons were initially created.
+
+ (Would a simple array of buttons be easier to use?)
+
+Cursors.py
+ Provides a means of controlling cursors on an application-wide
+ basis. (For an example of use, see Shells.py.)
+
+KWDict.py
+ Provides a way to construct dictionaries from keyword argument
+ lists. Many thanks to "Internet Programming With Python,"
+ whence this code was stolen.
+
+LabelEntry.py
+ Provides multi-column, single-column and read-only labelled
+ entries.
+
+Menu.py
+ Provides a convenient means of constructing pulldown and popup
+ menus. With a single method call you can construct an entire
+ pulldown menu, complete with cascaded submenus.
+
+ Exported classes include Menubar (basically just a Tk.Frame);
+ Item, representing a single item within a menu pane; Pulldown,
+ representing a Menubutton and its associated menu pane; and
+ Pane, which can serve as either the pane for a Pulldown or as a
+ Popup.
+
+ This module was inspired by, but is less capable than, the
+ menu-creation functions in XEmacs 19.14.
+
+ This module needs more work to properly support Checkbutton and
+ Radiobutton menu items. The current version makes it hard to
+ associate Tk variables with such menu items.
+
+ProgressBar.py
+ Provides a status bar for displaying task completion percentages.
+ For an example of the use of a progress bar in a dialog, see
+ Alerts.py.
+
+Scrolled.py
+ Provides scrollbar decorations, along with scrolled texts,
+ canvases and lists. Exports include T, a generic
+ scrolled-window composite class into which an arbitrary "view"
+ widget may be inserted; Text, a scrolled text window; Canvas, a
+ scrolled canvas window; and List, a scrolled listbox.
+
+ This module was written using the Tk packer, rather than the
+ Tk 4.1+ grid widget. (The 4.1 grid widget looked a little
+ unreliable to me, and apparently not many people have yet
+ installed Tk 4.2.)
+
+ The scrollbars extend only to the bottom-right edge of the
+ scrolled view, using a technique described in Welch's "Practical
+ Programming with Tcl/Tk". (Dang! Is that the right title?)
+
+ In this version, both vertical and horizontal scrollbars are
+ displayed fulltime. I haven't yet found a way to dynamically
+ map and unmap the scrollbars which doesn't introduce
+ "flickering". (The scrollbar will map and unmap two or three
+ times before settling into a visible or invisible state.) Your
+ help is kindly requested.
+
+
+Shells.py
+ Provides various top-level, non-modal and modal dialog shells,
+ together with application-wide cursor control. Exports include
+ Main, a subclass of Tk.Frame; Toplevel, derived from
+ Tk.Toplevel; and NonModal and Modal dialog classes.
+
+ Main and Toplevel are provided because they support
+ application-wide cursor management. That is, when a
+ Shells.Modal dialog is mapped, all existing Main, Toplevel and
+ NonModal windows display a "do not enter" cursor.
+
+ The dialog classes are similar to those provided by the
+ tkdialogs archive. They differ in that they can easily be
+ created and initialized without being displayed. So, for
+ example, if you have a non-modal dialog which shows logged
+ network traffic, you can create it as soon as your application
+ starts and display it only when the user requests to see it.
+
+StdDialog.py
+ Provides "standard" modal and non-modal dialogs, as well as
+ button boxes populated with OK, Cancel and Help buttons.
+
+ Exported classes include Controls, a composite containing a
+ frame and a ButtonBox.T, stacked vertically; StdControls, a
+ subclass of Controls which populates its ButtonBox.T with "OK",
+ "Cancel" and "Help" buttons; NonModal and Modal, two dialog
+ classes whose managed contents consist of StdControl instances.
+
+ This module has a really poor design. I was trying to build a
+ framework for "typical" dialog windows, which contain a set of
+ controls packed into a frame, below which is a row of
+ dialog buttons. Class Controls represents the "typical" dialog
+ contents; StdControls represents typical contents along with
+ typical dialog buttons; and NonModal and Modal are dialogs whose
+ innards are StdControls.
+
+ Please help me straighten this out.
+
+TkDlgWrapper.py
+ Provides wrappers around the standard dialogs included with
+ Tk 4.2. (NOTE: This module _requires_ Tk 4.2 or later.)
+
+ The most significant exported classes include ChooseColor, an
+ interactive color-selection dialog, and OpenFile and SaveFile,
+ file open/save dialogs which display directory contents using
+ folder and document icons.
+
+ I originally released this module in October 1996 as "TkDialogs.py",
+ not knowing that ftp.python.org already had a tkdialogs archive.
+ Both this module module and the containing archive (UITools)
+ now have names which bear no resemblance to "tkdialogs", so
+ ending any potential name conflicts. I hope.
+
+ The classes have been renamed to omit "Dlg" from the class
+ names, but are otherwise unchanged from the original release.
+
diff --git a/uitools/Scrolled.py b/uitools/Scrolled.py
new file mode 100644
index 0000000..e2c1a53
--- /dev/null
+++ b/uitools/Scrolled.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+######################################################################
+# Provides a generic window containing a frame and h/v scrollbars.
+# Subclasses pack the scrollable/scannable thing into the frame and
+# hook it up to the scrollbars by invoking setView.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: Scrolled.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+import string, time
+
+######################################################################
+# This is the scrolled window class.
+######################################################################
+class T:
+ ##################################################################
+ # Specify the view to be scrolled by self.
+ ##################################################################
+ def _setView(self, newView):
+ self.view = newView
+ self.view.pack(in_=self.borderframe, fill='both', expand='yes')
+ self.vsPacking['before'] = self.view
+ # Attach the scrollbars. Note that scrollable must support
+ # these methods: xview, yview, xscrollcommand, yscrollcommand.
+ self.vscroll['command'] = self.view.yview
+ self.hscroll['command'] = self.view.xview
+ self.view['xscrollcommand'] = self.hscroll.set
+ self.view['yscrollcommand'] = self.vscroll.set
+ # Make sure the view is visible to the user
+ # Gotta explicitly use Tk.Misc.tkraise, because if the view
+ # is a canvas it defines its own tkraise -- whose purpose
+ # is to change the stacking order of canvas items.
+ Tk.Misc.tkraise(self.view, self.borderframe)
+
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None, view=None):
+ self.master = master
+ frame = self.frame = Tk.Frame(master)
+
+ # The inner frame is provided because the Tk Text widget
+ # (<= Tk 4.1) wasn't smart enough to keep embedded windows from
+ # drawing over its border. This inner frame can take the place
+ # of the Text border in subclasses.
+ self.borderframe = Tk.Frame(frame, relief='sunken', bd=3,
+ highlightthickness=2)
+
+ # Create the scrollbars. Record their packing rules for later
+ # use in dynamically mapping/unmapping scrollbars.
+ self.vsPacking = {'side':'right', 'fill':'y'}
+ vs = self.vscroll = Tk.Scrollbar(frame, orient='vertical', width=12)
+
+ # The horizontal scrollbar goes into its own frame at the
+ # bottom. This offers the opportunity to stick a "spacer"
+ # frame to the right of the horizontal bar, so both scrollbars
+ # appear to stop at the edge of the contained view.
+ # This is stolen from "Practical Programming in Tcl and Tk," by
+ # Brent B. Welch.
+ padsize = self.padsize = (string.atoi(vs['width']) +
+ 2 *(string.atoi(vs['bd']) +
+ string.atoi(vs['highlightthickness'])))
+ hsFrame = self.hsFrame = Tk.Frame(frame)
+ # Here's the "spacer" frame.
+ hsPad = self.hsPad = Tk.Frame(hsFrame, width=padsize, height=padsize)
+
+ hs = self.hscroll = Tk.Scrollbar(hsFrame, orient='horizontal',
+ width=12)
+ hs.pack(side='bottom', fill='x')
+ hsPad.pack(side='right', before=self.hscroll)
+ # This time, the packing is for self.hsFrame
+ self.hsFramePacking = {'before':vs,
+ 'side':'bottom', 'fill':'x', 'expand':'no'}
+
+ apply(vs.pack, (), self.vsPacking)
+ self.borderframe.pack(fill='both', expand='yes')
+ apply(hsFrame.pack, (), self.hsFramePacking)
+
+ self.view = None
+ self.hsPacked = 1
+ self.vsPacked = 1
+
+ # Bad move: initializer invoking another method on self.
+ # At least _setView is named so as to indicate that subclasses
+ # should not override it directly...
+ if view:
+ self._setView(view)
+
+ ##################################################################
+ # Install a scrollable thingy as the view for self.
+ # Note that you can change views on the fly, though it's up to
+ # you to remove any old views before setting the new one.
+ # newView should be a widget. It must provide Tkinter.Text-style
+ # methods xview() and yview(), and have configurable
+ # xscrollcommand and yscrollcommand attributes.
+ ##################################################################
+ def setView(self, newView):
+ self._setView(newView)
+
+
+######################################################################
+# This is a scrolled text class.
+######################################################################
+class Text(T):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None):
+ self.text = Tk.Text(master, relief='flat')
+ T.__init__(self, master, self.text)
+
+######################################################################
+# This is a scrolled canvas class.
+######################################################################
+class Canvas(T):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None):
+ self.canvas = Tk.Canvas(master)
+ T.__init__(self, master, self.canvas)
+
+######################################################################
+# A List differs from other Scrolled types in that it has only a
+# vertical scrollbar. (This despite the fact that lists with long
+# entries could benefit from horizontal scrollbars...)
+######################################################################
+class List(T):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None):
+ self.listbox = Tk.Listbox(master)
+ T.__init__(self, master, self.listbox)
+ self.hsFrame.forget()
+ self.view['xscrollcommand'] = None
+ self.hscroll['command'] = None
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ if 0:
+ # Here's one way to build a custom scrolled window:
+ scroller = T(view=Tk.Text(relief='flat', wrap='none'))
+ scroller.frame.pack(fill='both', expand='yes')
+ scroller.frame.master.title("Second Scroller")
+ Tk.mainloop()
+ else:
+ f = Tk.Frame()
+ t = Text(f)
+ # Turn off text wrapping so you can see the scrollbars at work.
+ t.text.configure(width=32, height=5, wrap='none')
+ t.text.insert('end', "This is a text.")
+ t.frame.pack(fill='both', expand='yes')
+
+ c = Canvas(f)
+ c.canvas.configure(scrollregion="-100 -100 1000 1000")
+ c.canvas.create_text(0, 0, text="This is a canvas.", anchor="nw")
+ c.canvas.create_arc(300, 300, 400, 400, extent=270, fill='red')
+ c.canvas.create_oval(100, 100, 200, 200, fill='blue')
+ c.frame.pack(fill='both', expand='yes')
+
+ l = List(f)
+ l.listbox.insert('end', "This is a listbox.")
+ for i in range(1, 21):
+ l.listbox.insert('end', "%d Mississippi" % i)
+ l.frame.pack(fill='both', expand='yes')
+
+ f.master.title("Sample Scrolled Windows")
+ f.pack()
+ Tk.mainloop()
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/Shells.py b/uitools/Shells.py
new file mode 100644
index 0000000..623deea
--- /dev/null
+++ b/uitools/Shells.py
@@ -0,0 +1,315 @@
+#!/usr/bin/env python
+######################################################################
+# This module provides base classes for various shell classes.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: Shells.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+import Cursors
+
+######################################################################
+# Make widget a transient for master.
+# widget should be a Toplevel.
+######################################################################
+def makeTransientFor(widget, master):
+ shell = master.winfo_toplevel()
+ name = shell.group() or "."
+ widget.group(name)
+ widget.transient(widget._nametowidget(name))
+
+######################################################################
+# This is a Tk Frame which knows how to register itself for cursor
+# stacking. It is intended to serve as the main window of an
+# application.
+######################################################################
+class Main(Tk.Frame, Cursors.Mixin):
+ def __init__(self, master=None, **kw):
+ apply(Tk.Frame.__init__, (self, master), kw)
+ apply(Cursors.Mixin.__init__, (self,))
+
+ def destroy(self):
+ Tk.Frame.destroy(self)
+ Cursors.Mixin.destroy(self)
+
+
+######################################################################
+# This is a Tk Toplevel which knows how to register itself for cursor
+# stacking.
+######################################################################
+class Toplevel(Tk.Toplevel, Cursors.Mixin):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None, **kw):
+ apply(Tk.Toplevel.__init__, (self, master), kw)
+ apply(Cursors.Mixin.__init__, (self,))
+
+ ##################################################################
+ # Destroy an instance.
+ ##################################################################
+ def destroy(self):
+ Tk.Toplevel.destroy(self)
+ Cursors.Mixin.destroy(self)
+
+
+######################################################################
+# This is a modal dialog class.
+######################################################################
+class NonModal:
+ ##################################################################
+ # Initialize a new instance. master should be a widget in front
+ # of which self must appear.
+ ##################################################################
+ def __init__(self, master):
+ self.master = master
+ self.top = Toplevel(self.master)
+ self.top.withdraw()
+ self.isShowing = 0
+ self.top.title(self.__class__.__name__)
+ self.waitVar = `self.top` + 'EndDialogVar'
+
+ # Indicate that the dialog has not been centered in front of
+ # its master before.
+ self.isCentered = 0
+
+ # Modal dialogs may need to block input from other application
+ # windows by setting a grab on them. oldGrabber is where the
+ # previous owner of the app grab (if any) is saved.
+ self.oldGrabber = None
+
+ # In case somebody manages to blow away self by underhanded
+ # means, stop waiting for waitVar.
+ self.top.protocol("WM_DELETE_WINDOW", self.deleteWindow)
+
+ # A dialog has a result of arbitrary type.
+ # The result should be considered valid after the dialog is
+ # dismissed.
+ # Modal dialogs can use this to return arbitrary
+ # descriptions of what the user did. For example, a file-
+ # selection dialog might return either the pathname specified
+ # by the user or, if the dialog was cancelled, None.
+ self.result = None
+
+ ##################################################################
+ # Default response to an attempt to delete the window: ignore it.
+ ##################################################################
+ def deleteWindow(self):
+ return
+
+ ##################################################################
+ # Show a dialog.
+ ##################################################################
+ def show(self):
+ if self.isShowing:
+ # Dialog is already visible. Raise it to the top.
+ self.top.tkraise()
+ return
+ # Give us a chance to initialize contents. Other program
+ # state may have changed while the dialog was unmapped.
+ self.initControls()
+
+ self.center()
+ # Remember the old grab owner, for modal subclasses.
+ self.oldGrabber = self.top.grab_current()
+
+ self.top.deiconify()
+
+ # Here's where modal subclasses get a chance to set a grab.
+ self.top.wait_visibility()
+ self.modalGrab()
+
+ self.top.focus()
+ # Old results are invalid, now.
+ self.result = None
+ self.isShowing = 1
+
+ ##################################################################
+ # Initialize the contents of the dialog, e.g. put default values
+ # into text fields.
+ ##################################################################
+ def initControls(self):
+ return
+
+ ##################################################################
+ # Center the dialog in front of its master, if possible.
+ ##################################################################
+ def center(self):
+ if self.isCentered:
+ # Center the dialog only the first time it is displayed.
+ # After that, just pop it up where the user left it.
+ return
+
+ # Try to ensure any geometry requests have been processed.
+ self.top.update_idletasks()
+
+ w = self.master.winfo_width()
+ h = self.master.winfo_height()
+ x = self.master.winfo_rootx()
+ y = self.master.winfo_rooty()
+ reqw = self.top.winfo_reqwidth()
+ reqh = self.top.winfo_reqheight()
+
+ centerx = `x + (w - reqw)/2`
+ centery = `y + (h - reqh)/2`
+ geomStr = "+" + centerx + "+" + centery
+ self.top.geometry(geomStr)
+ self.isCentered = 1
+
+ ##################################################################
+ # This is a hook for modal dialog subclasses. It gives them a
+ # chance to do a grab_set on the dialog's Toplevel, to prevent
+ # input from being delivered to other application windows.
+ ##################################################################
+ def modalGrab(self):
+ return
+
+ ##################################################################
+ # This is another hook for modal dialog subclasses. It allows
+ # for the release of any grab set via modalGrab.
+ ##################################################################
+ def modalReleaseGrab(self):
+ return
+
+ ##################################################################
+ # Terminate the dialog, returning its result.
+ ##################################################################
+ def terminate(self):
+ # Set the wait variable, so modal dialogs will know they're
+ # done.
+ self.top.setvar(self.waitVar, 1)
+ self.top.withdraw()
+ self.isShowing = 0
+
+ # Here's where modal subclasses get a chance to release any
+ # grab they may have taken.
+ self.modalReleaseGrab()
+
+ self.master.focus()
+ return self.result
+
+ ##################################################################
+ # Destroy the visuals.
+ ##################################################################
+ def destroy(self):
+ self.top.destroy()
+
+
+######################################################################
+# This class represents an application-modal dialog.
+######################################################################
+class Modal(NonModal):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master):
+ apply(NonModal.__init__, (self, master))
+
+ # Make self a transient for its master. This basically forces
+ # self to remain in front of its master, and not to be
+ # separately iconifiable. (Depends on the window manager?)
+ #
+ # Note this isn't done for non-modal dialogs. Sorry,
+ # OpenWindows fans, but I can't stand having to slide
+ # non-modal dialogs out of the way just so I can see the
+ # master window...
+ makeTransientFor(self.top, master)
+
+ # If somebody had an application-wide pointer grab,
+ # remember them here. When the dialog is dismissed, the
+ # grab will be returned to the previous owner.
+ self.oldGrabber = None
+
+ ##################################################################
+ # Show a modal dialog.
+ ##################################################################
+ def show(self):
+ apply(NonModal.show, (self,))
+ self.top.waitvar(self.waitVar)
+ return self.result
+
+ ##################################################################
+ # Put an application-wide grab into effect, to block input from
+ # other application windows.
+ ##################################################################
+ def modalGrab(self):
+ # Display a do-not-enter cursor in front of all other toplevels
+ # in the application.
+ self.top.pushOtherCursors(Cursors.noEnterCursor)
+ self.top.grab_set()
+
+ ##################################################################
+ # Release the grab obtained via modalGrab().
+ ##################################################################
+ def modalReleaseGrab(self):
+ self.top.grab_release()
+ # If somebody else owned the grab before we did, give it
+ # back to them.
+ if self.oldGrabber and (self.oldGrabber != self.top):
+ self.oldGrabber.grab_set()
+
+ self.top.popOtherCursors()
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ global f, nonmodal, modal
+
+ # Display a non-modal dialog.
+ f = Main()
+ nonmodal = None
+ modal = None
+
+ def showNonModal(event=None):
+ global f, nonmodal
+
+ if not nonmodal:
+ nonmodal = NonModal(f)
+ label = Tk.Label(nonmodal.top,
+ text="This is a non-modal dialog.")
+ label.pack()
+
+ def quitCB(event=None, dlg=nonmodal):
+ result = dlg.terminate()
+ print "Dialog terminated with result", `result`
+ btn = Tk.Button(nonmodal.top, text="OK", command=quitCB)
+ btn.pack()
+
+ nonmodal.show()
+
+ def showModal(event=None):
+ global f, modal
+
+ if not modal:
+ modal = Modal(f)
+ label = Tk.Label(modal.top, text="This is a modal dialog.")
+ label.pack()
+
+ def quitCB(event=None, dlg=modal):
+ result = dlg.terminate()
+ print "Dialog terminated with result", `result`
+ btn = Tk.Button(modal.top, text="OK", command=quitCB)
+ btn.pack()
+
+ modal.show()
+
+ for text, command in [("NonModal", showNonModal),
+ ("Modal", showModal),
+ ("Quit", f.quit)]:
+ b = Tk.Button(f, text=text, width=10, command=command)
+ b.pack()
+
+ f.pack()
+ f.mainloop()
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/StdDialog.py b/uitools/StdDialog.py
new file mode 100644
index 0000000..3e19e12
--- /dev/null
+++ b/uitools/StdDialog.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python
+######################################################################
+# This module provides some standard dialog classes.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: StdDialog.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+import Shells, ButtonBox
+
+######################################################################
+# This class creates basic dialog contents: a frame and
+# a btnBox.
+######################################################################
+class Controls:
+ ##################################################################
+ # Init a new instance. master must be a Shells.NonModal
+ # subclass.
+ ##################################################################
+ def __init__(self, master, okCB=None, cancelCB=None, helpCB=None):
+ self.master = master
+
+ # Subclasses should put their custom controls inside the
+ # frame.
+ self.frame = Tk.Frame(master.top, relief='raised', bd=1)
+ self.frame.pack(side='top', fill='both', expand='yes')
+
+ self.btnBox = ButtonBox.T(master.top)
+ self.btnBox.frame.pack()
+
+
+######################################################################
+# This class creates "standard" dialog contents and some std buttons.
+######################################################################
+class StdControls(Controls):
+ ##################################################################
+ # Init a new instance. master must be a Shells.NonModal
+ # subclass.
+ ##################################################################
+ def __init__(self, master, okCB=None, cancelCB=None, helpCB=None):
+ apply(Controls.__init__, (self, master))
+
+ self.btnBox.addButtons(["OK", okCB],
+ ["Cancel", cancelCB],
+ ["Help", helpCB])
+
+######################################################################
+# This is a non-modal dialog with the standard dialog buttons.
+######################################################################
+class NonModal(Shells.NonModal):
+ ##################################################################
+ # Init a new instance.
+ # The optional arguments are lists or tuples, each specifying the
+ # name and (optionally) command for a new dialog button.
+ ##################################################################
+ def __init__(self, master, *args):
+ Shells.NonModal.__init__(self, master)
+ self.controls = StdControls(self, self.okCB, self.cancelCB,
+ self.helpCB)
+
+ ##################################################################
+ # Define callbacks for the standard buttons, for subclasses.
+ ##################################################################
+ def okCB(self, event=None):
+ self.terminate()
+ return self.result
+
+ def cancelCB(self, event=None):
+ self.terminate()
+ return self.result
+
+ def helpCB(self, event=None):
+ return
+
+######################################################################
+# This is a modal dialog with the standard set of dialog buttons
+# arrayed across the bottom.
+######################################################################
+class Modal(Shells.Modal):
+ ##################################################################
+ # Initialize a new instance.
+ # The optional arguments are lists or tuples, each specifying the
+ # name and (optionally) command for a new dialog button.
+ ##################################################################
+ def __init__(self, master, *args):
+ Shells.Modal.__init__(self, master)
+ self.controls = StdControls(self, self.okCB, self.cancelCB,
+ self.helpCB)
+
+ ##################################################################
+ # Define callbacks for the standard buttons, for subclasses.
+ ##################################################################
+ def okCB(self, event=None):
+ self.terminate()
+ return self.result
+
+ def cancelCB(self, event=None):
+ self.terminate()
+ return self.result
+
+ def helpCB(self, event=None):
+ return
+
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ f = Shells.Main()
+ f.pack()
+
+ quitBtn = Tk.Button(f, text="Quit", command=f.quit)
+ quitBtn.pack()
+
+ d = Modal(f)
+ Tk.Label(d.controls.frame, text="Press a button").pack()
+
+ result = d.show()
+ print "Modal Result =", result
+ d.destroy()
+ d = NonModal(f)
+ Tk.Label(d.controls.frame,
+ text="Press a button\nwhenever you're ready").pack()
+ d.show()
+ f.mainloop()
+
+if __name__ == "__main__":
+ main()
diff --git a/uitools/TkDlgWrapper.py b/uitools/TkDlgWrapper.py
new file mode 100644
index 0000000..54b2236
--- /dev/null
+++ b/uitools/TkDlgWrapper.py
@@ -0,0 +1,268 @@
+#!/usr/bin/env python
+######################################################################
+# Try to access some of the newer Tk dialogs.
+# This module is inspired by the Python 1.[34] Dialog.py module.
+#
+# Mitch Chapman
+#---------------------------------------------------------------------
+# $Log: TkDlgWrapper.py,v $
+# Revision 1.1 1996/12/01 22:58:54 mchapman
+# Initial revision
+#
+######################################################################
+
+__version__ = "$Revision: 1.1 $"
+
+import Tkinter; Tk=Tkinter
+import os, copy
+
+VersionError = "VersionError"
+
+if Tk.TkVersion < 4.2:
+ raise VersionError, "This module requires Tk 4.2 or greater."
+
+######################################################################
+# This is an abstract wrapper for all classes of built-in Tk dialogs.
+# It's subclassed from Tk.Widget to gain access to widget
+# configuration methods.
+######################################################################
+class T(Tk.Widget):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, widgetName, master=None, **kw):
+ self.widgetName = "__%s__" % widgetName
+ # The unadorned widgetName is the Tk command/proc which
+ # displays the dialog, e.g. tk_chooseColor
+ self.tkCommand = widgetName
+ self.master = master
+ # Defer the actual widget initialization. I want instances
+ # of T to persist even when they are not visible. For
+ # example, this allows a file selection dialog to remember
+ # what file extension it last selected. But in order to allow
+ # persistence, the creation of the underlying Tk dialog must
+ # be delayed.
+
+ # Make a copy of the configuration, so it can persist. This
+ # is where self remembers things such as the last file type
+ # selected, last color used, etc.
+ self.kw = copy.copy(kw)
+
+ ##################################################################
+ # Show the dialog.
+ ##################################################################
+ def show(self, **kw):
+ # Use the configuration options to override the current config --
+ # and remember them for later.
+ for k, v in kw.items():
+ self.kw[k] = v
+ Tk.Widget._setup(self, self.master, self.kw)
+ resultStr = apply(self.tk.call, (self.tkCommand,) +
+ self._options(self.kw))
+ try:
+ # Don't leak Python widget descriptors?
+ Tk.Widget.destroy(self)
+ except Tk.TclError: pass
+
+ return resultStr
+
+ ##################################################################
+ # Explicitly destroy a widget instance. Nothing to do, here,
+ # because the widget is gone as soon as show() returns.
+ ##################################################################
+ def destroy(self):
+ pass
+
+
+######################################################################
+# Abstract wrapper for the Tk 4.2 tk_get\(Open\|Save\)File dialog.
+# Tries to remember the last-used directory, so it can be used as
+# the initial directory next time the dialog is displayed.
+######################################################################
+class OpenSaveFile(T):
+ ##################################################################
+ # Initialize a new instance. whichDlg tells whether this is
+ # a tk_getOpenFile or a tk_getSaveFile, etc.
+ ##################################################################
+ def __init__(self, whichDlg, master=None, **kw):
+ apply(T.__init__, (self, whichDlg, master), kw)
+ self.filename = None
+
+ ##################################################################
+ # Show the dialog and return either the selected file or "".
+ ##################################################################
+ def show(self, **kw):
+ self.filename = apply(T.show, (self,), kw)
+ # Try to remember a few configuration items for the next time
+ # the dialog is displayed.
+ if self.filename:
+ dirname, basename = os.path.split(self.filename)
+
+ # Next time, start in the directory we ended in this time.
+ self.kw['initialdir'] = dirname
+
+ # Try to figure out what extension to use next time.
+ ext = os.path.splitext(basename)[1]
+ self.kw['defaultextension'] = "%s" % ext
+
+ # Try to specify the default name to use next time.
+ self.kw['initialfile'] = basename
+
+ # The Tk dialogs are a little stupid: Even if you specify
+ # an initial file with extension ".txt", the dialog will show
+ # only files matching the first set of extensions in the
+ # -filetypes option.
+ # My cheap workaround is to create a new first entry for
+ # -filetypes which matches the extension selected this
+ # time.
+ oldtypes = ()
+ if self.kw.has_key('filetypes'):
+ oldtypes = self.kw['filetypes']
+ # The following works only for Unix (and maybe Windows).
+ # Somebody wanna fix this for handling Mac file types?
+ newtypes = ()
+ dfltExt = ext or "*"
+ dfltDescription = "(Last Selected)"
+ for typespec in oldtypes:
+ description, types = typespec
+ if description == dfltDescription:
+ newtypes = ((dfltDescription, dfltExt),) + oldtypes[1:]
+ break # EXIT FOR LOOP
+ else:
+ newtypes = ((dfltDescription, dfltExt),) + oldtypes
+
+ self.kw['filetypes'] = newtypes
+
+ # The Tk dialogs are a little smart: If you create both an
+ # open and a save dialog in the same application, they seem
+ # to both slave to the same working directory. Change to dir
+ # "/blah" when opening? Then when you try to save, the
+ # default displayed directory will be "/blah".
+ # This may be a bug, given what the man pages say, but
+ # I like it.
+ return self.filename
+
+######################################################################
+# Wrapper for the tk_getOpenFile dialog.
+######################################################################
+class OpenFile(OpenSaveFile):
+ def __init__(self, master=None, **kw):
+ apply(OpenSaveFile.__init__, (self, "tk_getOpenFile", master), kw)
+
+
+######################################################################
+# Wrapper for the tk_getSaveFile dialog.
+######################################################################
+class SaveFile(OpenSaveFile):
+ def __init__(self, master=None, **kw):
+ apply(OpenSaveFile.__init__, (self, "tk_getSaveFile", master), kw)
+
+
+######################################################################
+# Wrapper for the tk_chooseColor dialog.
+######################################################################
+class ChooseColor(T):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None, **kw):
+ apply(T.__init__, (self, "tk_chooseColor", master), kw)
+
+
+######################################################################
+# Wrapper for the tk_messageBox dialog.
+######################################################################
+class MessageBox(T):
+ ##################################################################
+ # Initialize a new instance.
+ ##################################################################
+ def __init__(self, master=None, defaultCfg=None, **kw):
+ cfg = Tk._cnfmerge((defaultCfg or {}, kw))
+ apply(T.__init__, (self, "tk_messageBox", master), cfg)
+
+
+######################################################################
+# These are the specific types of MessageBox dialogs provided by Tk.
+# For each of these classes, the return value of show() is a string
+# containing the label of the button which was pressed.
+#
+# Yeah, I know, these aren't particularly useful when you can roll
+# your own much more beautiful dialogs in Python. But hey! They're
+# here and they work, and they're supposed to have native look-n-feel
+# on every supported platform.
+######################################################################
+
+class AbortRetryIgnore(MessageBox):
+ def __init__(self, master=None, **kw):
+ apply(MessageBox.__init__,
+ (self, master, {'icon':'error', 'type':'abortretryignore'}), kw)
+
+class OK(MessageBox):
+ def __init__(self, master=None, **kw):
+ apply(MessageBox.__init__, (self, master, {'type':'ok'}), kw)
+
+class OKCancel(MessageBox):
+ def __init__(self, master=None, **kw):
+ apply(MessageBox.__init__, (self, master, {'type':'okcancel'}), kw)
+
+class RetryCancel(MessageBox):
+ def __init__(self, master=None, **kw):
+ apply(MessageBox.__init__,
+ (self, master, {'icon':'error', 'type':'retrycancel'}), kw)
+
+class YesNo(MessageBox):
+ def __init__(self, master=None, **kw):
+ apply(MessageBox.__init__,
+ (self, master, {'icon':'question', 'type':'yesno'}), kw)
+
+class YesNoCancel(MessageBox):
+ def __init__(self, master=None, **kw):
+ apply(MessageBox.__init__,
+ (self, master, {'icon':'question', 'type':'yesnocancel'}), kw)
+
+
+######################################################################
+# Don't wrap tk_dialog -- that's already done in Dialog.py.
+######################################################################
+
+######################################################################
+# Main function for unit testing.
+######################################################################
+def main():
+ # Demo all of the message box dialog classes:
+ for c in [AbortRetryIgnore, OK, OKCancel,
+ RetryCancel, YesNo, YesNoCancel]:
+ d = c(title=c.__name__)
+ print d.show(message="This is an instance of %s." % c.__name__)
+
+ # Demo the color chooser dialog:
+ d = ChooseColor(title="PickaKuhla", initialcolor="SlateGray")
+ color = d.show()
+ print "Selected color is", `color`
+
+ # Demo the open file dialog.
+ d = OpenFile(defaultextension=".py",
+ filetypes=(("Python", ".py"),
+ ("Emacs Lisp", ".el"),
+ ("Text", ".txt"),
+ ("All Files", "*")))
+
+ # Run multiple times to demonstrate how the dialog remembers the
+ # last working directory, file extension, etc.
+ for i in range(2):
+ filename = d.show()
+ print "Open file", `d.filename`
+
+ print 72 * "_"
+ print "Current working directory is", os.getcwd()
+ d = SaveFile(defaultextension=".py",
+ filetypes=(("Pithon [er, Python]", ".py"),
+ ("Text", ".txt"),
+ ("All Files", "*")))
+ for i in range(2):
+ filename = d.show()
+ print "Save File As", `d.filename`
+
+
+if __name__ == "__main__":
+ main()