blob: f6fce097156600c7339bd2b01d094d682c0d20ba [file] [log] [blame]
Georg Brandl7e6b33f2008-05-17 15:07:03 +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 Brandl7e6b33f2008-05-17 15:07:03 +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 Brandlbbc57d72008-05-16 14:24:45 +000026from tkinter import *
Guido van Rossum990e6191996-06-17 17:14:46 +000027
Guido van Rossum990e6191996-06-17 17:14:46 +000028class SimpleDialog:
29
30 def __init__(self, master,
Guido van Rossumc4570481998-03-20 20:45:49 +000031 text='', buttons=[], default=None, cancel=None,
32 title=None, class_=None):
33 if class_:
34 self.root = Toplevel(master, class_=class_)
35 else:
36 self.root = Toplevel(master)
37 if title:
38 self.root.title(title)
39 self.root.iconname(title)
40 self.message = Message(self.root, text=text, aspect=400)
41 self.message.pack(expand=1, fill=BOTH)
42 self.frame = Frame(self.root)
43 self.frame.pack()
44 self.num = default
45 self.cancel = cancel
46 self.default = default
47 self.root.bind('<Return>', self.return_event)
48 for num in range(len(buttons)):
49 s = buttons[num]
50 b = Button(self.frame, text=s,
51 command=(lambda self=self, num=num: self.done(num)))
52 if num == default:
53 b.config(relief=RIDGE, borderwidth=8)
54 b.pack(side=LEFT, fill=BOTH, expand=1)
55 self.root.protocol('WM_DELETE_WINDOW', self.wm_delete_window)
56 self._set_transient(master)
Guido van Rossum17f2b2d1996-07-21 02:20:06 +000057
58 def _set_transient(self, master, relx=0.5, rely=0.3):
Guido van Rossumc4570481998-03-20 20:45:49 +000059 widget = self.root
60 widget.withdraw() # Remain invisible while we figure out the geometry
61 widget.transient(master)
62 widget.update_idletasks() # Actualize geometry information
63 if master.winfo_ismapped():
64 m_width = master.winfo_width()
65 m_height = master.winfo_height()
66 m_x = master.winfo_rootx()
67 m_y = master.winfo_rooty()
68 else:
69 m_width = master.winfo_screenwidth()
70 m_height = master.winfo_screenheight()
71 m_x = m_y = 0
72 w_width = widget.winfo_reqwidth()
73 w_height = widget.winfo_reqheight()
74 x = m_x + (m_width - w_width) * relx
75 y = m_y + (m_height - w_height) * rely
76 if x+w_width > master.winfo_screenwidth():
77 x = master.winfo_screenwidth() - w_width
78 elif x < 0:
79 x = 0
80 if y+w_height > master.winfo_screenheight():
81 y = master.winfo_screenheight() - w_height
82 elif y < 0:
83 y = 0
84 widget.geometry("+%d+%d" % (x, y))
85 widget.deiconify() # Become visible at the desired location
Guido van Rossum990e6191996-06-17 17:14:46 +000086
87 def go(self):
Martin v. Löwisb217cd82004-08-03 18:36:25 +000088 self.root.wait_visibility()
Guido van Rossumc4570481998-03-20 20:45:49 +000089 self.root.grab_set()
90 self.root.mainloop()
91 self.root.destroy()
92 return self.num
Guido van Rossum990e6191996-06-17 17:14:46 +000093
94 def return_event(self, event):
Guido van Rossumc4570481998-03-20 20:45:49 +000095 if self.default is None:
96 self.root.bell()
97 else:
98 self.done(self.default)
Guido van Rossum990e6191996-06-17 17:14:46 +000099
100 def wm_delete_window(self):
Guido van Rossumc4570481998-03-20 20:45:49 +0000101 if self.cancel is None:
102 self.root.bell()
103 else:
104 self.done(self.cancel)
Guido van Rossum990e6191996-06-17 17:14:46 +0000105
106 def done(self, num):
Guido van Rossumc4570481998-03-20 20:45:49 +0000107 self.num = num
108 self.root.quit()
Guido van Rossum990e6191996-06-17 17:14:46 +0000109
110
Georg Brandl7e6b33f2008-05-17 15:07:03 +0000111class Dialog(Toplevel):
112
113 '''Class to open dialogs.
114
115 This class is intended as a base class for custom dialogs
116 '''
117
118 def __init__(self, parent, title = None):
119
120 '''Initialize a dialog.
121
122 Arguments:
123
124 parent -- a parent window (the application window)
125
126 title -- the dialog title
127 '''
128 Toplevel.__init__(self, parent)
129
130 # If the master is not viewable, don't
131 # make the child transient, or else it
132 # would be opened withdrawn
133 if parent.winfo_viewable():
134 self.transient(parent)
135
136 if title:
137 self.title(title)
138
139 self.parent = parent
140
141 self.result = None
142
143 body = Frame(self)
144 self.initial_focus = self.body(body)
145 body.pack(padx=5, pady=5)
146
147 self.buttonbox()
148
149 self.wait_visibility() # window needs to be visible for the grab
150 self.grab_set()
151
152 if not self.initial_focus:
153 self.initial_focus = self
154
155 self.protocol("WM_DELETE_WINDOW", self.cancel)
156
157 if self.parent is not None:
158 self.geometry("+%d+%d" % (parent.winfo_rootx()+50,
159 parent.winfo_rooty()+50))
160
161 self.initial_focus.focus_set()
162
163 self.wait_window(self)
164
165 def destroy(self):
166 '''Destroy the window'''
167 self.initial_focus = None
168 Toplevel.destroy(self)
169
170 #
171 # construction hooks
172
173 def body(self, master):
174 '''create dialog body.
175
176 return widget that should have initial focus.
177 This method should be overridden, and is called
178 by the __init__ method.
179 '''
180 pass
181
182 def buttonbox(self):
183 '''add standard button box.
184
185 override if you do not want the standard buttons
186 '''
187
188 box = Frame(self)
189
190 w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
191 w.pack(side=LEFT, padx=5, pady=5)
192 w = Button(box, text="Cancel", width=10, command=self.cancel)
193 w.pack(side=LEFT, padx=5, pady=5)
194
195 self.bind("<Return>", self.ok)
196 self.bind("<Escape>", self.cancel)
197
198 box.pack()
199
200 #
201 # standard button semantics
202
203 def ok(self, event=None):
204
205 if not self.validate():
206 self.initial_focus.focus_set() # put focus back
207 return
208
209 self.withdraw()
210 self.update_idletasks()
211
212 try:
213 self.apply()
214 finally:
215 self.cancel()
216
217 def cancel(self, event=None):
218
219 # put focus back to the parent window
220 if self.parent is not None:
221 self.parent.focus_set()
222 self.destroy()
223
224 #
225 # command hooks
226
227 def validate(self):
228 '''validate the data
229
230 This method is called automatically to validate the data before the
231 dialog is destroyed. By default, it always validates OK.
232 '''
233
234 return 1 # override
235
236 def apply(self):
237 '''process the data
238
239 This method is called automatically to process the data, *after*
240 the dialog is destroyed. By default, it does nothing.
241 '''
242
243 pass # override
244
245
246# --------------------------------------------------------------------
247# convenience dialogues
248
249class _QueryDialog(Dialog):
250
251 def __init__(self, title, prompt,
252 initialvalue=None,
253 minvalue = None, maxvalue = None,
254 parent = None):
255
256 if not parent:
257 import Tkinter
258 parent = Tkinter._default_root
259
260 self.prompt = prompt
261 self.minvalue = minvalue
262 self.maxvalue = maxvalue
263
264 self.initialvalue = initialvalue
265
266 Dialog.__init__(self, parent, title)
267
268 def destroy(self):
269 self.entry = None
270 Dialog.destroy(self)
271
272 def body(self, master):
273
274 w = Label(master, text=self.prompt, justify=LEFT)
275 w.grid(row=0, padx=5, sticky=W)
276
277 self.entry = Entry(master, name="entry")
278 self.entry.grid(row=1, padx=5, sticky=W+E)
279
280 if self.initialvalue:
281 self.entry.insert(0, self.initialvalue)
282 self.entry.select_range(0, END)
283
284 return self.entry
285
286 def validate(self):
287
288 import tkMessageBox
289
290 try:
291 result = self.getresult()
292 except ValueError:
293 tkMessageBox.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 tkMessageBox.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 tkMessageBox.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."
325 def getresult(self):
326 return int(self.entry.get())
327
328def askinteger(title, prompt, **kw):
329 '''get an integer from the user
330
331 Arguments:
332
333 title -- the dialog title
334 prompt -- the label text
335 **kw -- see SimpleDialog class
336
337 Return value is an integer
338 '''
339 d = _QueryInteger(title, prompt, **kw)
340 return d.result
341
342class _QueryFloat(_QueryDialog):
343 errormessage = "Not a floating point value."
344 def getresult(self):
345 return float(self.entry.get())
346
347def askfloat(title, prompt, **kw):
348 '''get a float from the user
349
350 Arguments:
351
352 title -- the dialog title
353 prompt -- the label text
354 **kw -- see SimpleDialog class
355
356 Return value is a float
357 '''
358 d = _QueryFloat(title, prompt, **kw)
359 return d.result
360
361class _QueryString(_QueryDialog):
362 def __init__(self, *args, **kw):
363 if kw.has_key("show"):
364 self.__show = kw["show"]
365 del kw["show"]
366 else:
367 self.__show = None
368 _QueryDialog.__init__(self, *args, **kw)
369
370 def body(self, master):
371 entry = _QueryDialog.body(self, master)
372 if self.__show is not None:
373 entry.configure(show=self.__show)
374 return entry
375
376 def getresult(self):
377 return self.entry.get()
378
379def askstring(title, prompt, **kw):
380 '''get a string from the user
381
382 Arguments:
383
384 title -- the dialog title
385 prompt -- the label text
386 **kw -- see SimpleDialog class
387
388 Return value is a string
389 '''
390 d = _QueryString(title, prompt, **kw)
391 return d.result
392
393
394
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000395if __name__ == '__main__':
396
397 def test():
398 root = Tk()
399 def doit(root=root):
400 d = SimpleDialog(root,
Guido van Rossumc4570481998-03-20 20:45:49 +0000401 text="This is a test dialog. "
402 "Would this have been an actual dialog, "
403 "the buttons below would have been glowing "
404 "in soft pink light.\n"
405 "Do you believe this?",
406 buttons=["Yes", "No", "Cancel"],
407 default=0,
408 cancel=2,
409 title="Test Dialog")
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000410 print d.go()
Georg Brandl7e6b33f2008-05-17 15:07:03 +0000411 print askinteger("Spam", "Egg count", initialvalue=12*12)
412 print askfloat("Spam", "Egg weight\n(in tons)", minvalue=1,
413 maxvalue=100)
414 print askstring("Spam", "Egg label")
415
Martin v. Löwisa3837a02004-03-22 21:49:47 +0000416 t = Button(root, text='Test', command=doit)
417 t.pack()
418 q = Button(root, text='Quit', command=t.quit)
419 q.pack()
420 t.mainloop()
Guido van Rossum990e6191996-06-17 17:14:46 +0000421
Guido van Rossum990e6191996-06-17 17:14:46 +0000422 test()