blob: 45302b4569b1deeb5ed4c2f9bc71ddee62e94c1c [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
Guilherme Polof45b4cc2009-03-07 02:19:14 +0000133 self.withdraw() # remain invisible for now
Georg Brandl2e081362008-05-17 19:04:04 +0000134 # If the master is not viewable, don't
135 # make the child transient, or else it
136 # would be opened withdrawn
137 if parent.winfo_viewable():
138 self.transient(parent)
139
140 if title:
141 self.title(title)
142
143 self.parent = parent
144
145 self.result = None
146
147 body = Frame(self)
148 self.initial_focus = self.body(body)
149 body.pack(padx=5, pady=5)
150
151 self.buttonbox()
152
Georg Brandl2e081362008-05-17 19:04:04 +0000153 if not self.initial_focus:
154 self.initial_focus = self
155
156 self.protocol("WM_DELETE_WINDOW", self.cancel)
157
158 if self.parent is not None:
159 self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
160 parent.winfo_rooty()+50))
161
Guilherme Polof45b4cc2009-03-07 02:19:14 +0000162 self.deiconify() # become visible now
163
Georg Brandl2e081362008-05-17 19:04:04 +0000164 self.initial_focus.focus_set()
165
Guilherme Polof45b4cc2009-03-07 02:19:14 +0000166 # wait for window to appear on screen before calling grab_set
167 self.wait_visibility()
168 self.grab_set()
Georg Brandl2e081362008-05-17 19:04:04 +0000169 self.wait_window(self)
170
171 def destroy(self):
172 '''Destroy the window'''
173 self.initial_focus = None
174 Toplevel.destroy(self)
175
176 #
177 # construction hooks
178
179 def body(self, master):
180 '''create dialog body.
181
182 return widget that should have initial focus.
183 This method should be overridden, and is called
184 by the __init__ method.
185 '''
186 pass
187
188 def buttonbox(self):
189 '''add standard button box.
190
191 override if you do not want the standard buttons
192 '''
193
194 box = Frame(self)
195
196 w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
197 w.pack(side=LEFT, padx=5, pady=5)
198 w = Button(box, text="Cancel", width=10, command=self.cancel)
199 w.pack(side=LEFT, padx=5, pady=5)
200
201 self.bind("<Return>", self.ok)
202 self.bind("<Escape>", self.cancel)
203
204 box.pack()
205
206 #
207 # standard button semantics
208
209 def ok(self, event=None):
210
211 if not self.validate():
212 self.initial_focus.focus_set() # put focus back
213 return
214
215 self.withdraw()
216 self.update_idletasks()
217
218 try:
219 self.apply()
220 finally:
221 self.cancel()
222
223 def cancel(self, event=None):
224
225 # put focus back to the parent window
226 if self.parent is not None:
227 self.parent.focus_set()
228 self.destroy()
229
230 #
231 # command hooks
232
233 def validate(self):
234 '''validate the data
235
236 This method is called automatically to validate the data before the
237 dialog is destroyed. By default, it always validates OK.
238 '''
239
240 return 1 # override
241
242 def apply(self):
243 '''process the data
244
245 This method is called automatically to process the data, *after*
246 the dialog is destroyed. By default, it does nothing.
247 '''
248
249 pass # override
250
251
252# --------------------------------------------------------------------
253# convenience dialogues
254
255class _QueryDialog(Dialog):
256
257 def __init__(self, title, prompt,
258 initialvalue=None,
259 minvalue = None, maxvalue = None,
260 parent = None):
261
262 if not parent:
263 parent = tkinter._default_root
264
265 self.prompt = prompt
266 self.minvalue = minvalue
267 self.maxvalue = maxvalue
268
269 self.initialvalue = initialvalue
270
271 Dialog.__init__(self, parent, title)
272
273 def destroy(self):
274 self.entry = None
275 Dialog.destroy(self)
276
277 def body(self, master):
278
279 w = Label(master, text=self.prompt, justify=LEFT)
280 w.grid(row=0, padx=5, sticky=W)
281
282 self.entry = Entry(master, name="entry")
283 self.entry.grid(row=1, padx=5, sticky=W+E)
284
Andrew Svetlov1fb0e3f2012-07-30 19:59:53 +0300285 if self.initialvalue is not None:
Georg Brandl2e081362008-05-17 19:04:04 +0000286 self.entry.insert(0, self.initialvalue)
287 self.entry.select_range(0, END)
288
289 return self.entry
290
291 def validate(self):
292 try:
293 result = self.getresult()
294 except ValueError:
295 messagebox.showwarning(
296 "Illegal value",
297 self.errormessage + "\nPlease try again",
298 parent = self
299 )
300 return 0
301
302 if self.minvalue is not None and result < self.minvalue:
303 messagebox.showwarning(
304 "Too small",
305 "The allowed minimum value is %s. "
306 "Please try again." % self.minvalue,
307 parent = self
308 )
309 return 0
310
311 if self.maxvalue is not None and result > self.maxvalue:
312 messagebox.showwarning(
313 "Too large",
314 "The allowed maximum value is %s. "
315 "Please try again." % self.maxvalue,
316 parent = self
317 )
318 return 0
319
320 self.result = result
321
322 return 1
323
324
325class _QueryInteger(_QueryDialog):
326 errormessage = "Not an integer."
327 def getresult(self):
328 return int(self.entry.get())
329
330def askinteger(title, prompt, **kw):
331 '''get an integer from the user
332
333 Arguments:
334
335 title -- the dialog title
336 prompt -- the label text
337 **kw -- see SimpleDialog class
338
339 Return value is an integer
340 '''
341 d = _QueryInteger(title, prompt, **kw)
342 return d.result
343
344class _QueryFloat(_QueryDialog):
345 errormessage = "Not a floating point value."
346 def getresult(self):
347 return float(self.entry.get())
348
349def askfloat(title, prompt, **kw):
350 '''get a float from the user
351
352 Arguments:
353
354 title -- the dialog title
355 prompt -- the label text
356 **kw -- see SimpleDialog class
357
358 Return value is a float
359 '''
360 d = _QueryFloat(title, prompt, **kw)
361 return d.result
362
363class _QueryString(_QueryDialog):
364 def __init__(self, *args, **kw):
365 if "show" in kw:
366 self.__show = kw["show"]
367 del kw["show"]
368 else:
369 self.__show = None
370 _QueryDialog.__init__(self, *args, **kw)
371
372 def body(self, master):
373 entry = _QueryDialog.body(self, master)
374 if self.__show is not None:
375 entry.configure(show=self.__show)
376 return entry
377
378 def getresult(self):
379 return self.entry.get()
380
381def askstring(title, prompt, **kw):
382 '''get a string from the user
383
384 Arguments:
385
386 title -- the dialog title
387 prompt -- the label text
388 **kw -- see SimpleDialog class
389
390 Return value is a string
391 '''
392 d = _QueryString(title, prompt, **kw)
393 return d.result
394
395
396
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000397if __name__ == '__main__':
398
399 def test():
400 root = Tk()
401 def doit(root=root):
402 d = SimpleDialog(root,
Guido van Rossumc4570481998-03-20 20:45:49 +0000403 text="This is a test dialog. "
404 "Would this have been an actual dialog, "
405 "the buttons below would have been glowing "
406 "in soft pink light.\n"
407 "Do you believe this?",
408 buttons=["Yes", "No", "Cancel"],
409 default=0,
410 cancel=2,
411 title="Test Dialog")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000412 print(d.go())
Georg Brandl2e081362008-05-17 19:04:04 +0000413 print(askinteger("Spam", "Egg count", initialvalue=12*12))
414 print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1,
415 maxvalue=100))
416 print(askstring("Spam", "Egg label"))
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000417 t = Button(root, text='Test', command=doit)
418 t.pack()
419 q = Button(root, text='Quit', command=t.quit)
420 q.pack()
421 t.mainloop()
Guido van Rossum990e6191996-06-17 17:14:46 +0000422
Guido van Rossum990e6191996-06-17 17:14:46 +0000423 test()