1 #
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.
12
13 It contains the following public symbols:
14
15 SimpleDialog -- A simple but flexible modal dialog box
16
17 Dialog -- a base class for dialogs
18
19 askinteger -- get an integer from the user
20
21 askfloat -- get a float from the user
22
23 askstring -- get a string from the user
24 """
25
26 from tkinter import *
27 from tkinter import _get_temp_root, _destroy_temp_root
28 from tkinter import messagebox
29
30
31 class ESC[4;38;5;81mSimpleDialog:
32
33 def __init__(self, master,
34 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
44 _setup_dialog(self.root)
45
46 self.message = Message(self.root, text=text, aspect=400)
47 self.message.pack(expand=1, fill=BOTH)
48 self.frame = Frame(self.root)
49 self.frame.pack()
50 self.num = default
51 self.cancel = cancel
52 self.default = default
53 self.root.bind('<Return>', self.return_event)
54 for num in range(len(buttons)):
55 s = buttons[num]
56 b = Button(self.frame, text=s,
57 command=(lambda self=self, num=num: self.done(num)))
58 if num == default:
59 b.config(relief=RIDGE, borderwidth=8)
60 b.pack(side=LEFT, fill=BOTH, expand=1)
61 self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
62 self.root.transient(master)
63 _place_window(self.root, master)
64
65 def go(self):
66 self.root.wait_visibility()
67 self.root.grab_set()
68 self.root.mainloop()
69 self.root.destroy()
70 return self.num
71
72 def return_event(self, event):
73 if self.default is None:
74 self.root.bell()
75 else:
76 self.done(self.default)
77
78 def wm_delete_window(self):
79 if self.cancel is None:
80 self.root.bell()
81 else:
82 self.done(self.cancel)
83
84 def done(self, num):
85 self.num = num
86 self.root.quit()
87
88
89 class ESC[4;38;5;81mDialog(ESC[4;38;5;149mToplevel):
90
91 '''Class to open dialogs.
92
93 This class is intended as a base class for custom dialogs
94 '''
95
96 def __init__(self, parent, title = None):
97 '''Initialize a dialog.
98
99 Arguments:
100
101 parent -- a parent window (the application window)
102
103 title -- the dialog title
104 '''
105 master = parent
106 if master is None:
107 master = _get_temp_root()
108
109 Toplevel.__init__(self, master)
110
111 self.withdraw() # remain invisible for now
112 # If the parent is not viewable, don't
113 # make the child transient, or else it
114 # would be opened withdrawn
115 if parent is not None and parent.winfo_viewable():
116 self.transient(parent)
117
118 if title:
119 self.title(title)
120
121 _setup_dialog(self)
122
123 self.parent = parent
124
125 self.result = None
126
127 body = Frame(self)
128 self.initial_focus = self.body(body)
129 body.pack(padx=5, pady=5)
130
131 self.buttonbox()
132
133 if self.initial_focus is None:
134 self.initial_focus = self
135
136 self.protocol("WM_DELETE_WINDOW", self.cancel)
137
138 _place_window(self, parent)
139
140 self.initial_focus.focus_set()
141
142 # wait for window to appear on screen before calling grab_set
143 self.wait_visibility()
144 self.grab_set()
145 self.wait_window(self)
146
147 def destroy(self):
148 '''Destroy the window'''
149 self.initial_focus = None
150 Toplevel.destroy(self)
151 _destroy_temp_root(self.master)
152
153 #
154 # construction hooks
155
156 def body(self, master):
157 '''create dialog body.
158
159 return widget that should have initial focus.
160 This method should be overridden, and is called
161 by the __init__ method.
162 '''
163 pass
164
165 def buttonbox(self):
166 '''add standard button box.
167
168 override if you do not want the standard buttons
169 '''
170
171 box = Frame(self)
172
173 w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
174 w.pack(side=LEFT, padx=5, pady=5)
175 w = Button(box, text="Cancel", width=10, command=self.cancel)
176 w.pack(side=LEFT, padx=5, pady=5)
177
178 self.bind("<Return>", self.ok)
179 self.bind("<Escape>", self.cancel)
180
181 box.pack()
182
183 #
184 # standard button semantics
185
186 def ok(self, event=None):
187
188 if not self.validate():
189 self.initial_focus.focus_set() # put focus back
190 return
191
192 self.withdraw()
193 self.update_idletasks()
194
195 try:
196 self.apply()
197 finally:
198 self.cancel()
199
200 def cancel(self, event=None):
201
202 # put focus back to the parent window
203 if self.parent is not None:
204 self.parent.focus_set()
205 self.destroy()
206
207 #
208 # command hooks
209
210 def validate(self):
211 '''validate the data
212
213 This method is called automatically to validate the data before the
214 dialog is destroyed. By default, it always validates OK.
215 '''
216
217 return 1 # override
218
219 def apply(self):
220 '''process the data
221
222 This method is called automatically to process the data, *after*
223 the dialog is destroyed. By default, it does nothing.
224 '''
225
226 pass # override
227
228
229 # Place a toplevel window at the center of parent or screen
230 # It is a Python implementation of ::tk::PlaceWindow.
231 def _place_window(w, parent=None):
232 w.wm_withdraw() # Remain invisible while we figure out the geometry
233 w.update_idletasks() # Actualize geometry information
234
235 minwidth = w.winfo_reqwidth()
236 minheight = w.winfo_reqheight()
237 maxwidth = w.winfo_vrootwidth()
238 maxheight = w.winfo_vrootheight()
239 if parent is not None and parent.winfo_ismapped():
240 x = parent.winfo_rootx() + (parent.winfo_width() - minwidth) // 2
241 y = parent.winfo_rooty() + (parent.winfo_height() - minheight) // 2
242 vrootx = w.winfo_vrootx()
243 vrooty = w.winfo_vrooty()
244 x = min(x, vrootx + maxwidth - minwidth)
245 x = max(x, vrootx)
246 y = min(y, vrooty + maxheight - minheight)
247 y = max(y, vrooty)
248 if w._windowingsystem == 'aqua':
249 # Avoid the native menu bar which sits on top of everything.
250 y = max(y, 22)
251 else:
252 x = (w.winfo_screenwidth() - minwidth) // 2
253 y = (w.winfo_screenheight() - minheight) // 2
254
255 w.wm_maxsize(maxwidth, maxheight)
256 w.wm_geometry('+%d+%d' % (x, y))
257 w.wm_deiconify() # Become visible at the desired location
258
259
260 def _setup_dialog(w):
261 if w._windowingsystem == "aqua":
262 w.tk.call("::tk::unsupported::MacWindowStyle", "style",
263 w, "moveableModal", "")
264 elif w._windowingsystem == "x11":
265 w.wm_attributes("-type", "dialog")
266
267 # --------------------------------------------------------------------
268 # convenience dialogues
269
270 class ESC[4;38;5;81m_QueryDialog(ESC[4;38;5;149mDialog):
271
272 def __init__(self, title, prompt,
273 initialvalue=None,
274 minvalue = None, maxvalue = None,
275 parent = None):
276
277 self.prompt = prompt
278 self.minvalue = minvalue
279 self.maxvalue = maxvalue
280
281 self.initialvalue = initialvalue
282
283 Dialog.__init__(self, parent, title)
284
285 def destroy(self):
286 self.entry = None
287 Dialog.destroy(self)
288
289 def body(self, master):
290
291 w = Label(master, text=self.prompt, justify=LEFT)
292 w.grid(row=0, padx=5, sticky=W)
293
294 self.entry = Entry(master, name="entry")
295 self.entry.grid(row=1, padx=5, sticky=W+E)
296
297 if self.initialvalue is not None:
298 self.entry.insert(0, self.initialvalue)
299 self.entry.select_range(0, END)
300
301 return self.entry
302
303 def validate(self):
304 try:
305 result = self.getresult()
306 except ValueError:
307 messagebox.showwarning(
308 "Illegal value",
309 self.errormessage + "\nPlease try again",
310 parent = self
311 )
312 return 0
313
314 if self.minvalue is not None and result < self.minvalue:
315 messagebox.showwarning(
316 "Too small",
317 "The allowed minimum value is %s. "
318 "Please try again." % self.minvalue,
319 parent = self
320 )
321 return 0
322
323 if self.maxvalue is not None and result > self.maxvalue:
324 messagebox.showwarning(
325 "Too large",
326 "The allowed maximum value is %s. "
327 "Please try again." % self.maxvalue,
328 parent = self
329 )
330 return 0
331
332 self.result = result
333
334 return 1
335
336
337 class ESC[4;38;5;81m_QueryInteger(ESC[4;38;5;149m_QueryDialog):
338 errormessage = "Not an integer."
339
340 def getresult(self):
341 return self.getint(self.entry.get())
342
343
344 def askinteger(title, prompt, **kw):
345 '''get an integer from the user
346
347 Arguments:
348
349 title -- the dialog title
350 prompt -- the label text
351 **kw -- see SimpleDialog class
352
353 Return value is an integer
354 '''
355 d = _QueryInteger(title, prompt, **kw)
356 return d.result
357
358
359 class ESC[4;38;5;81m_QueryFloat(ESC[4;38;5;149m_QueryDialog):
360 errormessage = "Not a floating point value."
361
362 def getresult(self):
363 return self.getdouble(self.entry.get())
364
365
366 def askfloat(title, prompt, **kw):
367 '''get a float from the user
368
369 Arguments:
370
371 title -- the dialog title
372 prompt -- the label text
373 **kw -- see SimpleDialog class
374
375 Return value is a float
376 '''
377 d = _QueryFloat(title, prompt, **kw)
378 return d.result
379
380
381 class ESC[4;38;5;81m_QueryString(ESC[4;38;5;149m_QueryDialog):
382 def __init__(self, *args, **kw):
383 if "show" in kw:
384 self.__show = kw["show"]
385 del kw["show"]
386 else:
387 self.__show = None
388 _QueryDialog.__init__(self, *args, **kw)
389
390 def body(self, master):
391 entry = _QueryDialog.body(self, master)
392 if self.__show is not None:
393 entry.configure(show=self.__show)
394 return entry
395
396 def getresult(self):
397 return self.entry.get()
398
399
400 def askstring(title, prompt, **kw):
401 '''get a string from the user
402
403 Arguments:
404
405 title -- the dialog title
406 prompt -- the label text
407 **kw -- see SimpleDialog class
408
409 Return value is a string
410 '''
411 d = _QueryString(title, prompt, **kw)
412 return d.result
413
414
415 if __name__ == '__main__':
416
417 def test():
418 root = Tk()
419 def doit(root=root):
420 d = SimpleDialog(root,
421 text="This is a test dialog. "
422 "Would this have been an actual dialog, "
423 "the buttons below would have been glowing "
424 "in soft pink light.\n"
425 "Do you believe this?",
426 buttons=["Yes", "No", "Cancel"],
427 default=0,
428 cancel=2,
429 title="Test Dialog")
430 print(d.go())
431 print(askinteger("Spam", "Egg count", initialvalue=12*12))
432 print(askfloat("Spam", "Egg weight\n(in tons)", minvalue=1,
433 maxvalue=100))
434 print(askstring("Spam", "Egg label"))
435 t = Button(root, text='Test', command=doit)
436 t.pack()
437 q = Button(root, text='Quit', command=t.quit)
438 q.pack()
439 t.mainloop()
440
441 test()