• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
13It 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"""
25
26from tkinter import *
27from tkinter import _get_temp_root, _destroy_temp_root
28from tkinter import messagebox
29
30
31class SimpleDialog:
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
89class Dialog(Toplevel):
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.
231def _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
260def _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
270class _QueryDialog(Dialog):
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
337class _QueryInteger(_QueryDialog):
338    errormessage = "Not an integer."
339
340    def getresult(self):
341        return self.getint(self.entry.get())
342
343
344def 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
359class _QueryFloat(_QueryDialog):
360    errormessage = "Not a floating point value."
361
362    def getresult(self):
363        return self.getdouble(self.entry.get())
364
365
366def 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
381class _QueryString(_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
400def 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
415if __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()
442