blob: d9762b1351a24a34dd3f845ca8bf7830b372c476 [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 *
Serhiy Storchaka3d569fd2020-12-19 12:17:08 +020027from tkinter import messagebox, _get_default_root
Guido van Rossum990e6191996-06-17 17:14:46 +000028
Serhiy Storchakadc0d5712018-10-12 19:01:00 +030029
Guido van Rossum990e6191996-06-17 17:14:46 +000030class SimpleDialog:
31
32 def __init__(self, master,
Guido van Rossumc4570481998-03-20 20:45:49 +000033 text='', buttons=[], default=None, cancel=None,
34 title=None, class_=None):
35 if class_:
36 self.root = Toplevel(master, class_=class_)
37 else:
38 self.root = Toplevel(master)
39 if title:
40 self.root.title(title)
41 self.root.iconname(title)
42 self.message = Message(self.root, text=text, aspect=400)
43 self.message.pack(expand=1, fill=BOTH)
44 self.frame = Frame(self.root)
45 self.frame.pack()
46 self.num = default
47 self.cancel = cancel
48 self.default = default
49 self.root.bind('<Return>', self.return_event)
50 for num in range(len(buttons)):
51 s = buttons[num]
52 b = Button(self.frame, text=s,
53 command=(lambda self=self, num=num: self.done(num)))
54 if num == default:
55 b.config(relief=RIDGE, borderwidth=8)
56 b.pack(side=LEFT, fill=BOTH, expand=1)
57 self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
Serhiy Storchakac6c43b22020-12-24 20:26:28 +020058 self.root.transient(master)
59 _place_window(self.root, master)
Guido van Rossum990e6191996-06-17 17:14:46 +000060
61 def go(self):
Martin v. Löwisb217cd82004-08-03 18:36:25 +000062 self.root.wait_visibility()
Guido van Rossumc4570481998-03-20 20:45:49 +000063 self.root.grab_set()
64 self.root.mainloop()
65 self.root.destroy()
66 return self.num
Guido van Rossum990e6191996-06-17 17:14:46 +000067
68 def return_event(self, event):
Guido van Rossumc4570481998-03-20 20:45:49 +000069 if self.default is None:
70 self.root.bell()
71 else:
72 self.done(self.default)
Guido van Rossum990e6191996-06-17 17:14:46 +000073
74 def wm_delete_window(self):
Guido van Rossumc4570481998-03-20 20:45:49 +000075 if self.cancel is None:
76 self.root.bell()
77 else:
78 self.done(self.cancel)
Guido van Rossum990e6191996-06-17 17:14:46 +000079
80 def done(self, num):
Guido van Rossumc4570481998-03-20 20:45:49 +000081 self.num = num
82 self.root.quit()
Guido van Rossum990e6191996-06-17 17:14:46 +000083
84
Georg Brandl2e081362008-05-17 19:04:04 +000085class Dialog(Toplevel):
86
87 '''Class to open dialogs.
88
89 This class is intended as a base class for custom dialogs
90 '''
91
92 def __init__(self, parent, title = None):
Georg Brandl2e081362008-05-17 19:04:04 +000093 '''Initialize a dialog.
94
95 Arguments:
96
97 parent -- a parent window (the application window)
98
99 title -- the dialog title
100 '''
Serhiy Storchaka3d569fd2020-12-19 12:17:08 +0200101 master = parent
Serhiy Storchakabb70b2a2020-12-25 17:04:26 +0200102 if master is None:
Serhiy Storchaka3d569fd2020-12-19 12:17:08 +0200103 master = _get_default_root('create dialog window')
104
105 Toplevel.__init__(self, master)
Georg Brandl2e081362008-05-17 19:04:04 +0000106
Guilherme Polof45b4cc2009-03-07 02:19:14 +0000107 self.withdraw() # remain invisible for now
Serhiy Storchaka3d569fd2020-12-19 12:17:08 +0200108 # If the parent is not viewable, don't
Georg Brandl2e081362008-05-17 19:04:04 +0000109 # make the child transient, or else it
110 # would be opened withdrawn
Serhiy Storchaka3d569fd2020-12-19 12:17:08 +0200111 if parent is not None and parent.winfo_viewable():
Georg Brandl2e081362008-05-17 19:04:04 +0000112 self.transient(parent)
113
114 if title:
115 self.title(title)
116
117 self.parent = parent
118
119 self.result = None
120
121 body = Frame(self)
122 self.initial_focus = self.body(body)
123 body.pack(padx=5, pady=5)
124
125 self.buttonbox()
126
Serhiy Storchakabb70b2a2020-12-25 17:04:26 +0200127 if self.initial_focus is None:
Georg Brandl2e081362008-05-17 19:04:04 +0000128 self.initial_focus = self
129
130 self.protocol("WM_DELETE_WINDOW", self.cancel)
131
Serhiy Storchakac6c43b22020-12-24 20:26:28 +0200132 _place_window(self, parent)
Guilherme Polof45b4cc2009-03-07 02:19:14 +0000133
Georg Brandl2e081362008-05-17 19:04:04 +0000134 self.initial_focus.focus_set()
135
Guilherme Polof45b4cc2009-03-07 02:19:14 +0000136 # wait for window to appear on screen before calling grab_set
137 self.wait_visibility()
138 self.grab_set()
Georg Brandl2e081362008-05-17 19:04:04 +0000139 self.wait_window(self)
140
141 def destroy(self):
142 '''Destroy the window'''
143 self.initial_focus = None
144 Toplevel.destroy(self)
145
146 #
147 # construction hooks
148
149 def body(self, master):
150 '''create dialog body.
151
152 return widget that should have initial focus.
153 This method should be overridden, and is called
154 by the __init__ method.
155 '''
156 pass
157
158 def buttonbox(self):
159 '''add standard button box.
160
161 override if you do not want the standard buttons
162 '''
163
164 box = Frame(self)
165
166 w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
167 w.pack(side=LEFT, padx=5, pady=5)
168 w = Button(box, text="Cancel", width=10, command=self.cancel)
169 w.pack(side=LEFT, padx=5, pady=5)
170
171 self.bind("<Return>", self.ok)
172 self.bind("<Escape>", self.cancel)
173
174 box.pack()
175
176 #
177 # standard button semantics
178
179 def ok(self, event=None):
180
181 if not self.validate():
182 self.initial_focus.focus_set() # put focus back
183 return
184
185 self.withdraw()
186 self.update_idletasks()
187
188 try:
189 self.apply()
190 finally:
191 self.cancel()
192
193 def cancel(self, event=None):
194
195 # put focus back to the parent window
196 if self.parent is not None:
197 self.parent.focus_set()
198 self.destroy()
199
200 #
201 # command hooks
202
203 def validate(self):
204 '''validate the data
205
206 This method is called automatically to validate the data before the
207 dialog is destroyed. By default, it always validates OK.
208 '''
209
210 return 1 # override
211
212 def apply(self):
213 '''process the data
214
215 This method is called automatically to process the data, *after*
216 the dialog is destroyed. By default, it does nothing.
217 '''
218
219 pass # override
220
221
Serhiy Storchakac6c43b22020-12-24 20:26:28 +0200222# Place a toplevel window at the center of parent or screen
223# It is a Python implementation of ::tk::PlaceWindow.
224def _place_window(w, parent=None):
225 w.wm_withdraw() # Remain invisible while we figure out the geometry
226 w.update_idletasks() # Actualize geometry information
227
228 minwidth = w.winfo_reqwidth()
229 minheight = w.winfo_reqheight()
230 maxwidth = w.winfo_vrootwidth()
231 maxheight = w.winfo_vrootheight()
232 if parent is not None and parent.winfo_ismapped():
233 x = parent.winfo_rootx() + (parent.winfo_width() - minwidth) // 2
234 y = parent.winfo_rooty() + (parent.winfo_height() - minheight) // 2
235 vrootx = w.winfo_vrootx()
236 vrooty = w.winfo_vrooty()
237 x = min(x, vrootx + maxwidth - minwidth)
238 x = max(x, vrootx)
239 y = min(y, vrooty + maxheight - minheight)
240 y = max(y, vrooty)
241 if w._windowingsystem == 'aqua':
242 # Avoid the native menu bar which sits on top of everything.
243 y = max(y, 22)
244 else:
245 x = (w.winfo_screenwidth() - minwidth) // 2
246 y = (w.winfo_screenheight() - minheight) // 2
247
248 w.wm_maxsize(maxwidth, maxheight)
249 w.wm_geometry('+%d+%d' % (x, y))
250 w.wm_deiconify() # Become visible at the desired location
251
252
Georg Brandl2e081362008-05-17 19:04:04 +0000253# --------------------------------------------------------------------
254# convenience dialogues
255
256class _QueryDialog(Dialog):
257
258 def __init__(self, title, prompt,
259 initialvalue=None,
260 minvalue = None, maxvalue = None,
261 parent = None):
262
Georg Brandl2e081362008-05-17 19:04:04 +0000263 self.prompt = prompt
264 self.minvalue = minvalue
265 self.maxvalue = maxvalue
266
267 self.initialvalue = initialvalue
268
269 Dialog.__init__(self, parent, title)
270
271 def destroy(self):
272 self.entry = None
273 Dialog.destroy(self)
274
275 def body(self, master):
276
277 w = Label(master, text=self.prompt, justify=LEFT)
278 w.grid(row=0, padx=5, sticky=W)
279
280 self.entry = Entry(master, name="entry")
281 self.entry.grid(row=1, padx=5, sticky=W+E)
282
Andrew Svetlov1fb0e3f2012-07-30 19:59:53 +0300283 if self.initialvalue is not None:
Georg Brandl2e081362008-05-17 19:04:04 +0000284 self.entry.insert(0, self.initialvalue)
285 self.entry.select_range(0, END)
286
287 return self.entry
288
289 def validate(self):
290 try:
291 result = self.getresult()
292 except ValueError:
293 messagebox.showwarning(
294 "Illegal value",
295 self.errormessage + "\nPlease try again",
296 parent = self
297 )
298 return 0
299
300 if self.minvalue is not None and result < self.minvalue:
301 messagebox.showwarning(
302 "Too small",
303 "The allowed minimum value is %s. "
304 "Please try again." % self.minvalue,
305 parent = self
306 )
307 return 0
308
309 if self.maxvalue is not None and result > self.maxvalue:
310 messagebox.showwarning(
311 "Too large",
312 "The allowed maximum value is %s. "
313 "Please try again." % self.maxvalue,
314 parent = self
315 )
316 return 0
317
318 self.result = result
319
320 return 1
321
322
323class _QueryInteger(_QueryDialog):
324 errormessage = "Not an integer."
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300325
Georg Brandl2e081362008-05-17 19:04:04 +0000326 def getresult(self):
Serhiy Storchaka645058d2015-05-06 14:00:04 +0300327 return self.getint(self.entry.get())
Georg Brandl2e081362008-05-17 19:04:04 +0000328
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300329
Georg Brandl2e081362008-05-17 19:04:04 +0000330def 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
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300344
Georg Brandl2e081362008-05-17 19:04:04 +0000345class _QueryFloat(_QueryDialog):
346 errormessage = "Not a floating point value."
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300347
Georg Brandl2e081362008-05-17 19:04:04 +0000348 def getresult(self):
Serhiy Storchaka645058d2015-05-06 14:00:04 +0300349 return self.getdouble(self.entry.get())
Georg Brandl2e081362008-05-17 19:04:04 +0000350
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300351
Georg Brandl2e081362008-05-17 19:04:04 +0000352def askfloat(title, prompt, **kw):
353 '''get a float from the user
354
355 Arguments:
356
357 title -- the dialog title
358 prompt -- the label text
359 **kw -- see SimpleDialog class
360
361 Return value is a float
362 '''
363 d = _QueryFloat(title, prompt, **kw)
364 return d.result
365
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300366
Georg Brandl2e081362008-05-17 19:04:04 +0000367class _QueryString(_QueryDialog):
368 def __init__(self, *args, **kw):
369 if "show" in kw:
370 self.__show = kw["show"]
371 del kw["show"]
372 else:
373 self.__show = None
374 _QueryDialog.__init__(self, *args, **kw)
375
376 def body(self, master):
377 entry = _QueryDialog.body(self, master)
378 if self.__show is not None:
379 entry.configure(show=self.__show)
380 return entry
381
382 def getresult(self):
383 return self.entry.get()
384
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300385
Georg Brandl2e081362008-05-17 19:04:04 +0000386def askstring(title, prompt, **kw):
387 '''get a string from the user
388
389 Arguments:
390
391 title -- the dialog title
392 prompt -- the label text
393 **kw -- see SimpleDialog class
394
395 Return value is a string
396 '''
397 d = _QueryString(title, prompt, **kw)
398 return d.result
399
400
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000401if __name__ == '__main__':
402
403 def test():
404 root = Tk()
405 def doit(root=root):
406 d = SimpleDialog(root,
Guido van Rossumc4570481998-03-20 20:45:49 +0000407 text="This is a test dialog. "
408 "Would this have been an actual dialog, "
409 "the buttons below would have been glowing "
410 "in soft pink light.\n"
411 "Do you believe this?",
412 buttons=["Yes", "No", "Cancel"],
413 default=0,
414 cancel=2,
415 title="Test Dialog")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000416 print(d.go())
Georg Brandl2e081362008-05-17 19:04:04 +0000417 print(askinteger("Spam", "Egg count", initialvalue=12*12))
418 print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1,
419 maxvalue=100))
420 print(askstring("Spam", "Egg label"))
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000421 t = Button(root, text='Test', command=doit)
422 t.pack()
423 q = Button(root, text='Quit', command=t.quit)
424 q.pack()
425 t.mainloop()
Guido van Rossum990e6191996-06-17 17:14:46 +0000426
Guido van Rossum990e6191996-06-17 17:14:46 +0000427 test()