1""" 2 3.. moduleauthor:: easygui developers and Stephen Raymond Ferg 4.. default-domain:: py 5.. highlight:: python 6 7Version |release| 8""" 9 10 11import sys 12 13try: 14 from . import global_state 15except (SystemError, ValueError, ImportError): 16 import global_state 17 18try: 19 import tkinter as tk # python 3 20 import tkinter.font as tk_Font 21except: 22 import Tkinter as tk # python 2 23 import tkFont as tk_Font 24 25 26def demo_textbox(): 27 demo_1() 28 Demo2() 29 Demo3() 30 31 32def demo_1(): 33 34 title = "Demo of textbox: Classic box" 35 36 gnexp = ("This is a demo of the classic textbox call, " 37 "you can see it closes when ok is pressed.\n\n") 38 39 challenge = "INSERT A TEXT WITH MORE THAN TWO PARAGRAPHS" 40 41 text = "Insert your text here\n" 42 43 msg = gnexp + challenge 44 45 finished = False 46 while True: 47 48 text = textbox(msg, title, text) 49 escaped = not text 50 if escaped or finished: 51 break 52 53 if text.count("\n") >= 2: 54 msg = (u"You did it right! Press OK") 55 finished = True 56 else: 57 msg = u"You did it wrong! Try again!\n" + challenge 58 59 60class Demo2(object): 61 62 """ Program that challenges the user to write 5 a's """ 63 64 def __init__(self): 65 """ Set and run the program """ 66 67 title = "Demo of textbox: Classic box with callback" 68 69 gnexp = ("This is a demo of the textbox with a callback, " 70 "it doesn't flicker!.\n\n") 71 72 msg = "INSERT A TEXT WITH FIVE OR MORE A\'s" 73 74 text_snippet = "Insert your text here" 75 76 self.finished = False 77 78 textbox(gnexp + msg, title, text_snippet, False, 79 callback=self.check_answer, run=True) 80 81 def check_answer(self, box): 82 """ Callback from TextBox 83 84 Parameters 85 ----------- 86 box: object 87 object containing parameters and methods to communicate with the ui 88 89 Returns 90 ------- 91 nothing: 92 its return is through the box object 93 """ 94 95 if self.finished: 96 box.stop() 97 98 if box.text.lower().count("a") >= 5: 99 box.msg = u"\n\nYou did it right! Press OK button to continue." 100 box.stop() 101 self.finished 102 else: 103 box.msg = u"\n\nMore a's are needed!" 104 105 106class Demo3(object): 107 108 """ Program that challenges the user to find a typo """ 109 110 def __init__(self): 111 """ Set and run the program """ 112 113 self.finished = False 114 115 title = "Demo of textbox: Object with callback" 116 117 msg = ("This is a demo of the textbox set as " 118 "an object with a callback, " 119 "you can configure it and when you are finished, " 120 "you run it.\n\nThere is a typo in it. Find and correct it.") 121 122 text_snippet = "Hello" # This text wont show 123 124 box = textbox( 125 msg, title, text_snippet, False, callback=self.check_answer, run=False) 126 127 box.text = ( 128 "It was the west of times, and it was the worst of times. " 129 "The rich ate cake, and the poor had cake recommended to them, " 130 "but wished only for enough cash to buy bread." 131 "The time was ripe for revolution! ") 132 133 box.run() 134 135 def check_answer(self, box): 136 """ Callback from TextBox 137 138 Parameters 139 ---------- 140 box: object 141 object containing parameters and methods to communicate with the ui 142 143 Returns 144 ------- 145 nothing: 146 its return is through the box object 147 """ 148 if self.finished: 149 box.stop() 150 151 if "best" in box.text: 152 box.msg = u"\n\nYou did right! Press OK button to continue." 153 self.finished = True 154 else: 155 box.msg = u"\n\nLook to the west!" 156 157 158def textbox(msg="", title=" ", text="", 159 codebox=False, callback=None, run=True): 160 """ Display a message and a text to edit 161 162 Parameters 163 ---------- 164 msg : string 165 text displayed in the message area (instructions...) 166 title : str 167 the window title 168 text: str, list or tuple 169 text displayed in textAreas (editable) 170 codebox: bool 171 if True, don't wrap and width is set to 80 chars 172 callback: function 173 if set, this function will be called when OK is pressed 174 run: bool 175 if True, a box object will be created and returned, but not run 176 177 Returns 178 ------- 179 None 180 If cancel is pressed 181 str 182 If OK is pressed returns the contents of textArea 183 184 """ 185 186 tb = TextBox(msg=msg, title=title, text=text, 187 codebox=codebox, callback=callback) 188 if not run: 189 return tb 190 else: 191 reply = tb.run() 192 return reply 193 194 195class TextBox(object): 196 197 """ Display a message and a text to edit 198 199 This object separates user from ui, defines which methods can 200 the user invoke and which properties can he change. 201 202 It also calls the ui in defined ways, so if other gui 203 library can be used (wx, qt) without breaking anything for the user. 204 """ 205 206 def __init__(self, msg, title, text, codebox, callback=lambda *args, **kwargs: True): 207 """ Create box object 208 209 Parameters 210 ---------- 211 msg : string 212 text displayed in the message area (instructions...) 213 title : str 214 the window title 215 text: str, list or tuple 216 text displayed in textAres (editable) 217 codebox: bool 218 if True, don't wrap and width is set to 80 chars 219 callback: function 220 if set, this function will be called when OK is pressed 221 222 Returns 223 ------- 224 object 225 The box object 226 """ 227 228 self.callback = callback 229 self.ui = GUItk(msg, title, text, codebox, self.callback_ui) 230 self.text = text 231 232 def run(self): 233 """ Start the ui """ 234 self.ui.run() 235 self.ui = None 236 return self._text 237 238 def stop(self): 239 """ Stop the ui """ 240 self.ui.stop() 241 242 def callback_ui(self, ui, command, text): 243 """ This method is executed when ok, cancel, or x is pressed in the ui. 244 """ 245 if command == 'update': # OK was pressed 246 self._text = text 247 if self.callback: 248 # If a callback was set, call main process 249 self.callback(self) 250 else: 251 self.stop() 252 elif command == 'x': 253 self.stop() 254 self._text = None 255 elif command == 'cancel': 256 self.stop() 257 self._text = None 258 259 # methods to change properties -------------- 260 @property 261 def text(self): 262 """Text in text Area""" 263 return self._text 264 265 @text.setter 266 def text(self, text): 267 self._text = self.to_string(text) 268 self.ui.set_text(self._text) 269 270 @text.deleter 271 def text(self): 272 self._text = "" 273 self.ui.set_text(self._text) 274 275 @property 276 def msg(self): 277 """Text in msg Area""" 278 return self._msg 279 280 @msg.setter 281 def msg(self, msg): 282 self._msg = self.to_string(msg) 283 self.ui.set_msg(self._msg) 284 285 @msg.deleter 286 def msg(self): 287 self._msg = "" 288 self.ui.set_msg(self._msg) 289 290 # Methods to validate what will be sent to ui --------- 291 292 def to_string(self, something): 293 try: 294 basestring # python 2 295 except NameError: 296 basestring = str # Python 3 297 298 if isinstance(something, basestring): 299 return something 300 try: 301 text = "".join(something) # convert a list or a tuple to a string 302 except: 303 textbox( 304 "Exception when trying to convert {} to text in self.textArea" 305 .format(type(something))) 306 sys.exit(16) 307 return text 308 309 310class GUItk(object): 311 312 """ This is the object that contains the tk root object""" 313 314 def __init__(self, msg, title, text, codebox, callback): 315 """ Create ui object 316 317 Parameters 318 ---------- 319 msg : string 320 text displayed in the message area (instructions...) 321 title : str 322 the window title 323 text: str, list or tuple 324 text displayed in textAres (editable) 325 codebox: bool 326 if True, don't wrap, and width is set to 80 chars 327 callback: function 328 if set, this function will be called when OK is pressed 329 330 Returns 331 ------- 332 object 333 The ui object 334 """ 335 336 self.callback = callback 337 338 self.boxRoot = tk.Tk() 339 # self.boxFont = tk_Font.Font( 340 # family=global_state.PROPORTIONAL_FONT_FAMILY, 341 # size=global_state.PROPORTIONAL_FONT_SIZE) 342 343 wrap_text = not codebox 344 if wrap_text: 345 self.boxFont = tk_Font.nametofont("TkTextFont") 346 self.width_in_chars = global_state.prop_font_line_length 347 else: 348 self.boxFont = tk_Font.nametofont("TkFixedFont") 349 self.width_in_chars = global_state.fixw_font_line_length 350 351 # default_font.configure(size=global_state.PROPORTIONAL_FONT_SIZE) 352 353 self.configure_root(title) 354 355 self.create_msg_widget(msg) 356 357 self.create_text_area(wrap_text) 358 359 self.create_buttons_frame() 360 361 self.create_cancel_button() 362 363 self.create_ok_button() 364 365 # Run and stop methods --------------------------------------- 366 367 def run(self): 368 self.boxRoot.mainloop() 369 self.boxRoot.destroy() 370 371 def stop(self): 372 # Get the current position before quitting 373 self.get_pos() 374 self.boxRoot.quit() 375 376 # Methods to change content --------------------------------------- 377 378 def set_msg(self, msg): 379 self.messageArea.config(state=tk.NORMAL) 380 self.messageArea.delete(1.0, tk.END) 381 self.messageArea.insert(tk.END, msg) 382 self.messageArea.config(state=tk.DISABLED) 383 # Adjust msg height 384 self.messageArea.update() 385 numlines = self.get_num_lines(self.messageArea) 386 self.set_msg_height(numlines) 387 self.messageArea.update() 388 389 def set_msg_height(self, numlines): 390 self.messageArea.configure(height=numlines) 391 392 def get_num_lines(self, widget): 393 end_position = widget.index(tk.END) # '4.0' 394 end_line = end_position.split('.')[0] # 4 395 return int(end_line) + 1 # 5 396 397 def set_text(self, text): 398 self.textArea.delete(1.0, tk.END) 399 self.textArea.insert(tk.END, text, "normal") 400 self.textArea.focus() 401 402 def set_pos(self, pos): 403 self.boxRoot.geometry(pos) 404 405 def get_pos(self): 406 # The geometry() method sets a size for the window and positions it on 407 # the screen. The first two parameters are width and height of 408 # the window. The last two parameters are x and y screen coordinates. 409 # geometry("250x150+300+300") 410 geom = self.boxRoot.geometry() # "628x672+300+200" 411 global_state.window_position = '+' + geom.split('+', 1)[1] 412 413 def get_text(self): 414 return self.textArea.get(0.0, 'end-1c') 415 416 # Methods executing when a key is pressed ------------------------------- 417 def x_pressed(self): 418 self.callback(self, command='x', text=self.get_text()) 419 420 def cancel_pressed(self, event): 421 self.callback(self, command='cancel', text=self.get_text()) 422 423 def ok_button_pressed(self, event): 424 self.callback(self, command='update', text=self.get_text()) 425 426 # Auxiliary methods ----------------------------------------------- 427 def calc_character_width(self): 428 char_width = self.boxFont.measure('W') 429 return char_width 430 431 # Initial configuration methods --------------------------------------- 432 # These ones are just called once, at setting. 433 434 def configure_root(self, title): 435 436 self.boxRoot.title(title) 437 438 self.set_pos(global_state.window_position) 439 440 # Quit when x button pressed 441 self.boxRoot.protocol('WM_DELETE_WINDOW', self.x_pressed) 442 self.boxRoot.bind("<Escape>", self.cancel_pressed) 443 444 self.boxRoot.iconname('Dialog') 445 446 def create_msg_widget(self, msg): 447 448 if msg is None: 449 msg = "" 450 451 self.msgFrame = tk.Frame( 452 self.boxRoot, 453 padx=2 * self.calc_character_width(), 454 455 ) 456 self.messageArea = tk.Text( 457 self.msgFrame, 458 width=self.width_in_chars, 459 state=tk.DISABLED, 460 padx=(global_state.default_hpad_in_chars) * 461 self.calc_character_width(), 462 pady=global_state.default_hpad_in_chars * 463 self.calc_character_width(), 464 wrap=tk.WORD, 465 466 ) 467 self.set_msg(msg) 468 469 self.msgFrame.pack(side=tk.TOP, expand=1, fill='both') 470 471 self.messageArea.pack(side=tk.TOP, expand=1, fill='both') 472 473 def create_text_area(self, wrap_text): 474 """ 475 Put a textArea in the top frame 476 Put and configure scrollbars 477 """ 478 479 self.textFrame = tk.Frame( 480 self.boxRoot, 481 padx=2 * self.calc_character_width(), 482 ) 483 484 self.textFrame.pack(side=tk.TOP) 485 # self.textFrame.grid(row=1, column=0, sticky=tk.EW) 486 487 self.textArea = tk.Text( 488 self.textFrame, 489 padx=global_state.default_hpad_in_chars * 490 self.calc_character_width(), 491 pady=global_state.default_hpad_in_chars * 492 self.calc_character_width(), 493 height=25, # lines 494 width=self.width_in_chars, # chars of the current font 495 ) 496 497 if wrap_text: 498 self.textArea.configure(wrap=tk.WORD) 499 else: 500 self.textArea.configure(wrap=tk.NONE) 501 502 # some simple keybindings for scrolling 503 self.boxRoot.bind("<Next>", self.textArea.yview_scroll(1, tk.PAGES)) 504 self.boxRoot.bind( 505 "<Prior>", self.textArea.yview_scroll(-1, tk.PAGES)) 506 507 self.boxRoot.bind("<Right>", self.textArea.xview_scroll(1, tk.PAGES)) 508 self.boxRoot.bind("<Left>", self.textArea.xview_scroll(-1, tk.PAGES)) 509 510 self.boxRoot.bind("<Down>", self.textArea.yview_scroll(1, tk.UNITS)) 511 self.boxRoot.bind("<Up>", self.textArea.yview_scroll(-1, tk.UNITS)) 512 513 # add a vertical scrollbar to the frame 514 rightScrollbar = tk.Scrollbar( 515 self.textFrame, orient=tk.VERTICAL, command=self.textArea.yview) 516 self.textArea.configure(yscrollcommand=rightScrollbar.set) 517 518 # add a horizontal scrollbar to the frame 519 bottomScrollbar = tk.Scrollbar( 520 self.textFrame, orient=tk.HORIZONTAL, command=self.textArea.xview) 521 self.textArea.configure(xscrollcommand=bottomScrollbar.set) 522 523 # pack the textArea and the scrollbars. Note that although 524 # we must define the textArea first, we must pack it last, 525 # so that the bottomScrollbar will be located properly. 526 527 # Note that we need a bottom scrollbar only for code. 528 # Text will be displayed with wordwrap, so we don't need to have 529 # a horizontal scroll for it. 530 531 if not wrap_text: 532 bottomScrollbar.pack(side=tk.BOTTOM, fill=tk.X) 533 rightScrollbar.pack(side=tk.RIGHT, fill=tk.Y) 534 535 self.textArea.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.YES) 536 537 def create_buttons_frame(self): 538 539 self.buttonsFrame = tk.Frame(self.boxRoot, 540 # background="green", 541 542 ) 543 self.buttonsFrame.pack(side=tk.TOP) 544 545 def create_cancel_button(self): 546 # put the buttons in the buttonsFrame 547 self.cancelButton = tk.Button( 548 self.buttonsFrame, takefocus=tk.YES, text="Cancel", 549 height=1, width=6) 550 self.cancelButton.pack( 551 expand=tk.NO, side=tk.LEFT, padx='2m', pady='1m', ipady="1m", 552 ipadx="2m") 553 554 # for the commandButton, bind activation events to the activation event 555 # handler 556 self.cancelButton.bind("<Return>", self.cancel_pressed) 557 self.cancelButton.bind("<Button-1>", self.cancel_pressed) 558 self.cancelButton.bind("<Escape>", self.cancel_pressed) 559 560 def create_ok_button(self): 561 # put the buttons in the buttonsFrame 562 self.okButton = tk.Button( 563 self.buttonsFrame, takefocus=tk.YES, text="OK", height=1, width=6) 564 self.okButton.pack( 565 expand=tk.NO, side=tk.LEFT, padx='2m', pady='1m', ipady="1m", 566 ipadx="2m") 567 568 # for the commandButton, bind activation events to the activation event 569 # handler 570 self.okButton.bind("<Return>", self.ok_button_pressed) 571 self.okButton.bind("<Button-1>", self.ok_button_pressed) 572 573 574if __name__ == '__main__': 575 demo_textbox() 576