summaryrefslogtreecommitdiff
path: root/uitools/Shells.py
blob: 623deeac8bad5d7e832de17b202d5999de2d5528 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
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()