From 2828885db093be86ef5b2c58f5c05ac3c4ed3664 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 2 Nov 2019 10:54:13 +0200 Subject: Import historical files --- uitools/Alerts.py | 206 +++++++++++++++++++++++++++++++ uitools/ButtonBox.py | 77 ++++++++++++ uitools/Cursors.py | 104 ++++++++++++++++ uitools/KWDict.py | 35 ++++++ uitools/LabelEntry.py | 154 +++++++++++++++++++++++ uitools/Menu.py | 245 +++++++++++++++++++++++++++++++++++++ uitools/ProgressBar.py | 70 +++++++++++ uitools/README | 186 ++++++++++++++++++++++++++++ uitools/Scrolled.py | 184 ++++++++++++++++++++++++++++ uitools/Shells.py | 315 ++++++++++++++++++++++++++++++++++++++++++++++++ uitools/StdDialog.py | 136 +++++++++++++++++++++ uitools/TkDlgWrapper.py | 268 ++++++++++++++++++++++++++++++++++++++++ 12 files changed, 1980 insertions(+) create mode 100644 uitools/Alerts.py create mode 100644 uitools/ButtonBox.py create mode 100644 uitools/Cursors.py create mode 100644 uitools/KWDict.py create mode 100644 uitools/LabelEntry.py create mode 100644 uitools/Menu.py create mode 100644 uitools/ProgressBar.py create mode 100644 uitools/README create mode 100644 uitools/Scrolled.py create mode 100644 uitools/Shells.py create mode 100644 uitools/StdDialog.py create mode 100644 uitools/TkDlgWrapper.py (limited to 'uitools') 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 " rather than "from 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() -- cgit v1.2.1