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