blob: 85244171117b61e8a7ef4412064b63ba45782bef [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
Serhiy Storchakadc0d5712018-10-12 19:01:00 +030031
Guido van Rossum990e6191996-06-17 17:14:46 +000032class SimpleDialog:
33
34 def __init__(self, master,
Guido van Rossumc4570481998-03-20 20:45:49 +000035 text='', buttons=[], default=None, cancel=None,
36 title=None, class_=None):
37 if class_:
38 self.root = Toplevel(master, class_=class_)
39 else:
40 self.root = Toplevel(master)
41 if title:
42 self.root.title(title)
43 self.root.iconname(title)
44 self.message = Message(self.root, text=text, aspect=400)
45 self.message.pack(expand=1, fill=BOTH)
46 self.frame = Frame(self.root)
47 self.frame.pack()
48 self.num = default
49 self.cancel = cancel
50 self.default = default
51 self.root.bind('<Return>', self.return_event)
52 for num in range(len(buttons)):
53 s = buttons[num]
54 b = Button(self.frame, text=s,
55 command=(lambda self=self, num=num: self.done(num)))
56 if num == default:
57 b.config(relief=RIDGE, borderwidth=8)
58 b.pack(side=LEFT, fill=BOTH, expand=1)
59 self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
60 self._set_transient(master)
Guido van Rossum17f2b2d1996-07-21 02:20:06 +000061
62 def _set_transient(self, master, relx=0.5, rely=0.3):
Guido van Rossumc4570481998-03-20 20:45:49 +000063 widget = self.root
64 widget.withdraw() # Remain invisible while we figure out the geometry
65 widget.transient(master)
66 widget.update_idletasks() # Actualize geometry information
67 if master.winfo_ismapped():
68 m_width = master.winfo_width()
69 m_height = master.winfo_height()
70 m_x = master.winfo_rootx()
71 m_y = master.winfo_rooty()
72 else:
73 m_width = master.winfo_screenwidth()
74 m_height = master.winfo_screenheight()
75 m_x = m_y = 0
76 w_width = widget.winfo_reqwidth()
77 w_height = widget.winfo_reqheight()
78 x = m_x + (m_width - w_width) * relx
79 y = m_y + (m_height - w_height) * rely
80 if x+w_width > master.winfo_screenwidth():
81 x = master.winfo_screenwidth() - w_width
82 elif x < 0:
83 x = 0
84 if y+w_height > master.winfo_screenheight():
85 y = master.winfo_screenheight() - w_height
86 elif y < 0:
87 y = 0
88 widget.geometry("+%d+%d" % (x, y))
89 widget.deiconify() # Become visible at the desired location
Guido van Rossum990e6191996-06-17 17:14:46 +000090
91 def go(self):
Martin v. Löwisb217cd82004-08-03 18:36:25 +000092 self.root.wait_visibility()
Guido van Rossumc4570481998-03-20 20:45:49 +000093 self.root.grab_set()
94 self.root.mainloop()
95 self.root.destroy()
96 return self.num
Guido van Rossum990e6191996-06-17 17:14:46 +000097
98 def return_event(self, event):
Guido van Rossumc4570481998-03-20 20:45:49 +000099 if self.default is None:
100 self.root.bell()
101 else:
102 self.done(self.default)
Guido van Rossum990e6191996-06-17 17:14:46 +0000103
104 def wm_delete_window(self):
Guido van Rossumc4570481998-03-20 20:45:49 +0000105 if self.cancel is None:
106 self.root.bell()
107 else:
108 self.done(self.cancel)
Guido van Rossum990e6191996-06-17 17:14:46 +0000109
110 def done(self, num):
Guido van Rossumc4570481998-03-20 20:45:49 +0000111 self.num = num
112 self.root.quit()
Guido van Rossum990e6191996-06-17 17:14:46 +0000113
114
Georg Brandl2e081362008-05-17 19:04:04 +0000115class Dialog(Toplevel):
116
117 '''Class to open dialogs.
118
119 This class is intended as a base class for custom dialogs
120 '''
121
122 def __init__(self, parent, title = None):
Georg Brandl2e081362008-05-17 19:04:04 +0000123 '''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."
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300327
Georg Brandl2e081362008-05-17 19:04:04 +0000328 def getresult(self):
Serhiy Storchaka645058d2015-05-06 14:00:04 +0300329 return self.getint(self.entry.get())
Georg Brandl2e081362008-05-17 19:04:04 +0000330
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300331
Georg Brandl2e081362008-05-17 19:04:04 +0000332def askinteger(title, prompt, **kw):
333 '''get an integer from the user
334
335 Arguments:
336
337 title -- the dialog title
338 prompt -- the label text
339 **kw -- see SimpleDialog class
340
341 Return value is an integer
342 '''
343 d = _QueryInteger(title, prompt, **kw)
344 return d.result
345
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300346
Georg Brandl2e081362008-05-17 19:04:04 +0000347class _QueryFloat(_QueryDialog):
348 errormessage = "Not a floating point value."
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300349
Georg Brandl2e081362008-05-17 19:04:04 +0000350 def getresult(self):
Serhiy Storchaka645058d2015-05-06 14:00:04 +0300351 return self.getdouble(self.entry.get())
Georg Brandl2e081362008-05-17 19:04:04 +0000352
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300353
Georg Brandl2e081362008-05-17 19:04:04 +0000354def askfloat(title, prompt, **kw):
355 '''get a float from the user
356
357 Arguments:
358
359 title -- the dialog title
360 prompt -- the label text
361 **kw -- see SimpleDialog class
362
363 Return value is a float
364 '''
365 d = _QueryFloat(title, prompt, **kw)
366 return d.result
367
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300368
Georg Brandl2e081362008-05-17 19:04:04 +0000369class _QueryString(_QueryDialog):
370 def __init__(self, *args, **kw):
371 if "show" in kw:
372 self.__show = kw["show"]
373 del kw["show"]
374 else:
375 self.__show = None
376 _QueryDialog.__init__(self, *args, **kw)
377
378 def body(self, master):
379 entry = _QueryDialog.body(self, master)
380 if self.__show is not None:
381 entry.configure(show=self.__show)
382 return entry
383
384 def getresult(self):
385 return self.entry.get()
386
Serhiy Storchakadc0d5712018-10-12 19:01:00 +0300387
Georg Brandl2e081362008-05-17 19:04:04 +0000388def askstring(title, prompt, **kw):
389 '''get a string from the user
390
391 Arguments:
392
393 title -- the dialog title
394 prompt -- the label text
395 **kw -- see SimpleDialog class
396
397 Return value is a string
398 '''
399 d = _QueryString(title, prompt, **kw)
400 return d.result
401
402
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000403if __name__ == '__main__':
404
405 def test():
406 root = Tk()
407 def doit(root=root):
408 d = SimpleDialog(root,
Guido van Rossumc4570481998-03-20 20:45:49 +0000409 text="This is a test dialog. "
410 "Would this have been an actual dialog, "
411 "the buttons below would have been glowing "
412 "in soft pink light.\n"
413 "Do you believe this?",
414 buttons=["Yes", "No", "Cancel"],
415 default=0,
416 cancel=2,
417 title="Test Dialog")
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000418 print(d.go())
Georg Brandl2e081362008-05-17 19:04:04 +0000419 print(askinteger("Spam", "Egg count", initialvalue=12*12))
420 print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1,
421 maxvalue=100))
422 print(askstring("Spam", "Egg label"))
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000423 t = Button(root, text='Test', command=doit)
424 t.pack()
425 q = Button(root, text='Quit', command=t.quit)
426 q.pack()
427 t.mainloop()
Guido van Rossum990e6191996-06-17 17:14:46 +0000428
Guido van Rossum990e6191996-06-17 17:14:46 +0000429 test()