#!/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()