blob: 96b8b90d21518154fc79ff366e9d9df3ea14d8a7 [file] [log] [blame]
Georg Brandl2e081362008-05-17 19:04:04 +00001#
2# An Introduction to Tkinter
3#
4# Copyright (c) 1997 by Fredrik Lundh
5#
6# This copyright applies to Dialog, askinteger, askfloat and asktring
7#
8# fredrik@pythonware.com
9# http://www.pythonware.com
10#
11"""This modules handles dialog boxes.
Guido van Rossum990e6191996-06-17 17:14:46 +000012
Georg Brandl2e081362008-05-17 19:04:04 +000013It contains the following public symbols:
14
15SimpleDialog -- A simple but flexible modal dialog box
16
17Dialog -- a base class for dialogs
18
19askinteger -- get an integer from the user
20
21askfloat -- get a float from the user
22
23askstring -- get a string from the user
24"""
Guido van Rossum990e6191996-06-17 17:14:46 +000025
Georg Brandl14fc4272008-05-17 18:39:55 +000026from tkinter import *
Georg Brandl2e081362008-05-17 19:04:04 +000027from tkinter import messagebox
Guido van Rossum990e6191996-06-17 17:14:46 +000028
Georg Brandl2e081362008-05-17 19:04:04 +000029import tkinter # used at _QueryDialog for tkinter._default_root
Guido van Rossum990e6191996-06-17 17:14:46 +000030
31class SimpleDialog:
32
33 def __init__(self, master,
Guido van Rossumc4570481998-03-20 20:45:49 +000034 text='', buttons=[], default=None, cancel=None,
35 title=None, class_=None):
36 if class_:
37 self.root = Toplevel(master, class_=class_)
38 else:
39 self.root = Toplevel(master)
40 if title:
41 self.root.title(title)
42 self.root.iconname(title)
43 self.message = Message(self.root, text=text, aspect=400)
44 self.message.pack(expand=1, fill=BOTH)
45 self.frame = Frame(self.root)
46 self.frame.pack()
47 self.num = default
48 self.cancel = cancel
49 self.default = default
50 self.root.bind('<Return>', self.return_event)
51 for num in range(len(buttons)):
52 s = buttons[num]
53 b = Button(self.frame, text=s,
54 command=(lambda self=self, num=num: self.done(num)))
55 if num == default:
56 b.config(relief=RIDGE, borderwidth=8)
57 b.pack(side=LEFT, fill=BOTH, expand=1)
58 self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
59 self._set_transient(master)
Guido van Rossum17f2b2d1996-07-21 02:20:06 +000060
61 def _set_transient(self, master, relx=0.5, rely=0.3):
Guido van Rossumc4570481998-03-20 20:45:49 +000062 widget = self.root
63 widget.withdraw() # Remain invisible while we figure out the geometry
64 widget.transient(master)
65 widget.update_idletasks() # Actualize geometry information
66 if master.winfo_ismapped():
67 m_width = master.winfo_width()
68 m_height = master.winfo_height()
69 m_x = master.winfo_rootx()
70 m_y = master.winfo_rooty()
71 else:
72 m_width = master.winfo_screenwidth()
73 m_height = master.winfo_screenheight()
74 m_x = m_y = 0
75 w_width = widget.winfo_reqwidth()
76 w_height = widget.winfo_reqheight()
77 x = m_x + (m_width - w_width) * relx
78 y = m_y + (m_height - w_height) * rely
79 if x+w_width > master.winfo_screenwidth():
80 x = master.winfo_screenwidth() - w_width
81 elif x < 0:
82 x = 0
83 if y+w_height > master.winfo_screenheight():
84 y = master.winfo_screenheight() - w_height
85 elif y < 0:
86 y = 0
87 widget.geometry("+%d+%d" % (x, y))
88 widget.deiconify() # Become visible at the desired location
Guido van Rossum990e6191996-06-17 17:14:46 +000089
90 def go(self):
Martin v. Löwisb217cd82004-08-03 18:36:25 +000091 self.root.wait_visibility()
Guido van Rossumc4570481998-03-20 20:45:49 +000092 self.root.grab_set()
93 self.root.mainloop()
94 self.root.destroy()
95 return self.num
Guido van Rossum990e6191996-06-17 17:14:46 +000096
97 def return_event(self, event):
Guido van Rossumc4570481998-03-20 20:45:49 +000098 if self.default is None:
99 self.root.bell()
100 else:
101 self.done(self.default)
Guido van Rossum990e6191996-06-17 17:14:46 +0000102
103 def wm_delete_window(self):
Guido van Rossumc4570481998-03-20 20:45:49 +0000104 if self.cancel is None:
105 self.root.bell()
106 else:
107 self.done(self.cancel)
Guido van Rossum990e6191996-06-17 17:14:46 +0000108
109 def done(self, num):
Guido van Rossumc4570481998-03-20 20:45:49 +0000110 self.num = num
111 self.root.quit()
Guido van Rossum990e6191996-06-17 17:14:46 +0000112
113
Georg Brandl2e081362008-05-17 19:04:04 +0000114class Dialog(Toplevel):
115
116 '''Class to open dialogs.
117
118 This class is intended as a base class for custom dialogs
119 '''
120
121 def __init__(self, parent, title = None):
122
123 '''Initialize a dialog.
124
125 Arguments:
126
127 parent -- a parent window (the application window)
128
129 title -- the dialog title
130 '''
131 Toplevel.__init__(self, parent)
132
133 # If the master is not viewable, don't
134 # make the child transient, or else it
135 # would be opened withdrawn
136 if parent.winfo_viewable():
137 self.transient(parent)
138
139 if title:
140 self.title(title)
141
142 self.parent = parent
143
144 self.result = None
145
146 body = Frame(self)
147 self.initial_focus = self.body(body)
148 body.pack(padx=5, pady=5)
149
150 self.buttonbox()
151
152 self.wait_visibility() # window needs to be visible for the grab
153 self.grab_set()
154
155 if not self.initial_focus:
156 self.initial_focus = self
157
158 self.protocol("WM_DELETE_WINDOW", self.cancel)
159
160 if self.parent is not None:
161 self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
162 parent.winfo_rooty()+50))
163
164 self.initial_focus.focus_set()
165
166 self.wait_window(self)
167
168 def destroy(self):
169 '''Destroy the window'''
170 self.initial_focus = None
171 Toplevel.destroy(self)
172
173 #
174 # construction hooks
175
176 def body(self, master):
177 '''create dialog body.
178
179 return widget that should have initial focus.
180 This method should be overridden, and is called
181 by the __init__ method.
182 '''
183 pass
184
185 def buttonbox(self):
186 '''add standard button box.
187
188 override if you do not want the standard buttons
189 '''
190
191 box = Frame(self)
192
193 w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
194 w.pack(side=LEFT, padx=5, pady=5)
195 w = Button(box, text="Cancel", width=10, command=self.cancel)
196 w.pack(side=LEFT, padx=5, pady=5)
197
198 self.bind("<Return>", self.ok)
199 self.bind("<Escape>", self.cancel)
200
201 box.pack()
202
203 #
204 # standard button semantics
205
206 def ok(self, event=None):
207
208 if not self.validate():
209 self.initial_focus.focus_set() # put focus back
210 return
211
212 self.withdraw()
213 self.update_idletasks()
214
215 try:
216 self.apply()
217 finally:
218 self.cancel()
219
220 def cancel(self, event=None):
221
222 # put focus back to the parent window
223 if self.parent is not None:
224 self.parent.focus_set()
225 self.destroy()
226
227 #
228 # command hooks
229
230 def validate(self):
231 '''validate the data
232
233 This method is called automatically to validate the data before the
234 dialog is destroyed. By default, it always validates OK.
235 '''
236
237 return 1 # override
238
239 def apply(self):
240 '''process the data
241
242 This method is called automatically to process the data, *after*
243 the dialog is destroyed. By default, it does nothing.
244 '''
245
246 pass # override
247
248
249# --------------------------------------------------------------------
250# convenience dialogues
251
252class _QueryDialog(Dialog):
253
254 def __init__(self, title, prompt,
255 initialvalue=None,
256 minvalue = None, maxvalue = None,
257 parent = None):
258
259 if not parent:
260 parent = tkinter._default_root
261
262 self.prompt = prompt
263 self.minvalue = minvalue
264 self.maxvalue = maxvalue
265
266 self.initialvalue = initialvalue
267
268 Dialog.__init__(self, parent, title)
269
270 def destroy(self):
271 self.entry = None
272 Dialog.destroy(self)
273
274 def body(self, master):
275
276 w = Label(master, text=self.prompt, justify=LEFT)
277 w.grid(row=0, padx=5, sticky=W)
278
279 self.entry = Entry(master, name="entry")
280 self.entry.grid(row=1, padx=5, sticky=W+E)
281
282 if self.initialvalue:
283 self.entry.insert(0, self.initialvalue)
284 self.entry.select_range(0, END)
285
286 return self.entry
287
288 def validate(self):
289 try:
290 result = self.getresult()
291 except ValueError:
292 messagebox.showwarning(
293 "Illegal value",
294 self.errormessage + "\nPlease try again",
295 parent = self
296 )
297 return 0
298
299 if self.minvalue is not None and result < self.minvalue:
300 messagebox.showwarning(
301 "Too small",
302 "The allowed minimum value is %s. "
303 "Please try again." % self.minvalue,
304 parent = self
305 )
306 return 0
307
308 if self.maxvalue is not None and result > self.maxvalue:
309 messagebox.showwarning(
310 "Too large",
311 "The allowed maximum value is %s. "
312 "Please try again." % self.maxvalue,
313 parent = self
314 )
315 return 0
316
317 self.result = result
318
319 return 1
320
321
322class _QueryInteger(_QueryDialog):
323 errormessage = "Not an integer."
324 def getresult(self):
325 return int(self.entry.get())
326
327def askinteger(title, prompt, **kw):
328 '''get an integer from the user
329
330 Arguments:
331
332 title -- the dialog title
333 prompt -- the label text
334 **kw -- see SimpleDialog class
335
336 Return value is an integer
337 '''
338 d = _QueryInteger(title, prompt, **kw)
339 return d.result
340
341class _QueryFloat(_QueryDialog):
342 errormessage = "Not a floating point value."
343 def getresult(self):
344 return float(self.entry.get())
345
346def askfloat(title, prompt, **kw):
347 '''get a float from the user
348
349 Arguments:
350
351 title -- the dialog title
352 prompt -- the label text
353 **kw -- see SimpleDialog class
354
355 Return value is a float
356 '''
357 d = _QueryFloat(title, prompt, **kw)
358 return d.result
359
360class _QueryString(_QueryDialog):
361 def __init__(self, *args, **kw):
362 if "show" in kw:
363 self.__show = kw["show"]
364 del kw["show"]
365 else:
366 self.__show = None
367 _QueryDialog.__init__(self, *args, **kw)
368
369 def body(self, master):
370 entry = _QueryDialog.body(self, master)
371 if self.__show is not None:
372 entry.configure(show=self.__show)
373 return entry
374
375 def getresult(self):
376 return self.entry.get()
377
378def askstring(title, prompt, **kw):
379 '''get a string from the user
380
381 Arguments:
382
383 title -- the dialog title
384 prompt -- the label text
385 **kw -- see SimpleDialog class
386
387 Return value is a string
388 '''
389 d = _QueryString(title, prompt, **kw)
390 return d.result
391
392
393
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000394if __name__ == '__main__':
395
396 def test():
397 root = Tk()
398 def doit(root=root):
399 d = SimpleDialog(root,
Guido van Rossumc4570481998-03-20 20:45:49 +0000400 text="This is a test dialog. "
401 "Would this have been an actual dialog, "
402 "the buttons below would have been glowing "
403 "in soft pink light.\n"
404 "Do you believe this?",
405 buttons=["Yes", "No", "Cancel"],
406 default=0,
407 cancel=2,
408 title="Test Dialog")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000409 print(d.go())
Georg Brandl2e081362008-05-17 19:04:04 +0000410 print(askinteger("Spam", "Egg count", initialvalue=12*12))
411 print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1,
412 maxvalue=100))
413 print(askstring("Spam", "Egg label"))
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000414 t = Button(root, text='Test', command=doit)
415 t.pack()
416 q = Button(root, text='Quit', command=t.quit)
417 q.pack()
418 t.mainloop()
Guido van Rossum990e6191996-06-17 17:14:46 +0000419
Guido van Rossum990e6191996-06-17 17:14:46 +0000420 test()