1 """
2 @version: 0.96(2010-08-29)
3
4 @note:
5 ABOUT EASYGUI
6
7 EasyGui provides an easy-to-use interface for simple GUI interaction
8 with a user. It does not require the programmer to know anything about
9 tkinter, frames, widgets, callbacks or lambda. All GUI interactions are
10 invoked by simple function calls that return results.
11
12 @note:
13 WARNING about using EasyGui with IDLE
14
15 You may encounter problems using IDLE to run programs that use EasyGui. Try it
16 and find out. EasyGui is a collection of Tkinter routines that run their own
17 event loops. IDLE is also a Tkinter application, with its own event loop. The
18 two may conflict, with unpredictable results. If you find that you have
19 problems, try running your EasyGui program outside of IDLE.
20
21 Note that EasyGui requires Tk release 8.0 or greater.
22
23 @note:
24 LICENSE INFORMATION
25
26 EasyGui version 0.96
27
28 Copyright (c) 2010, Stephen Raymond Ferg
29
30 All rights reserved.
31
32 Redistribution and use in source and binary forms, with or without modification,
33 are permitted provided that the following conditions are met:
34
35 1. Redistributions of source code must retain the above copyright notice,
36 this list of conditions and the following disclaimer.
37
38 2. Redistributions in binary form must reproduce the above copyright notice,
39 this list of conditions and the following disclaimer in the documentation and/or
40 other materials provided with the distribution.
41
42 3. The name of the author may not be used to endorse or promote products derived
43 from this software without specific prior written permission.
44
45 THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS"
46 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
47 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
48 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
49 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
50 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
51 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
52 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
53 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
54 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
55 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
56
57 @note:
58 ABOUT THE EASYGUI LICENSE
59
60 This license is what is generally known as the "modified BSD license",
61 aka "revised BSD", "new BSD", "3-clause BSD".
62 See http://www.opensource.org/licenses/bsd-license.php
63
64 This license is GPL-compatible.
65 See http://en.wikipedia.org/wiki/License_compatibility
66 See http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses
67
68 The BSD License is less restrictive than GPL.
69 It allows software released under the license to be incorporated into proprietary products.
70 Works based on the software may be released under a proprietary license or as closed source software.
71 http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29
72
73 """
74 egversion = __doc__.split()[1]
75
76 __all__ = ['ynbox'
77 , 'ccbox'
78 , 'boolbox'
79 , 'indexbox'
80 , 'msgbox'
81 , 'buttonbox'
82 , 'integerbox'
83 , 'multenterbox'
84 , 'enterbox'
85 , 'exceptionbox'
86 , 'choicebox'
87 , 'codebox'
88 , 'textbox'
89 , 'diropenbox'
90 , 'fileopenbox'
91 , 'filesavebox'
92 , 'passwordbox'
93 , 'multpasswordbox'
94 , 'multchoicebox'
95 , 'abouteasygui'
96 , 'egversion'
97 , 'egdemo'
98 , 'EgStore'
99 ]
100
101 import sys, os
102 import string
103 import pickle
104 import traceback
105
106
107
108
109
110 """
111 From the python documentation:
112
113 sys.hexversion contains the version number encoded as a single integer. This is
114 guaranteed to increase with each version, including proper support for non-
115 production releases. For example, to test that the Python interpreter is at
116 least version 1.5.2, use:
117
118 if sys.hexversion >= 0x010502F0:
119 # use some advanced feature
120 ...
121 else:
122 # use an alternative implementation or warn the user
123 ...
124 """
125
126
127 if sys.hexversion >= 0x020600F0:
128 runningPython26 = True
129 else:
130 runningPython26 = False
131
132 if sys.hexversion >= 0x030000F0:
133 runningPython3 = True
134 else:
135 runningPython3 = False
136
137 try:
138 from PIL import Image as PILImage
139 from PIL import ImageTk as PILImageTk
140 PILisLoaded = True
141 except:
142 PILisLoaded = False
143
144
145 if runningPython3:
146 from tkinter import *
147 import tkinter.filedialog as tk_FileDialog
148 from io import StringIO
149 else:
150 from Tkinter import *
151 import tkFileDialog as tk_FileDialog
152 from StringIO import StringIO
153
155 args = [str(arg) for arg in args]
156 args = " ".join(args)
157 sys.stdout.write(args)
158
162
163 say = writeln
164
165
166 if TkVersion < 8.0 :
167 stars = "*"*75
168 writeln("""\n\n\n""" + stars + """
169 You are running Tk version: """ + str(TkVersion) + """
170 You must be using Tk version 8.0 or greater to use EasyGui.
171 Terminating.
172 """ + stars + """\n\n\n""")
173 sys.exit(0)
174
177
178 rootWindowPosition = "+300+200"
179
180 PROPORTIONAL_FONT_FAMILY = ("MS", "Sans", "Serif")
181 MONOSPACE_FONT_FAMILY = ("Courier")
182
183 PROPORTIONAL_FONT_SIZE = 10
184 MONOSPACE_FONT_SIZE = 9
185 TEXT_ENTRY_FONT_SIZE = 12
186
187
188 STANDARD_SELECTION_EVENTS = ["Return", "Button-1", "space"]
189
190
191 __choiceboxMultipleSelect = None
192 __widgetTexts = None
193 __replyButtonText = None
194 __choiceboxResults = None
195 __firstWidget = None
196 __enterboxText = None
197 __enterboxDefaultText=""
198 __multenterboxText = ""
199 choiceboxChoices = None
200 choiceboxWidget = None
201 entryWidget = None
202 boxRoot = None
203 ImageErrorMsg = (
204 "\n\n---------------------------------------------\n"
205 "Error: %s\n%s")
206
207
208
209
210
211
212
213 -def ynbox(msg="Shall I continue?"
214 , title=" "
215 , choices=("Yes", "No")
216 , image=None
217 ):
218 """
219 Display a msgbox with choices of Yes and No.
220
221 The default is "Yes".
222
223 The returned value is calculated this way::
224 if the first choice ("Yes") is chosen, or if the dialog is cancelled:
225 return 1
226 else:
227 return 0
228
229 If invoked without a msg argument, displays a generic request for a confirmation
230 that the user wishes to continue. So it can be used this way::
231 if ynbox(): pass # continue
232 else: sys.exit(0) # exit the program
233
234 @arg msg: the msg to be displayed.
235 @arg title: the window title
236 @arg choices: a list or tuple of the choices to be displayed
237 """
238 return boolbox(msg, title, choices, image=image)
239
240
241
242
243
244 -def ccbox(msg="Shall I continue?"
245 , title=" "
246 , choices=("Continue", "Cancel")
247 , image=None
248 ):
249 """
250 Display a msgbox with choices of Continue and Cancel.
251
252 The default is "Continue".
253
254 The returned value is calculated this way::
255 if the first choice ("Continue") is chosen, or if the dialog is cancelled:
256 return 1
257 else:
258 return 0
259
260 If invoked without a msg argument, displays a generic request for a confirmation
261 that the user wishes to continue. So it can be used this way::
262
263 if ccbox():
264 pass # continue
265 else:
266 sys.exit(0) # exit the program
267
268 @arg msg: the msg to be displayed.
269 @arg title: the window title
270 @arg choices: a list or tuple of the choices to be displayed
271 """
272 return boolbox(msg, title, choices, image=image)
273
274
275
276
277
278 -def boolbox(msg="Shall I continue?"
279 , title=" "
280 , choices=("Yes","No")
281 , image=None
282 ):
283 """
284 Display a boolean msgbox.
285
286 The default is the first choice.
287
288 The returned value is calculated this way::
289 if the first choice is chosen, or if the dialog is cancelled:
290 returns 1
291 else:
292 returns 0
293 """
294 reply = buttonbox(msg=msg, choices=choices, title=title, image=image)
295 if reply == choices[0]: return 1
296 else: return 0
297
298
299
300
301
302 -def indexbox(msg="Shall I continue?"
303 , title=" "
304 , choices=("Yes","No")
305 , image=None
306 ):
307 """
308 Display a buttonbox with the specified choices.
309 Return the index of the choice selected.
310 """
311 reply = buttonbox(msg=msg, choices=choices, title=title, image=image)
312 index = -1
313 for choice in choices:
314 index = index + 1
315 if reply == choice: return index
316 raise AssertionError(
317 "There is a program logic error in the EasyGui code for indexbox.")
318
319
320
321
322
323 -def msgbox(msg="(Your message goes here)", title=" ", ok_button="OK",image=None,root=None):
324 """
325 Display a messagebox
326 """
327 if type(ok_button) != type("OK"):
328 raise AssertionError("The 'ok_button' argument to msgbox must be a string.")
329
330 return buttonbox(msg=msg, title=title, choices=[ok_button], image=image,root=root)
331
332
333
334
335
431
432
433
434
435
436 -def integerbox(msg=""
437 , title=" "
438 , default=""
439 , lowerbound=0
440 , upperbound=99
441 , image = None
442 , root = None
443 , **invalidKeywordArguments
444 ):
445 """
446 Show a box in which a user can enter an integer.
447
448 In addition to arguments for msg and title, this function accepts
449 integer arguments for "default", "lowerbound", and "upperbound".
450
451 The default argument may be None.
452
453 When the user enters some text, the text is checked to verify that it
454 can be converted to an integer between the lowerbound and upperbound.
455
456 If it can be, the integer (not the text) is returned.
457
458 If it cannot, then an error msg is displayed, and the integerbox is
459 redisplayed.
460
461 If the user cancels the operation, None is returned.
462
463 NOTE that the "argLowerBound" and "argUpperBound" arguments are no longer
464 supported. They have been replaced by "upperbound" and "lowerbound".
465 """
466 if "argLowerBound" in invalidKeywordArguments:
467 raise AssertionError(
468 "\nintegerbox no longer supports the 'argLowerBound' argument.\n"
469 + "Use 'lowerbound' instead.\n\n")
470 if "argUpperBound" in invalidKeywordArguments:
471 raise AssertionError(
472 "\nintegerbox no longer supports the 'argUpperBound' argument.\n"
473 + "Use 'upperbound' instead.\n\n")
474
475 if default != "":
476 if type(default) != type(1):
477 raise AssertionError(
478 "integerbox received a non-integer value for "
479 + "default of " + dq(str(default)) , "Error")
480
481 if type(lowerbound) != type(1):
482 raise AssertionError(
483 "integerbox received a non-integer value for "
484 + "lowerbound of " + dq(str(lowerbound)) , "Error")
485
486 if type(upperbound) != type(1):
487 raise AssertionError(
488 "integerbox received a non-integer value for "
489 + "upperbound of " + dq(str(upperbound)) , "Error")
490
491 if msg == "":
492 msg = ("Enter an integer between " + str(lowerbound)
493 + " and "
494 + str(upperbound)
495 )
496
497 while 1:
498 reply = enterbox(msg, title, str(default), image=image, root=root)
499 if reply == None: return None
500
501 try:
502 reply = int(reply)
503 except:
504 msgbox ("The value that you entered:\n\t%s\nis not an integer." % dq(str(reply))
505 , "Error")
506 continue
507
508 if reply < lowerbound:
509 msgbox ("The value that you entered is less than the lower bound of "
510 + str(lowerbound) + ".", "Error")
511 continue
512
513 if reply > upperbound:
514 msgbox ("The value that you entered is greater than the upper bound of "
515 + str(upperbound) + ".", "Error")
516 continue
517
518
519
520 return reply
521
522
523
524
525 -def multenterbox(msg="Fill in values for the fields."
526 , title=" "
527 , fields=()
528 , values=()
529 ):
530 r"""
531 Show screen with multiple data entry fields.
532
533 If there are fewer values than names, the list of values is padded with
534 empty strings until the number of values is the same as the number of names.
535
536 If there are more values than names, the list of values
537 is truncated so that there are as many values as names.
538
539 Returns a list of the values of the fields,
540 or None if the user cancels the operation.
541
542 Here is some example code, that shows how values returned from
543 multenterbox can be checked for validity before they are accepted::
544 ----------------------------------------------------------------------
545 msg = "Enter your personal information"
546 title = "Credit Card Application"
547 fieldNames = ["Name","Street Address","City","State","ZipCode"]
548 fieldValues = [] # we start with blanks for the values
549 fieldValues = multenterbox(msg,title, fieldNames)
550
551 # make sure that none of the fields was left blank
552 while 1:
553 if fieldValues == None: break
554 errmsg = ""
555 for i in range(len(fieldNames)):
556 if fieldValues[i].strip() == "":
557 errmsg += ('"%s" is a required field.\n\n' % fieldNames[i])
558 if errmsg == "":
559 break # no problems found
560 fieldValues = multenterbox(errmsg, title, fieldNames, fieldValues)
561
562 writeln("Reply was: %s" % str(fieldValues))
563 ----------------------------------------------------------------------
564
565 @arg msg: the msg to be displayed.
566 @arg title: the window title
567 @arg fields: a list of fieldnames.
568 @arg values: a list of field values
569 """
570 return __multfillablebox(msg,title,fields,values,None)
571
572
573
574
575
576 -def multpasswordbox(msg="Fill in values for the fields."
577 , title=" "
578 , fields=tuple()
579 ,values=tuple()
580 ):
581 r"""
582 Same interface as multenterbox. But in multpassword box,
583 the last of the fields is assumed to be a password, and
584 is masked with asterisks.
585
586 Example
587 =======
588
589 Here is some example code, that shows how values returned from
590 multpasswordbox can be checked for validity before they are accepted::
591 msg = "Enter logon information"
592 title = "Demo of multpasswordbox"
593 fieldNames = ["Server ID", "User ID", "Password"]
594 fieldValues = [] # we start with blanks for the values
595 fieldValues = multpasswordbox(msg,title, fieldNames)
596
597 # make sure that none of the fields was left blank
598 while 1:
599 if fieldValues == None: break
600 errmsg = ""
601 for i in range(len(fieldNames)):
602 if fieldValues[i].strip() == "":
603 errmsg = errmsg + ('"%s" is a required field.\n\n' % fieldNames[i])
604 if errmsg == "": break # no problems found
605 fieldValues = multpasswordbox(errmsg, title, fieldNames, fieldValues)
606
607 writeln("Reply was: %s" % str(fieldValues))
608 """
609 return __multfillablebox(msg,title,fields,values,"*")
610
617
619 boxRoot.event_generate("<Tab>")
620
622 boxRoot.event_generate("<Shift-Tab>")
623
624
625
626
627 -def __multfillablebox(msg="Fill in values for the fields."
628 , title=" "
629 , fields=()
630 , values=()
631 , mask = None
632 ):
633 global boxRoot, __multenterboxText, __multenterboxDefaultText, cancelButton, entryWidget, okButton
634
635 choices = ["OK", "Cancel"]
636 if len(fields) == 0: return None
637
638 fields = list(fields[:])
639 values = list(values[:])
640
641 if len(values) == len(fields): pass
642 elif len(values) > len(fields):
643 fields = fields[0:len(values)]
644 else:
645 while len(values) < len(fields):
646 values.append("")
647
648 boxRoot = Tk()
649
650 boxRoot.protocol('WM_DELETE_WINDOW', denyWindowManagerClose )
651 boxRoot.title(title)
652 boxRoot.iconname('Dialog')
653 boxRoot.geometry(rootWindowPosition)
654 boxRoot.bind("<Escape>", __multenterboxCancel)
655
656
657 messageFrame = Frame(master=boxRoot)
658 messageFrame.pack(side=TOP, fill=BOTH)
659
660
661 messageWidget = Message(messageFrame, width="4.5i", text=msg)
662 messageWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,PROPORTIONAL_FONT_SIZE))
663 messageWidget.pack(side=RIGHT, expand=1, fill=BOTH, padx='3m', pady='3m')
664
665 global entryWidgets
666 entryWidgets = []
667
668 lastWidgetIndex = len(fields) - 1
669
670 for widgetIndex in range(len(fields)):
671 argFieldName = fields[widgetIndex]
672 argFieldValue = values[widgetIndex]
673 entryFrame = Frame(master=boxRoot)
674 entryFrame.pack(side=TOP, fill=BOTH)
675
676
677 labelWidget = Label(entryFrame, text=argFieldName)
678 labelWidget.pack(side=LEFT)
679
680 entryWidget = Entry(entryFrame, width=40,highlightthickness=2)
681 entryWidgets.append(entryWidget)
682 entryWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,TEXT_ENTRY_FONT_SIZE))
683 entryWidget.pack(side=RIGHT, padx="3m")
684
685 bindArrows(entryWidget)
686
687 entryWidget.bind("<Return>", __multenterboxGetText)
688 entryWidget.bind("<Escape>", __multenterboxCancel)
689
690
691
692 if widgetIndex == lastWidgetIndex:
693 if mask:
694 entryWidgets[widgetIndex].configure(show=mask)
695
696
697 entryWidgets[widgetIndex].insert(0,argFieldValue)
698 widgetIndex += 1
699
700
701 buttonsFrame = Frame(master=boxRoot)
702 buttonsFrame.pack(side=BOTTOM, fill=BOTH)
703
704 okButton = Button(buttonsFrame, takefocus=1, text="OK")
705 bindArrows(okButton)
706 okButton.pack(expand=1, side=LEFT, padx='3m', pady='3m', ipadx='2m', ipady='1m')
707
708
709 commandButton = okButton
710 handler = __multenterboxGetText
711 for selectionEvent in STANDARD_SELECTION_EVENTS:
712 commandButton.bind("<%s>" % selectionEvent, handler)
713
714
715
716 cancelButton = Button(buttonsFrame, takefocus=1, text="Cancel")
717 bindArrows(cancelButton)
718 cancelButton.pack(expand=1, side=RIGHT, padx='3m', pady='3m', ipadx='2m', ipady='1m')
719
720
721 commandButton = cancelButton
722 handler = __multenterboxCancel
723 for selectionEvent in STANDARD_SELECTION_EVENTS:
724 commandButton.bind("<%s>" % selectionEvent, handler)
725
726
727
728 entryWidgets[0].focus_force()
729 boxRoot.mainloop()
730
731
732 boxRoot.destroy()
733 return __multenterboxText
734
735
736
737
738
740 global __multenterboxText
741
742 __multenterboxText = []
743 for entryWidget in entryWidgets:
744 __multenterboxText.append(entryWidget.get())
745 boxRoot.quit()
746
747
752
753
754
755
756
757 -def enterbox(msg="Enter something."
758 , title=" "
759 , default=""
760 , strip=True
761 , image=None
762 , root=None
763 ):
764 """
765 Show a box in which a user can enter some text.
766
767 You may optionally specify some default text, which will appear in the
768 enterbox when it is displayed.
769
770 Returns the text that the user entered, or None if he cancels the operation.
771
772 By default, enterbox strips its result (i.e. removes leading and trailing
773 whitespace). (If you want it not to strip, use keyword argument: strip=False.)
774 This makes it easier to test the results of the call::
775
776 reply = enterbox(....)
777 if reply:
778 ...
779 else:
780 ...
781 """
782 result = __fillablebox(msg, title, default=default, mask=None,image=image,root=root)
783 if result and strip:
784 result = result.strip()
785 return result
786
787
788 -def passwordbox(msg="Enter your password."
789 , title=" "
790 , default=""
791 , image=None
792 , root=None
793 ):
794 """
795 Show a box in which a user can enter a password.
796 The text is masked with asterisks, so the password is not displayed.
797 Returns the text that the user entered, or None if he cancels the operation.
798 """
799 return __fillablebox(msg, title, default, mask="*",image=image,root=root)
800
801
802 -def __fillablebox(msg
803 , title=""
804 , default=""
805 , mask=None
806 , image=None
807 , root=None
808 ):
809 """
810 Show a box in which a user can enter some text.
811 You may optionally specify some default text, which will appear in the
812 enterbox when it is displayed.
813 Returns the text that the user entered, or None if he cancels the operation.
814 """
815
816 global boxRoot, __enterboxText, __enterboxDefaultText
817 global cancelButton, entryWidget, okButton
818
819 if title == None: title == ""
820 if default == None: default = ""
821 __enterboxDefaultText = default
822 __enterboxText = __enterboxDefaultText
823
824 if root:
825 root.withdraw()
826 boxRoot = Toplevel(master=root)
827 boxRoot.withdraw()
828 else:
829 boxRoot = Tk()
830 boxRoot.withdraw()
831
832 boxRoot.protocol('WM_DELETE_WINDOW', denyWindowManagerClose )
833 boxRoot.title(title)
834 boxRoot.iconname('Dialog')
835 boxRoot.geometry(rootWindowPosition)
836 boxRoot.bind("<Escape>", __enterboxCancel)
837
838
839 messageFrame = Frame(master=boxRoot)
840 messageFrame.pack(side=TOP, fill=BOTH)
841
842
843 tk_Image = None
844 if image:
845 imageFilename = os.path.normpath(image)
846 junk,ext = os.path.splitext(imageFilename)
847
848 if os.path.exists(imageFilename):
849 if ext.lower() in [".gif", ".pgm", ".ppm"]:
850 tk_Image = PhotoImage(master=boxRoot, file=imageFilename)
851 else:
852 if PILisLoaded:
853 try:
854 pil_Image = PILImage.open(imageFilename)
855 tk_Image = PILImageTk.PhotoImage(pil_Image, master=boxRoot)
856 except:
857 msg += ImageErrorMsg % (imageFilename,
858 "\nThe Python Imaging Library (PIL) could not convert this file to a displayable image."
859 "\n\nPIL reports:\n" + exception_format())
860
861 else:
862 msg += ImageErrorMsg % (imageFilename,
863 "\nI could not import the Python Imaging Library (PIL) to display the image.\n\n"
864 "You may need to install PIL\n"
865 "(http://www.pythonware.com/products/pil/)\n"
866 "to display " + ext + " image files.")
867
868 else:
869 msg += ImageErrorMsg % (imageFilename, "\nImage file not found.")
870
871 if tk_Image:
872 imageFrame = Frame(master=boxRoot)
873 imageFrame.pack(side=TOP, fill=BOTH)
874 label = Label(imageFrame,image=tk_Image)
875 label.image = tk_Image
876 label.pack(side=TOP, expand=YES, fill=X, padx='1m', pady='1m')
877
878
879 buttonsFrame = Frame(master=boxRoot)
880 buttonsFrame.pack(side=TOP, fill=BOTH)
881
882
883
884 entryFrame = Frame(master=boxRoot)
885 entryFrame.pack(side=TOP, fill=BOTH)
886
887
888 buttonsFrame = Frame(master=boxRoot)
889 buttonsFrame.pack(side=TOP, fill=BOTH)
890
891
892 messageWidget = Message(messageFrame, width="4.5i", text=msg)
893 messageWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,PROPORTIONAL_FONT_SIZE))
894 messageWidget.pack(side=RIGHT, expand=1, fill=BOTH, padx='3m', pady='3m')
895
896
897 entryWidget = Entry(entryFrame, width=40)
898 bindArrows(entryWidget)
899 entryWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,TEXT_ENTRY_FONT_SIZE))
900 if mask:
901 entryWidget.configure(show=mask)
902 entryWidget.pack(side=LEFT, padx="3m")
903 entryWidget.bind("<Return>", __enterboxGetText)
904 entryWidget.bind("<Escape>", __enterboxCancel)
905
906 entryWidget.insert(0,__enterboxDefaultText)
907
908
909 okButton = Button(buttonsFrame, takefocus=1, text="OK")
910 bindArrows(okButton)
911 okButton.pack(expand=1, side=LEFT, padx='3m', pady='3m', ipadx='2m', ipady='1m')
912
913
914 commandButton = okButton
915 handler = __enterboxGetText
916 for selectionEvent in STANDARD_SELECTION_EVENTS:
917 commandButton.bind("<%s>" % selectionEvent, handler)
918
919
920
921 cancelButton = Button(buttonsFrame, takefocus=1, text="Cancel")
922 bindArrows(cancelButton)
923 cancelButton.pack(expand=1, side=RIGHT, padx='3m', pady='3m', ipadx='2m', ipady='1m')
924
925
926 commandButton = cancelButton
927 handler = __enterboxCancel
928 for selectionEvent in STANDARD_SELECTION_EVENTS:
929 commandButton.bind("<%s>" % selectionEvent, handler)
930
931
932 entryWidget.focus_force()
933 boxRoot.deiconify()
934 boxRoot.mainloop()
935
936
937 if root: root.deiconify()
938 boxRoot.destroy()
939 return __enterboxText
940
941
947
948
954
955
961
963 """ don't allow WindowManager close
964 """
965 x = Tk()
966 x.withdraw()
967 x.bell()
968 x.destroy()
969
970
971
972
973
974
975 -def multchoicebox(msg="Pick as many items as you like."
976 , title=" "
977 , choices=()
978 , **kwargs
979 ):
980 """
981 Present the user with a list of choices.
982 allow him to select multiple items and return them in a list.
983 if the user doesn't choose anything from the list, return the empty list.
984 return None if he cancelled selection.
985
986 @arg msg: the msg to be displayed.
987 @arg title: the window title
988 @arg choices: a list or tuple of the choices to be displayed
989 """
990 if len(choices) == 0: choices = ["Program logic error - no choices were specified."]
991
992 global __choiceboxMultipleSelect
993 __choiceboxMultipleSelect = 1
994 return __choicebox(msg, title, choices)
995
996
997
998
999
1000 -def choicebox(msg="Pick something."
1001 , title=" "
1002 , choices=()
1003 ):
1004 """
1005 Present the user with a list of choices.
1006 return the choice that he selects.
1007 return None if he cancels the selection selection.
1008
1009 @arg msg: the msg to be displayed.
1010 @arg title: the window title
1011 @arg choices: a list or tuple of the choices to be displayed
1012 """
1013 if len(choices) == 0: choices = ["Program logic error - no choices were specified."]
1014
1015 global __choiceboxMultipleSelect
1016 __choiceboxMultipleSelect = 0
1017 return __choicebox(msg,title,choices)
1018
1019
1020
1021
1022
1023 -def __choicebox(msg
1024 , title
1025 , choices
1026 ):
1027 """
1028 internal routine to support choicebox() and multchoicebox()
1029 """
1030 global boxRoot, __choiceboxResults, choiceboxWidget, defaultText
1031 global choiceboxWidget, choiceboxChoices
1032
1033
1034
1035
1036
1037
1038 choices = list(choices[:])
1039 if len(choices) == 0:
1040 choices = ["Program logic error - no choices were specified."]
1041 defaultButtons = ["OK", "Cancel"]
1042
1043
1044 for index in range(len(choices)):
1045 choices[index] = str(choices[index])
1046
1047 lines_to_show = min(len(choices), 20)
1048 lines_to_show = 20
1049
1050 if title == None: title = ""
1051
1052
1053
1054 __choiceboxResults = None
1055
1056 boxRoot = Tk()
1057 boxRoot.protocol('WM_DELETE_WINDOW', denyWindowManagerClose )
1058 screen_width = boxRoot.winfo_screenwidth()
1059 screen_height = boxRoot.winfo_screenheight()
1060 root_width = int((screen_width * 0.8))
1061 root_height = int((screen_height * 0.5))
1062 root_xpos = int((screen_width * 0.1))
1063 root_ypos = int((screen_height * 0.05))
1064
1065 boxRoot.title(title)
1066 boxRoot.iconname('Dialog')
1067 rootWindowPosition = "+0+0"
1068 boxRoot.geometry(rootWindowPosition)
1069 boxRoot.expand=NO
1070 boxRoot.minsize(root_width, root_height)
1071 rootWindowPosition = "+" + str(root_xpos) + "+" + str(root_ypos)
1072 boxRoot.geometry(rootWindowPosition)
1073
1074
1075 message_and_buttonsFrame = Frame(master=boxRoot)
1076 message_and_buttonsFrame.pack(side=TOP, fill=X, expand=NO)
1077
1078 messageFrame = Frame(message_and_buttonsFrame)
1079 messageFrame.pack(side=LEFT, fill=X, expand=YES)
1080
1081
1082 buttonsFrame = Frame(message_and_buttonsFrame)
1083 buttonsFrame.pack(side=RIGHT, expand=NO, pady=0)
1084
1085
1086 choiceboxFrame = Frame(master=boxRoot)
1087 choiceboxFrame.pack(side=BOTTOM, fill=BOTH, expand=YES)
1088
1089
1090
1091
1092 messageWidget = Message(messageFrame, anchor=NW, text=msg, width=int(root_width * 0.9))
1093 messageWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,PROPORTIONAL_FONT_SIZE))
1094 messageWidget.pack(side=LEFT, expand=YES, fill=BOTH, padx='1m', pady='1m')
1095
1096
1097 choiceboxWidget = Listbox(choiceboxFrame
1098 , height=lines_to_show
1099 , borderwidth="1m"
1100 , relief="flat"
1101 , bg="white"
1102 )
1103
1104 if __choiceboxMultipleSelect:
1105 choiceboxWidget.configure(selectmode=MULTIPLE)
1106
1107 choiceboxWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,PROPORTIONAL_FONT_SIZE))
1108
1109
1110 rightScrollbar = Scrollbar(choiceboxFrame, orient=VERTICAL, command=choiceboxWidget.yview)
1111 choiceboxWidget.configure(yscrollcommand = rightScrollbar.set)
1112
1113
1114 bottomScrollbar = Scrollbar(choiceboxFrame, orient=HORIZONTAL, command=choiceboxWidget.xview)
1115 choiceboxWidget.configure(xscrollcommand = bottomScrollbar.set)
1116
1117
1118
1119
1120
1121 bottomScrollbar.pack(side=BOTTOM, fill = X)
1122 rightScrollbar.pack(side=RIGHT, fill = Y)
1123
1124 choiceboxWidget.pack(side=LEFT, padx="1m", pady="1m", expand=YES, fill=BOTH)
1125
1126
1127
1128
1129
1130
1131 for index in range(len(choices)):
1132 choices[index] = str(choices[index])
1133
1134 if runningPython3:
1135 choices.sort(key=str.lower)
1136 else:
1137 choices.sort( lambda x,y: cmp(x.lower(), y.lower()))
1138
1139 lastInserted = None
1140 choiceboxChoices = []
1141 for choice in choices:
1142 if choice == lastInserted: pass
1143 else:
1144 choiceboxWidget.insert(END, choice)
1145 choiceboxChoices.append(choice)
1146 lastInserted = choice
1147
1148 boxRoot.bind('<Any-Key>', KeyboardListener)
1149
1150
1151 if len(choices) > 0:
1152 okButton = Button(buttonsFrame, takefocus=YES, text="OK", height=1, width=6)
1153 bindArrows(okButton)
1154 okButton.pack(expand=NO, side=TOP, padx='2m', pady='1m', ipady="1m", ipadx="2m")
1155
1156
1157 commandButton = okButton
1158 handler = __choiceboxGetChoice
1159 for selectionEvent in STANDARD_SELECTION_EVENTS:
1160 commandButton.bind("<%s>" % selectionEvent, handler)
1161
1162
1163 choiceboxWidget.bind("<Return>", __choiceboxGetChoice)
1164 choiceboxWidget.bind("<Double-Button-1>", __choiceboxGetChoice)
1165 else:
1166
1167 choiceboxWidget.bind("<Return>", __choiceboxCancel)
1168 choiceboxWidget.bind("<Double-Button-1>", __choiceboxCancel)
1169
1170 cancelButton = Button(buttonsFrame, takefocus=YES, text="Cancel", height=1, width=6)
1171 bindArrows(cancelButton)
1172 cancelButton.pack(expand=NO, side=BOTTOM, padx='2m', pady='1m', ipady="1m", ipadx="2m")
1173
1174
1175 commandButton = cancelButton
1176 handler = __choiceboxCancel
1177 for selectionEvent in STANDARD_SELECTION_EVENTS:
1178 commandButton.bind("<%s>" % selectionEvent, handler)
1179
1180
1181
1182 if len(choices) > 0 and __choiceboxMultipleSelect:
1183 selectionButtonsFrame = Frame(messageFrame)
1184 selectionButtonsFrame.pack(side=RIGHT, fill=Y, expand=NO)
1185
1186 selectAllButton = Button(selectionButtonsFrame, text="Select All", height=1, width=6)
1187 bindArrows(selectAllButton)
1188
1189 selectAllButton.bind("<Button-1>",__choiceboxSelectAll)
1190 selectAllButton.pack(expand=NO, side=TOP, padx='2m', pady='1m', ipady="1m", ipadx="2m")
1191
1192 clearAllButton = Button(selectionButtonsFrame, text="Clear All", height=1, width=6)
1193 bindArrows(clearAllButton)
1194 clearAllButton.bind("<Button-1>",__choiceboxClearAll)
1195 clearAllButton.pack(expand=NO, side=TOP, padx='2m', pady='1m', ipady="1m", ipadx="2m")
1196
1197
1198
1199 boxRoot.bind("<Escape>", __choiceboxCancel)
1200
1201
1202
1203 choiceboxWidget.select_set(0)
1204 choiceboxWidget.focus_force()
1205
1206
1207 boxRoot.mainloop()
1208
1209 boxRoot.destroy()
1210 return __choiceboxResults
1211
1212
1226
1227
1232
1237
1238
1239
1245
1246
1295
1296
1297
1298
1308
1309
1310
1311
1313 """
1314 Display a box that gives information about
1315 an exception that has just been raised.
1316
1317 The caller may optionally pass in a title for the window, or a
1318 msg to accompany the error information.
1319
1320 Note that you do not need to (and cannot) pass an exception object
1321 as an argument. The latest exception will automatically be used.
1322 """
1323 if title == None: title = "Error Report"
1324 if msg == None:
1325 msg = "An error (exception) has occurred in the program."
1326
1327 codebox(msg, title, exception_format())
1328
1329
1330
1331
1332
1333 -def codebox(msg=""
1334 , title=" "
1335 , text=""
1336 ):
1337 """
1338 Display some text in a monospaced font, with no line wrapping.
1339 This function is suitable for displaying code and text that is
1340 formatted using spaces.
1341
1342 The text parameter should be a string, or a list or tuple of lines to be
1343 displayed in the textbox.
1344 """
1345 return textbox(msg, title, text, codebox=1 )
1346
1347
1348
1349
1350 -def textbox(msg=""
1351 , title=" "
1352 , text=""
1353 , codebox=0
1354 ):
1355 """
1356 Display some text in a proportional font with line wrapping at word breaks.
1357 This function is suitable for displaying general written text.
1358
1359 The text parameter should be a string, or a list or tuple of lines to be
1360 displayed in the textbox.
1361 """
1362
1363 if msg == None: msg = ""
1364 if title == None: title = ""
1365
1366 global boxRoot, __replyButtonText, __widgetTexts, buttonsFrame
1367 global rootWindowPosition
1368 choices = ["OK"]
1369 __replyButtonText = choices[0]
1370
1371
1372 boxRoot = Tk()
1373
1374 boxRoot.protocol('WM_DELETE_WINDOW', denyWindowManagerClose )
1375
1376 screen_width = boxRoot.winfo_screenwidth()
1377 screen_height = boxRoot.winfo_screenheight()
1378 root_width = int((screen_width * 0.8))
1379 root_height = int((screen_height * 0.5))
1380 root_xpos = int((screen_width * 0.1))
1381 root_ypos = int((screen_height * 0.05))
1382
1383 boxRoot.title(title)
1384 boxRoot.iconname('Dialog')
1385 rootWindowPosition = "+0+0"
1386 boxRoot.geometry(rootWindowPosition)
1387 boxRoot.expand=NO
1388 boxRoot.minsize(root_width, root_height)
1389 rootWindowPosition = "+" + str(root_xpos) + "+" + str(root_ypos)
1390 boxRoot.geometry(rootWindowPosition)
1391
1392 mainframe = Frame(master=boxRoot)
1393 mainframe.pack(side=TOP, fill=BOTH, expand=YES)
1394
1395
1396
1397 textboxFrame = Frame(mainframe, borderwidth=3)
1398 textboxFrame.pack(side=BOTTOM , fill=BOTH, expand=YES)
1399
1400 message_and_buttonsFrame = Frame(mainframe)
1401 message_and_buttonsFrame.pack(side=TOP, fill=X, expand=NO)
1402
1403 messageFrame = Frame(message_and_buttonsFrame)
1404 messageFrame.pack(side=LEFT, fill=X, expand=YES)
1405
1406 buttonsFrame = Frame(message_and_buttonsFrame)
1407 buttonsFrame.pack(side=RIGHT, expand=NO)
1408
1409
1410
1411
1412 if codebox:
1413 character_width = int((root_width * 0.6) / MONOSPACE_FONT_SIZE)
1414 textArea = Text(textboxFrame,height=25,width=character_width, padx="2m", pady="1m")
1415 textArea.configure(wrap=NONE)
1416 textArea.configure(font=(MONOSPACE_FONT_FAMILY, MONOSPACE_FONT_SIZE))
1417
1418 else:
1419 character_width = int((root_width * 0.6) / MONOSPACE_FONT_SIZE)
1420 textArea = Text(
1421 textboxFrame
1422 , height=25
1423 , width=character_width
1424 , padx="2m"
1425 , pady="1m"
1426 )
1427 textArea.configure(wrap=WORD)
1428 textArea.configure(font=(PROPORTIONAL_FONT_FAMILY,PROPORTIONAL_FONT_SIZE))
1429
1430
1431
1432 mainframe.bind("<Next>" , textArea.yview_scroll( 1,PAGES))
1433 mainframe.bind("<Prior>", textArea.yview_scroll(-1,PAGES))
1434
1435 mainframe.bind("<Right>", textArea.xview_scroll( 1,PAGES))
1436 mainframe.bind("<Left>" , textArea.xview_scroll(-1,PAGES))
1437
1438 mainframe.bind("<Down>", textArea.yview_scroll( 1,UNITS))
1439 mainframe.bind("<Up>" , textArea.yview_scroll(-1,UNITS))
1440
1441
1442
1443 rightScrollbar = Scrollbar(textboxFrame, orient=VERTICAL, command=textArea.yview)
1444 textArea.configure(yscrollcommand = rightScrollbar.set)
1445
1446
1447 bottomScrollbar = Scrollbar(textboxFrame, orient=HORIZONTAL, command=textArea.xview)
1448 textArea.configure(xscrollcommand = bottomScrollbar.set)
1449
1450
1451
1452
1453
1454
1455
1456
1457 if codebox:
1458 bottomScrollbar.pack(side=BOTTOM, fill=X)
1459 rightScrollbar.pack(side=RIGHT, fill=Y)
1460
1461 textArea.pack(side=LEFT, fill=BOTH, expand=YES)
1462
1463
1464
1465 messageWidget = Message(messageFrame, anchor=NW, text=msg, width=int(root_width * 0.9))
1466 messageWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,PROPORTIONAL_FONT_SIZE))
1467 messageWidget.pack(side=LEFT, expand=YES, fill=BOTH, padx='1m', pady='1m')
1468
1469
1470 okButton = Button(buttonsFrame, takefocus=YES, text="OK", height=1, width=6)
1471 okButton.pack(expand=NO, side=TOP, padx='2m', pady='1m', ipady="1m", ipadx="2m")
1472
1473
1474 commandButton = okButton
1475 handler = __textboxOK
1476 for selectionEvent in ["Return","Button-1","Escape"]:
1477 commandButton.bind("<%s>" % selectionEvent, handler)
1478
1479
1480
1481 try:
1482
1483 if type(text) == type("abc"): pass
1484 else:
1485 try:
1486 text = "".join(text)
1487 except:
1488 msgbox("Exception when trying to convert "+ str(type(text)) + " to text in textArea")
1489 sys.exit(16)
1490 textArea.insert(END,text, "normal")
1491
1492 except:
1493 msgbox("Exception when trying to load the textArea.")
1494 sys.exit(16)
1495
1496 try:
1497 okButton.focus_force()
1498 except:
1499 msgbox("Exception when trying to put focus on okButton.")
1500 sys.exit(16)
1501
1502 boxRoot.mainloop()
1503
1504
1505 areaText = textArea.get(0.0,END)
1506 boxRoot.destroy()
1507 return areaText
1508
1509
1510
1511
1512 -def __textboxOK(event):
1513 global boxRoot
1514 boxRoot.quit()
1515
1516
1517
1518
1519
1520
1521 -def diropenbox(msg=None
1522 , title=None
1523 , default=None
1524 ):
1525 """
1526 A dialog to get a directory name.
1527 Note that the msg argument, if specified, is ignored.
1528
1529 Returns the name of a directory, or None if user chose to cancel.
1530
1531 If the "default" argument specifies a directory name, and that
1532 directory exists, then the dialog box will start with that directory.
1533 """
1534 title=getFileDialogTitle(msg,title)
1535 localRoot = Tk()
1536 localRoot.withdraw()
1537 if not default: default = None
1538 f = tk_FileDialog.askdirectory(
1539 parent=localRoot
1540 , title=title
1541 , initialdir=default
1542 , initialfile=None
1543 )
1544 localRoot.destroy()
1545 if not f: return None
1546 return os.path.normpath(f)
1547
1548
1549
1550
1551
1552
1556 if msg and title: return "%s - %s" % (title,msg)
1557 if msg and not title: return str(msg)
1558 if title and not msg: return str(title)
1559 return None
1560
1561
1562
1563
1566 if len(filemask) == 0:
1567 raise AssertionError('Filetype argument is empty.')
1568
1569 self.masks = []
1570
1571 if type(filemask) == type("abc"):
1572 self.initializeFromString(filemask)
1573
1574 elif type(filemask) == type([]):
1575 if len(filemask) < 2:
1576 raise AssertionError('Invalid filemask.\n'
1577 +'List contains less than 2 members: "%s"' % filemask)
1578 else:
1579 self.name = filemask[-1]
1580 self.masks = list(filemask[:-1] )
1581 else:
1582 raise AssertionError('Invalid filemask: "%s"' % filemask)
1583
1585 if self.name == other.name: return True
1586 return False
1587
1588 - def add(self,other):
1589 for mask in other.masks:
1590 if mask in self.masks: pass
1591 else: self.masks.append(mask)
1592
1594 return (self.name,tuple(self.masks))
1595
1597 if self.name == "All files": return True
1598 return False
1599
1601
1602 self.ext = os.path.splitext(filemask)[1]
1603 if self.ext == "" : self.ext = ".*"
1604 if self.ext == ".": self.ext = ".*"
1605 self.name = self.getName()
1606 self.masks = ["*" + self.ext]
1607
1609 e = self.ext
1610 if e == ".*" : return "All files"
1611 if e == ".txt": return "Text files"
1612 if e == ".py" : return "Python files"
1613 if e == ".pyc" : return "Python files"
1614 if e == ".xls": return "Excel files"
1615 if e.startswith("."):
1616 return e[1:].upper() + " files"
1617 return e.upper() + " files"
1618
1619
1620
1621
1622
1623 -def fileopenbox(msg=None
1624 , title=None
1625 , default="*"
1626 , filetypes=None
1627 ):
1628 """
1629 A dialog to get a file name.
1630
1631 About the "default" argument
1632 ============================
1633 The "default" argument specifies a filepath that (normally)
1634 contains one or more wildcards.
1635 fileopenbox will display only files that match the default filepath.
1636 If omitted, defaults to "*" (all files in the current directory).
1637
1638 WINDOWS EXAMPLE::
1639 ...default="c:/myjunk/*.py"
1640 will open in directory c:\myjunk\ and show all Python files.
1641
1642 WINDOWS EXAMPLE::
1643 ...default="c:/myjunk/test*.py"
1644 will open in directory c:\myjunk\ and show all Python files
1645 whose names begin with "test".
1646
1647
1648 Note that on Windows, fileopenbox automatically changes the path
1649 separator to the Windows path separator (backslash).
1650
1651 About the "filetypes" argument
1652 ==============================
1653 If specified, it should contain a list of items,
1654 where each item is either::
1655 - a string containing a filemask # e.g. "*.txt"
1656 - a list of strings, where all of the strings except the last one
1657 are filemasks (each beginning with "*.",
1658 such as "*.txt" for text files, "*.py" for Python files, etc.).
1659 and the last string contains a filetype description
1660
1661 EXAMPLE::
1662 filetypes = ["*.css", ["*.htm", "*.html", "HTML files"] ]
1663
1664 NOTE THAT
1665 =========
1666
1667 If the filetypes list does not contain ("All files","*"),
1668 it will be added.
1669
1670 If the filetypes list does not contain a filemask that includes
1671 the extension of the "default" argument, it will be added.
1672 For example, if default="*abc.py"
1673 and no filetypes argument was specified, then
1674 "*.py" will automatically be added to the filetypes argument.
1675
1676 @rtype: string or None
1677 @return: the name of a file, or None if user chose to cancel
1678
1679 @arg msg: the msg to be displayed.
1680 @arg title: the window title
1681 @arg default: filepath with wildcards
1682 @arg filetypes: filemasks that a user can choose, e.g. "*.txt"
1683 """
1684 localRoot = Tk()
1685 localRoot.withdraw()
1686
1687 initialbase, initialfile, initialdir, filetypes = fileboxSetup(default,filetypes)
1688
1689
1690
1691
1692
1693
1694
1695 if (initialfile.find("*") < 0) and (initialfile.find("?") < 0):
1696 initialfile = None
1697 elif initialbase == "*":
1698 initialfile = None
1699
1700 f = tk_FileDialog.askopenfilename(parent=localRoot
1701 , title=getFileDialogTitle(msg,title)
1702 , initialdir=initialdir
1703 , initialfile=initialfile
1704 , filetypes=filetypes
1705 )
1706
1707 localRoot.destroy()
1708
1709 if not f: return None
1710 return os.path.normpath(f)
1711
1712
1713
1714
1715
1716 -def filesavebox(msg=None
1717 , title=None
1718 , default=""
1719 , filetypes=None
1720 ):
1721 """
1722 A file to get the name of a file to save.
1723 Returns the name of a file, or None if user chose to cancel.
1724
1725 The "default" argument should contain a filename (i.e. the
1726 current name of the file to be saved). It may also be empty,
1727 or contain a filemask that includes wildcards.
1728
1729 The "filetypes" argument works like the "filetypes" argument to
1730 fileopenbox.
1731 """
1732
1733 localRoot = Tk()
1734 localRoot.withdraw()
1735
1736 initialbase, initialfile, initialdir, filetypes = fileboxSetup(default,filetypes)
1737
1738 f = tk_FileDialog.asksaveasfilename(parent=localRoot
1739 , title=getFileDialogTitle(msg,title)
1740 , initialfile=initialfile
1741 , initialdir=initialdir
1742 , filetypes=filetypes
1743 )
1744 localRoot.destroy()
1745 if not f: return None
1746 return os.path.normpath(f)
1747
1748
1749
1750
1751
1752
1753
1755 if not default: default = os.path.join(".","*")
1756 initialdir, initialfile = os.path.split(default)
1757 if not initialdir : initialdir = "."
1758 if not initialfile: initialfile = "*"
1759 initialbase, initialext = os.path.splitext(initialfile)
1760 initialFileTypeObject = FileTypeObject(initialfile)
1761
1762 allFileTypeObject = FileTypeObject("*")
1763 ALL_filetypes_was_specified = False
1764
1765 if not filetypes: filetypes= []
1766 filetypeObjects = []
1767
1768 for filemask in filetypes:
1769 fto = FileTypeObject(filemask)
1770
1771 if fto.isAll():
1772 ALL_filetypes_was_specified = True
1773
1774 if fto == initialFileTypeObject:
1775 initialFileTypeObject.add(fto)
1776 else:
1777 filetypeObjects.append(fto)
1778
1779
1780
1781
1782 if ALL_filetypes_was_specified:
1783 pass
1784 elif allFileTypeObject == initialFileTypeObject:
1785 pass
1786 else:
1787 filetypeObjects.insert(0,allFileTypeObject)
1788
1789
1790
1791
1792
1793 if len(filetypeObjects) == 0:
1794 filetypeObjects.append(initialFileTypeObject)
1795
1796 if initialFileTypeObject in (filetypeObjects[0], filetypeObjects[-1]):
1797 pass
1798 else:
1799 if runningPython26:
1800 filetypeObjects.append(initialFileTypeObject)
1801 else:
1802 filetypeObjects.insert(0,initialFileTypeObject)
1803
1804 filetypes = [fto.toTuple() for fto in filetypeObjects]
1805
1806 return initialbase, initialfile, initialdir, filetypes
1807
1808
1809
1810
1811
1812
1820
1821
1850
1851
1852
1853
1854
1855
1857 r"""
1858 A class to support persistent storage.
1859
1860 You can use EgStore to support the storage and retrieval
1861 of user settings for an EasyGui application.
1862
1863
1864 # Example A
1865 #-----------------------------------------------------------------------
1866 # define a class named Settings as a subclass of EgStore
1867 #-----------------------------------------------------------------------
1868 class Settings(EgStore):
1869 ::
1870 def __init__(self, filename): # filename is required
1871 #-------------------------------------------------
1872 # Specify default/initial values for variables that
1873 # this particular application wants to remember.
1874 #-------------------------------------------------
1875 self.userId = ""
1876 self.targetServer = ""
1877
1878 #-------------------------------------------------
1879 # For subclasses of EgStore, these must be
1880 # the last two statements in __init__
1881 #-------------------------------------------------
1882 self.filename = filename # this is required
1883 self.restore() # restore values from the storage file if possible
1884
1885
1886
1887 # Example B
1888 #-----------------------------------------------------------------------
1889 # create settings, a persistent Settings object
1890 #-----------------------------------------------------------------------
1891 settingsFile = "myApp_settings.txt"
1892 settings = Settings(settingsFile)
1893
1894 user = "obama_barak"
1895 server = "whitehouse1"
1896 settings.userId = user
1897 settings.targetServer = server
1898 settings.store() # persist the settings
1899
1900 # run code that gets a new value for userId, and persist the settings
1901 user = "biden_joe"
1902 settings.userId = user
1903 settings.store()
1904
1905
1906 # Example C
1907 #-----------------------------------------------------------------------
1908 # recover the Settings instance, change an attribute, and store it again.
1909 #-----------------------------------------------------------------------
1910 settings = Settings(settingsFile)
1911 settings.userId = "vanrossum_g"
1912 settings.store()
1913
1914 """
1916 self.filename = None
1917 raise NotImplementedError()
1918
1920 """
1921 Set the values of whatever attributes are recoverable
1922 from the pickle file.
1923
1924 Populate the attributes (the __dict__) of the EgStore object
1925 from the attributes (the __dict__) of the pickled object.
1926
1927 If the pickled object has attributes that have been initialized
1928 in the EgStore object, then those attributes of the EgStore object
1929 will be replaced by the values of the corresponding attributes
1930 in the pickled object.
1931
1932 If the pickled object is missing some attributes that have
1933 been initialized in the EgStore object, then those attributes
1934 of the EgStore object will retain the values that they were
1935 initialized with.
1936
1937 If the pickled object has some attributes that were not
1938 initialized in the EgStore object, then those attributes
1939 will be ignored.
1940
1941 IN SUMMARY:
1942
1943 After the recover() operation, the EgStore object will have all,
1944 and only, the attributes that it had when it was initialized.
1945
1946 Where possible, those attributes will have values recovered
1947 from the pickled object.
1948 """
1949 if not os.path.exists(self.filename): return self
1950 if not os.path.isfile(self.filename): return self
1951
1952 try:
1953 f = open(self.filename,"rb")
1954 unpickledObject = pickle.load(f)
1955 f.close()
1956
1957 for key in list(self.__dict__.keys()):
1958 default = self.__dict__[key]
1959 self.__dict__[key] = unpickledObject.__dict__.get(key,default)
1960 except:
1961 pass
1962
1963 return self
1964
1966 """
1967 Save the attributes of the EgStore object to a pickle file.
1968 Note that if the directory for the pickle file does not already exist,
1969 the store operation will fail.
1970 """
1971 f = open(self.filename, "wb")
1972 pickle.dump(self, f)
1973 f.close()
1974
1975
1977 """
1978 Delete my persistent file (i.e. pickle file), if it exists.
1979 """
1980 if os.path.isfile(self.filename):
1981 os.remove(self.filename)
1982 return
1983
1985 """
1986 return my contents as a string in an easy-to-read format.
1987 """
1988
1989 longest_key_length = 0
1990 keys = []
1991 for key in self.__dict__.keys():
1992 keys.append(key)
1993 longest_key_length = max(longest_key_length, len(key))
1994
1995 keys.sort()
1996 lines = []
1997 for key in keys:
1998 value = self.__dict__[key]
1999 key = key.ljust(longest_key_length)
2000 lines.append("%s : %s\n" % (key,repr(value)) )
2001 return "".join(lines)
2002
2003
2004
2005
2006
2007
2008
2009
2010
2012 """
2013 Run the EasyGui demo.
2014 """
2015
2016 writeln("\n" * 100)
2017
2018 intro_message = ("Pick the kind of box that you wish to demo.\n"
2019 + "\n * Python version " + sys.version
2020 + "\n * EasyGui version " + egversion
2021 + "\n * Tk version " + str(TkVersion)
2022 )
2023
2024
2025
2026
2027 while 1:
2028 choices = [
2029 "msgbox",
2030 "buttonbox",
2031 "buttonbox(image) -- a buttonbox that displays an image",
2032 "choicebox",
2033 "multchoicebox",
2034 "textbox",
2035 "ynbox",
2036 "ccbox",
2037 "enterbox",
2038 "enterbox(image) -- an enterbox that displays an image",
2039 "exceptionbox",
2040 "codebox",
2041 "integerbox",
2042 "boolbox",
2043 "indexbox",
2044 "filesavebox",
2045 "fileopenbox",
2046 "passwordbox",
2047 "multenterbox",
2048 "multpasswordbox",
2049 "diropenbox",
2050 "About EasyGui",
2051 " Help"
2052 ]
2053 choice = choicebox(msg=intro_message
2054 , title="EasyGui " + egversion
2055 , choices=choices)
2056
2057 if not choice: return
2058
2059 reply = choice.split()
2060
2061 if reply[0] == "msgbox":
2062 reply = msgbox("short msg", "This is a long title")
2063 writeln("Reply was: %s" % repr(reply))
2064
2065 elif reply[0] == "About":
2066 reply = abouteasygui()
2067
2068 elif reply[0] == "Help":
2069 _demo_help()
2070
2071 elif reply[0] == "buttonbox":
2072 reply = buttonbox()
2073 writeln("Reply was: %s" % repr(reply))
2074
2075 title = "Demo of Buttonbox with many, many buttons!"
2076 msg = "This buttonbox shows what happens when you specify too many buttons."
2077 reply = buttonbox(msg=msg, title=title, choices=choices)
2078 writeln("Reply was: %s" % repr(reply))
2079
2080 elif reply[0] == "buttonbox(image)":
2081 _demo_buttonbox_with_image()
2082
2083 elif reply[0] == "boolbox":
2084 reply = boolbox()
2085 writeln("Reply was: %s" % repr(reply))
2086
2087 elif reply[0] == "enterbox":
2088 image = "python_and_check_logo.gif"
2089 message = "Enter the name of your best friend."\
2090 "\n(Result will be stripped.)"
2091 reply = enterbox(message, "Love!", " Suzy Smith ")
2092 writeln("Reply was: %s" % repr(reply))
2093
2094 message = "Enter the name of your best friend."\
2095 "\n(Result will NOT be stripped.)"
2096 reply = enterbox(message, "Love!", " Suzy Smith ",strip=False)
2097 writeln("Reply was: %s" % repr(reply))
2098
2099 reply = enterbox("Enter the name of your worst enemy:", "Hate!")
2100 writeln("Reply was: %s" % repr(reply))
2101
2102 elif reply[0] == "enterbox(image)":
2103 image = "python_and_check_logo.gif"
2104 message = "What kind of snake is this?"
2105 reply = enterbox(message, "Quiz",image=image)
2106 writeln("Reply was: %s" % repr(reply))
2107
2108 elif reply[0] == "exceptionbox":
2109 try:
2110 thisWillCauseADivideByZeroException = 1/0
2111 except:
2112 exceptionbox()
2113
2114 elif reply[0] == "integerbox":
2115 reply = integerbox(
2116 "Enter a number between 3 and 333",
2117 "Demo: integerbox WITH a default value",
2118 222, 3, 333)
2119 writeln("Reply was: %s" % repr(reply))
2120
2121 reply = integerbox(
2122 "Enter a number between 0 and 99",
2123 "Demo: integerbox WITHOUT a default value"
2124 )
2125 writeln("Reply was: %s" % repr(reply))
2126
2127 elif reply[0] == "diropenbox" : _demo_diropenbox()
2128 elif reply[0] == "fileopenbox": _demo_fileopenbox()
2129 elif reply[0] == "filesavebox": _demo_filesavebox()
2130
2131 elif reply[0] == "indexbox":
2132 title = reply[0]
2133 msg = "Demo of " + reply[0]
2134 choices = ["Choice1", "Choice2", "Choice3", "Choice4"]
2135 reply = indexbox(msg, title, choices)
2136 writeln("Reply was: %s" % repr(reply))
2137
2138 elif reply[0] == "passwordbox":
2139 reply = passwordbox("Demo of password box WITHOUT default"
2140 + "\n\nEnter your secret password", "Member Logon")
2141 writeln("Reply was: %s" % str(reply))
2142
2143 reply = passwordbox("Demo of password box WITH default"
2144 + "\n\nEnter your secret password", "Member Logon", "alfie")
2145 writeln("Reply was: %s" % str(reply))
2146
2147 elif reply[0] == "multenterbox":
2148 msg = "Enter your personal information"
2149 title = "Credit Card Application"
2150 fieldNames = ["Name","Street Address","City","State","ZipCode"]
2151 fieldValues = []
2152 fieldValues = multenterbox(msg,title, fieldNames)
2153
2154
2155 while 1:
2156 if fieldValues == None: break
2157 errmsg = ""
2158 for i in range(len(fieldNames)):
2159 if fieldValues[i].strip() == "":
2160 errmsg = errmsg + ('"%s" is a required field.\n\n' % fieldNames[i])
2161 if errmsg == "": break
2162 fieldValues = multenterbox(errmsg, title, fieldNames, fieldValues)
2163
2164 writeln("Reply was: %s" % str(fieldValues))
2165
2166 elif reply[0] == "multpasswordbox":
2167 msg = "Enter logon information"
2168 title = "Demo of multpasswordbox"
2169 fieldNames = ["Server ID", "User ID", "Password"]
2170 fieldValues = []
2171 fieldValues = multpasswordbox(msg,title, fieldNames)
2172
2173
2174 while 1:
2175 if fieldValues == None: break
2176 errmsg = ""
2177 for i in range(len(fieldNames)):
2178 if fieldValues[i].strip() == "":
2179 errmsg = errmsg + ('"%s" is a required field.\n\n' % fieldNames[i])
2180 if errmsg == "": break
2181 fieldValues = multpasswordbox(errmsg, title, fieldNames, fieldValues)
2182
2183 writeln("Reply was: %s" % str(fieldValues))
2184
2185 elif reply[0] == "ynbox":
2186 title = "Demo of ynbox"
2187 msg = "Were you expecting the Spanish Inquisition?"
2188 reply = ynbox(msg, title)
2189 writeln("Reply was: %s" % repr(reply))
2190 if reply:
2191 msgbox("NOBODY expects the Spanish Inquisition!", "Wrong!")
2192
2193 elif reply[0] == "ccbox":
2194 title = "Demo of ccbox"
2195 reply = ccbox(msg,title)
2196 writeln("Reply was: %s" % repr(reply))
2197
2198 elif reply[0] == "choicebox":
2199 title = "Demo of choicebox"
2200 longchoice = "This is an example of a very long option which you may or may not wish to choose."*2
2201 listChoices = ["nnn", "ddd", "eee", "fff", "aaa", longchoice
2202 , "aaa", "bbb", "ccc", "ggg", "hhh", "iii", "jjj", "kkk", "LLL", "mmm" , "nnn", "ooo", "ppp", "qqq", "rrr", "sss", "ttt", "uuu", "vvv"]
2203
2204 msg = "Pick something. " + ("A wrapable sentence of text ?! "*30) + "\nA separate line of text."*6
2205 reply = choicebox(msg=msg, choices=listChoices)
2206 writeln("Reply was: %s" % repr(reply))
2207
2208 msg = "Pick something. "
2209 reply = choicebox(msg=msg, title=title, choices=listChoices)
2210 writeln("Reply was: %s" % repr(reply))
2211
2212 msg = "Pick something. "
2213 reply = choicebox(msg="The list of choices is empty!", choices=[])
2214 writeln("Reply was: %s" % repr(reply))
2215
2216 elif reply[0] == "multchoicebox":
2217 listChoices = ["aaa", "bbb", "ccc", "ggg", "hhh", "iii", "jjj", "kkk"
2218 , "LLL", "mmm" , "nnn", "ooo", "ppp", "qqq"
2219 , "rrr", "sss", "ttt", "uuu", "vvv"]
2220
2221 msg = "Pick as many choices as you wish."
2222 reply = multchoicebox(msg,"Demo of multchoicebox", listChoices)
2223 writeln("Reply was: %s" % repr(reply))
2224
2225 elif reply[0] == "textbox": _demo_textbox(reply[0])
2226 elif reply[0] == "codebox": _demo_codebox(reply[0])
2227
2228 else:
2229 msgbox("Choice\n\n" + choice + "\n\nis not recognized", "Program Logic Error")
2230 return
2231
2232
2233 -def _demo_textbox(reply):
2234 text_snippet = ((\
2235 """It was the best of times, and it was the worst of times. The rich ate cake, and the poor had cake recommended to them, but wished only for enough cash to buy bread. The time was ripe for revolution! """ \
2236 *5)+"\n\n")*10
2237 title = "Demo of textbox"
2238 msg = "Here is some sample text. " * 16
2239 reply = textbox(msg, title, text_snippet)
2240 writeln("Reply was: %s" % str(reply))
2241
2243 code_snippet = ("dafsdfa dasflkj pp[oadsij asdfp;ij asdfpjkop asdfpok asdfpok asdfpok"*3) +"\n"+\
2244 """# here is some dummy Python code
2245 for someItem in myListOfStuff:
2246 do something(someItem)
2247 do something()
2248 do something()
2249 if somethingElse(someItem):
2250 doSomethingEvenMoreInteresting()
2251
2252 """*16
2253 msg = "Here is some sample code. " * 16
2254 reply = codebox(msg, "Code Sample", code_snippet)
2255 writeln("Reply was: %s" % repr(reply))
2256
2257
2271
2272
2274 savedStdout = sys.stdout
2275 sys.stdout = capturedOutput = StringIO()
2276 help("easygui")
2277 sys.stdout = savedStdout
2278 codebox("EasyGui Help",text=capturedOutput.getvalue())
2279
2281 filename = "myNewFile.txt"
2282 title = "File SaveAs"
2283 msg ="Save file as:"
2284
2285 f = filesavebox(msg,title,default=filename)
2286 writeln("You chose to save file: %s" % f)
2287
2289 title = "Demo of diropenbox"
2290 msg = "Pick the directory that you wish to open."
2291 d = diropenbox(msg, title)
2292 writeln("You chose directory...: %s" % d)
2293
2294 d = diropenbox(msg, title,default="./")
2295 writeln("You chose directory...: %s" % d)
2296
2297 d = diropenbox(msg, title,default="c:/")
2298 writeln("You chose directory...: %s" % d)
2299
2300
2302 msg = "Python files"
2303 title = "Open files"
2304 default="*.py"
2305 f = fileopenbox(msg,title,default=default)
2306 writeln("You chose to open file: %s" % f)
2307
2308 default="./*.gif"
2309 filetypes = ["*.jpg",["*.zip","*.tgs","*.gz", "Archive files"],["*.htm", "*.html","HTML files"]]
2310 f = fileopenbox(msg,title,default=default,filetypes=filetypes)
2311 writeln("You chose to open file: %s" % f)
2312
2313 """#deadcode -- testing ----------------------------------------
2314 f = fileopenbox(None,None,default=default)
2315 writeln("You chose to open file: %s" % f)
2316
2317 f = fileopenbox(None,title,default=default)
2318 writeln("You chose to open file: %s" % f)
2319
2320 f = fileopenbox(msg,None,default=default)
2321 writeln("You chose to open file: %s" % f)
2322
2323 f = fileopenbox(default=default)
2324 writeln("You chose to open file: %s" % f)
2325
2326 f = fileopenbox(default=None)
2327 writeln("You chose to open file: %s" % f)
2328 #----------------------------------------------------deadcode """
2329
2330
2333
2334 EASYGUI_ABOUT_INFORMATION = '''
2335 ========================================================================
2336 0.96(2010-08-29)
2337 ========================================================================
2338 This version fixes some problems with version independence.
2339
2340 BUG FIXES
2341 ------------------------------------------------------
2342 * A statement with Python 2.x-style exception-handling syntax raised
2343 a syntax error when running under Python 3.x.
2344 Thanks to David Williams for reporting this problem.
2345
2346 * Under some circumstances, PIL was unable to display non-gif images
2347 that it should have been able to display.
2348 The cause appears to be non-version-independent import syntax.
2349 PIL modules are now imported with a version-independent syntax.
2350 Thanks to Horst Jens for reporting this problem.
2351
2352 LICENSE CHANGE
2353 ------------------------------------------------------
2354 Starting with this version, EasyGui is licensed under what is generally known as
2355 the "modified BSD license" (aka "revised BSD", "new BSD", "3-clause BSD").
2356 This license is GPL-compatible but less restrictive than GPL.
2357 Earlier versions were licensed under the Creative Commons Attribution License 2.0.
2358
2359
2360 ========================================================================
2361 0.95(2010-06-12)
2362 ========================================================================
2363
2364 ENHANCEMENTS
2365 ------------------------------------------------------
2366 * Previous versions of EasyGui could display only .gif image files using the
2367 msgbox "image" argument. This version can now display all image-file formats
2368 supported by PIL the Python Imaging Library) if PIL is installed.
2369 If msgbox is asked to open a non-gif image file, it attempts to import
2370 PIL and to use PIL to convert the image file to a displayable format.
2371 If PIL cannot be imported (probably because PIL is not installed)
2372 EasyGui displays an error message saying that PIL must be installed in order
2373 to display the image file.
2374
2375 Note that
2376 http://www.pythonware.com/products/pil/
2377 says that PIL doesn't yet support Python 3.x.
2378
2379
2380 ========================================================================
2381 0.94(2010-06-06)
2382 ========================================================================
2383
2384 ENHANCEMENTS
2385 ------------------------------------------------------
2386 * The codebox and textbox functions now return the contents of the box, rather
2387 than simply the name of the button ("Yes"). This makes it possible to use
2388 codebox and textbox as data-entry widgets. A big "thank you!" to Dominic
2389 Comtois for requesting this feature, patiently explaining his requirement,
2390 and helping to discover the tkinter techniques to implement it.
2391
2392 NOTE THAT in theory this change breaks backward compatibility. But because
2393 (in previous versions of EasyGui) the value returned by codebox and textbox
2394 was meaningless, no application should have been checking it. So in actual
2395 practice, this change should not break backward compatibility.
2396
2397 * Added support for SPACEBAR to command buttons. Now, when keyboard
2398 focus is on a command button, a press of the SPACEBAR will act like
2399 a press of the ENTER key; it will activate the command button.
2400
2401 * Added support for keyboard navigation with the arrow keys (up,down,left,right)
2402 to the fields and buttons in enterbox, multenterbox and multpasswordbox,
2403 and to the buttons in choicebox and all buttonboxes.
2404
2405 * added highlightthickness=2 to entry fields in multenterbox and
2406 multpasswordbox. Now it is easier to tell which entry field has
2407 keyboard focus.
2408
2409
2410 BUG FIXES
2411 ------------------------------------------------------
2412 * In EgStore, the pickle file is now opened with "rb" and "wb" rather than
2413 with "r" and "w". This change is necessary for compatibility with Python 3+.
2414 Thanks to Marshall Mattingly for reporting this problem and providing the fix.
2415
2416 * In integerbox, the actual argument names did not match the names described
2417 in the docstring. Thanks to Daniel Zingaro of at University of Toronto for
2418 reporting this problem.
2419
2420 * In integerbox, the "argLowerBound" and "argUpperBound" arguments have been
2421 renamed to "lowerbound" and "upperbound" and the docstring has been corrected.
2422
2423 NOTE THAT THIS CHANGE TO THE ARGUMENT-NAMES BREAKS BACKWARD COMPATIBILITY.
2424 If argLowerBound or argUpperBound are used, an AssertionError with an
2425 explanatory error message is raised.
2426
2427 * In choicebox, the signature to choicebox incorrectly showed choicebox as
2428 accepting a "buttons" argument. The signature has been fixed.
2429
2430
2431 ========================================================================
2432 0.93(2009-07-07)
2433 ========================================================================
2434
2435 ENHANCEMENTS
2436 ------------------------------------------------------
2437
2438 * Added exceptionbox to display stack trace of exceptions
2439
2440 * modified names of some font-related constants to make it
2441 easier to customize them
2442
2443
2444 ========================================================================
2445 0.92(2009-06-22)
2446 ========================================================================
2447
2448 ENHANCEMENTS
2449 ------------------------------------------------------
2450
2451 * Added EgStore class to to provide basic easy-to-use persistence.
2452
2453 BUG FIXES
2454 ------------------------------------------------------
2455
2456 * Fixed a bug that was preventing Linux users from copying text out of
2457 a textbox and a codebox. This was not a problem for Windows users.
2458
2459 '''
2460
2467
2468
2469
2470 if __name__ == '__main__':
2471 if True:
2472 egdemo()
2473 else:
2474
2475 root = Tk()
2476 msg = """This is a test of a main Tk() window in which we will place an easygui msgbox.
2477 It will be an interesting experiment.\n\n"""
2478 messageWidget = Message(root, text=msg, width=1000)
2479 messageWidget.pack(side=TOP, expand=YES, fill=X, padx='3m', pady='3m')
2480 messageWidget = Message(root, text=msg, width=1000)
2481 messageWidget.pack(side=TOP, expand=YES, fill=X, padx='3m', pady='3m')
2482
2483
2484 msgbox("this is a test of passing in boxRoot", root=root)
2485 msgbox("this is a second test of passing in boxRoot", root=root)
2486
2487 reply = enterbox("Enter something", root=root)
2488 writeln("You wrote:", reply)
2489
2490 reply = enterbox("Enter something else", root=root)
2491 writeln("You wrote:", reply)
2492 root.destroy()
2493