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