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