Commit 435baf80068ec33b230fe02972ddc43c312306fb

Authored by decalage2
1 parent afdeca24

easygui is now an external dependency, obsolete copy removed from thirdparty folder

oletools/ezhexviewer.py
... ... @@ -48,8 +48,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 48 # 2016-10-26 PL: - fixed to run on Python 2+3
49 49 # 2017-03-23 v0.51 PL: - fixed display of control characters (issue #151)
50 50 # 2017-04-26 PL: - fixed absolute imports (issue #141)
  51 +# 2018-09-15 v0.54 PL: - easygui is now a dependency
51 52  
52   -__version__ = '0.51'
  53 +__version__ = '0.54dev1'
53 54  
54 55 #-----------------------------------------------------------------------------
55 56 # TODO:
... ... @@ -71,7 +72,7 @@ _parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..'))
71 72 if not _parent_dir in sys.path:
72 73 sys.path.insert(0, _parent_dir)
73 74  
74   -from oletools.thirdparty.easygui import easygui
  75 +import easygui
75 76  
76 77 # === PYTHON 2+3 SUPPORT ======================================================
77 78  
... ...
oletools/olebrowse.py
... ... @@ -69,7 +69,7 @@ _parent_dir = os.path.normpath(os.path.join(_thismodule_dir, '..'))
69 69 if not _parent_dir in sys.path:
70 70 sys.path.insert(0, _parent_dir)
71 71  
72   -from oletools.thirdparty.easygui import easygui
  72 +import easygui
73 73 import olefile
74 74 from oletools import ezhexviewer
75 75  
... ...
oletools/thirdparty/easygui/LICENSE.txt deleted
1   -LICENSE INFORMATION
2   -
3   -EasyGui version 0.96
4   -
5   -Copyright (c) 2010, Stephen Raymond Ferg
6   -
7   -All rights reserved.
8   -
9   -Redistribution and use in source and binary forms, with or without modification,
10   -are permitted provided that the following conditions are met:
11   -
12   - 1. Redistributions of source code must retain the above copyright notice,
13   - this list of conditions and the following disclaimer.
14   -
15   - 2. Redistributions in binary form must reproduce the above copyright notice,
16   - this list of conditions and the following disclaimer in the documentation and/or
17   - other materials provided with the distribution.
18   -
19   - 3. The name of the author may not be used to endorse or promote products derived
20   - from this software without specific prior written permission.
21   -
22   -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS"
23   -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
24   -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25   -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26   -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27   -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28   -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29   -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
30   -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
31   -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32   -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
oletools/thirdparty/easygui/__init__.py deleted
oletools/thirdparty/easygui/easygui.py deleted
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   -# check python version and take appropriate action
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   -
154   -def write(*args):
155   - args = [str(arg) for arg in args]
156   - args = " ".join(args)
157   - sys.stdout.write(args)
158   -
159   -def writeln(*args):
160   - write(*args)
161   - sys.stdout.write("\n")
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   -
175   -def dq(s):
176   - return '"%s"' % s
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 #a little smaller, because it it more legible at a smaller size
185   -TEXT_ENTRY_FONT_SIZE = 12 # a little larger makes it easier to see
186   -
187   -#STANDARD_SELECTION_EVENTS = ["Return", "Button-1"]
188   -STANDARD_SELECTION_EVENTS = ["Return", "Button-1", "space"]
189   -
190   -# Initialize some global variables that will be reset later
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   -# various boxes built on top of the basic buttonbox
208   -#-----------------------------------------------------------------------
209   -
210   -#-----------------------------------------------------------------------
211   -# ynbox
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   -# ccbox
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   -# boolbox
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   -# indexbox
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   -# msgbox
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   -# buttonbox
335   -#-------------------------------------------------------------------
336   -def buttonbox(msg="",title=" "
337   - ,choices=("Button1", "Button2", "Button3")
338   - , image=None
339   - , root=None
340   - ):
341   - """
342   - Display a msg, a title, and a set of buttons.
343   - The buttons are defined by the members of the choices list.
344   - Return the text of the button that the user selected.
345   -
346   - @arg msg: the msg to be displayed.
347   - @arg title: the window title
348   - @arg choices: a list or tuple of the choices to be displayed
349   - """
350   - global boxRoot, __replyButtonText, __widgetTexts, buttonsFrame
351   -
352   -
353   - # Initialize __replyButtonText to the first choice.
354   - # This is what will be used if the window is closed by the close button.
355   - __replyButtonText = choices[0]
356   -
357   - if root:
358   - root.withdraw()
359   - boxRoot = Toplevel(master=root)
360   - boxRoot.withdraw()
361   - else:
362   - boxRoot = Tk()
363   - boxRoot.withdraw()
364   -
365   - boxRoot.protocol('WM_DELETE_WINDOW', denyWindowManagerClose )
366   - boxRoot.title(title)
367   - boxRoot.iconname('Dialog')
368   - boxRoot.geometry(rootWindowPosition)
369   - boxRoot.minsize(400, 100)
370   -
371   - # ------------- define the messageFrame ---------------------------------
372   - messageFrame = Frame(master=boxRoot)
373   - messageFrame.pack(side=TOP, fill=BOTH)
374   -
375   - # ------------- define the imageFrame ---------------------------------
376   - tk_Image = None
377   - if image:
378   - imageFilename = os.path.normpath(image)
379   - junk,ext = os.path.splitext(imageFilename)
380   -
381   - if os.path.exists(imageFilename):
382   - if ext.lower() in [".gif", ".pgm", ".ppm"]:
383   - tk_Image = PhotoImage(master=boxRoot, file=imageFilename)
384   - else:
385   - if PILisLoaded:
386   - try:
387   - pil_Image = PILImage.open(imageFilename)
388   - tk_Image = PILImageTk.PhotoImage(pil_Image, master=boxRoot)
389   - except:
390   - msg += ImageErrorMsg % (imageFilename,
391   - "\nThe Python Imaging Library (PIL) could not convert this file to a displayable image."
392   - "\n\nPIL reports:\n" + exception_format())
393   -
394   - else: # PIL is not loaded
395   - msg += ImageErrorMsg % (imageFilename,
396   - "\nI could not import the Python Imaging Library (PIL) to display the image.\n\n"
397   - "You may need to install PIL\n"
398   - "(http://www.pythonware.com/products/pil/)\n"
399   - "to display " + ext + " image files.")
400   -
401   - else:
402   - msg += ImageErrorMsg % (imageFilename, "\nImage file not found.")
403   -
404   - if tk_Image:
405   - imageFrame = Frame(master=boxRoot)
406   - imageFrame.pack(side=TOP, fill=BOTH)
407   - label = Label(imageFrame,image=tk_Image)
408   - label.image = tk_Image # keep a reference!
409   - label.pack(side=TOP, expand=YES, fill=X, padx='1m', pady='1m')
410   -
411   - # ------------- define the buttonsFrame ---------------------------------
412   - buttonsFrame = Frame(master=boxRoot)
413   - buttonsFrame.pack(side=TOP, fill=BOTH)
414   -
415   - # -------------------- place the widgets in the frames -----------------------
416   - messageWidget = Message(messageFrame, text=msg, width=400)
417   - messageWidget.configure(font=(PROPORTIONAL_FONT_FAMILY,PROPORTIONAL_FONT_SIZE))
418   - messageWidget.pack(side=TOP, expand=YES, fill=X, padx='3m', pady='3m')
419   -
420   - __put_buttons_in_buttonframe(choices)
421   -
422   - # -------------- the action begins -----------
423   - # put the focus on the first button
424   - __firstWidget.focus_force()
425   -
426   - boxRoot.deiconify()
427   - boxRoot.mainloop()
428   - boxRoot.destroy()
429   - if root: root.deiconify()
430   - return __replyButtonText
431   -
432   -
433   -#-------------------------------------------------------------------
434   -# integerbox
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   - # reply has passed all validation checks.
519   - # It is an integer between the specified bounds.
520   - return reply
521   -
522   -#-------------------------------------------------------------------
523   -# multenterbox
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   -# multpasswordbox
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   -
611   -def bindArrows(widget):
612   - widget.bind("<Down>", tabRight)
613   - widget.bind("<Up>" , tabLeft)
614   -
615   - widget.bind("<Right>",tabRight)
616   - widget.bind("<Left>" , tabLeft)
617   -
618   -def tabRight(event):
619   - boxRoot.event_generate("<Tab>")
620   -
621   -def tabLeft(event):
622   - boxRoot.event_generate("<Shift-Tab>")
623   -
624   -#-----------------------------------------------------------------------
625   -# __multfillablebox
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[:]) # convert possible tuples to a list
639   - values = list(values[:]) # convert possible tuples to a list
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   - # -------------------- put subframes in the boxRoot --------------------
657   - messageFrame = Frame(master=boxRoot)
658   - messageFrame.pack(side=TOP, fill=BOTH)
659   -
660   - #-------------------- the msg widget ----------------------------
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   - # --------- entryWidget ----------------------------------------------
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   - # for the last entryWidget, if this is a multpasswordbox,
691   - # show the contents as just asterisks
692   - if widgetIndex == lastWidgetIndex:
693   - if mask:
694   - entryWidgets[widgetIndex].configure(show=mask)
695   -
696   - # put text into the entryWidget
697   - entryWidgets[widgetIndex].insert(0,argFieldValue)
698   - widgetIndex += 1
699   -
700   - # ------------------ ok button -------------------------------
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   - # for the commandButton, bind activation events to the activation event handler
709   - commandButton = okButton
710   - handler = __multenterboxGetText
711   - for selectionEvent in STANDARD_SELECTION_EVENTS:
712   - commandButton.bind("<%s>" % selectionEvent, handler)
713   -
714   -
715   - # ------------------ cancel button -------------------------------
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   - # for the commandButton, bind activation events to the activation event handler
721   - commandButton = cancelButton
722   - handler = __multenterboxCancel
723   - for selectionEvent in STANDARD_SELECTION_EVENTS:
724   - commandButton.bind("<%s>" % selectionEvent, handler)
725   -
726   -
727   - # ------------------- time for action! -----------------
728   - entryWidgets[0].focus_force() # put the focus on the entryWidget
729   - boxRoot.mainloop() # run it!
730   -
731   - # -------- after the run has completed ----------------------------------
732   - boxRoot.destroy() # button_click didn't destroy boxRoot, so we do it now
733   - return __multenterboxText
734   -
735   -
736   -#-----------------------------------------------------------------------
737   -# __multenterboxGetText
738   -#-----------------------------------------------------------------------
739   -def __multenterboxGetText(event):
740   - global __multenterboxText
741   -
742   - __multenterboxText = []
743   - for entryWidget in entryWidgets:
744   - __multenterboxText.append(entryWidget.get())
745   - boxRoot.quit()
746   -
747   -
748   -def __multenterboxCancel(event):
749   - global __multenterboxText
750   - __multenterboxText = None
751   - boxRoot.quit()
752   -
753   -
754   -#-------------------------------------------------------------------
755   -# enterbox
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   - # ------------- define the messageFrame ---------------------------------
839   - messageFrame = Frame(master=boxRoot)
840   - messageFrame.pack(side=TOP, fill=BOTH)
841   -
842   - # ------------- define the imageFrame ---------------------------------
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: # PIL is not loaded
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 # keep a reference!
876   - label.pack(side=TOP, expand=YES, fill=X, padx='1m', pady='1m')
877   -
878   - # ------------- define the buttonsFrame ---------------------------------
879   - buttonsFrame = Frame(master=boxRoot)
880   - buttonsFrame.pack(side=TOP, fill=BOTH)
881   -
882   -
883   - # ------------- define the entryFrame ---------------------------------
884   - entryFrame = Frame(master=boxRoot)
885   - entryFrame.pack(side=TOP, fill=BOTH)
886   -
887   - # ------------- define the buttonsFrame ---------------------------------
888   - buttonsFrame = Frame(master=boxRoot)
889   - buttonsFrame.pack(side=TOP, fill=BOTH)
890   -
891   - #-------------------- the msg widget ----------------------------
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   - # --------- entryWidget ----------------------------------------------
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   - # put text into the entryWidget
906   - entryWidget.insert(0,__enterboxDefaultText)
907   -
908   - # ------------------ ok button -------------------------------
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   - # for the commandButton, bind activation events to the activation event handler
914   - commandButton = okButton
915   - handler = __enterboxGetText
916   - for selectionEvent in STANDARD_SELECTION_EVENTS:
917   - commandButton.bind("<%s>" % selectionEvent, handler)
918   -
919   -
920   - # ------------------ cancel button -------------------------------
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   - # for the commandButton, bind activation events to the activation event handler
926   - commandButton = cancelButton
927   - handler = __enterboxCancel
928   - for selectionEvent in STANDARD_SELECTION_EVENTS:
929   - commandButton.bind("<%s>" % selectionEvent, handler)
930   -
931   - # ------------------- time for action! -----------------
932   - entryWidget.focus_force() # put the focus on the entryWidget
933   - boxRoot.deiconify()
934   - boxRoot.mainloop() # run it!
935   -
936   - # -------- after the run has completed ----------------------------------
937   - if root: root.deiconify()
938   - boxRoot.destroy() # button_click didn't destroy boxRoot, so we do it now
939   - return __enterboxText
940   -
941   -
942   -def __enterboxGetText(event):
943   - global __enterboxText
944   -
945   - __enterboxText = entryWidget.get()
946   - boxRoot.quit()
947   -
948   -
949   -def __enterboxRestore(event):
950   - global entryWidget
951   -
952   - entryWidget.delete(0,len(entryWidget.get()))
953   - entryWidget.insert(0, __enterboxDefaultText)
954   -
955   -
956   -def __enterboxCancel(event):
957   - global __enterboxText
958   -
959   - __enterboxText = None
960   - boxRoot.quit()
961   -
962   -def denyWindowManagerClose():
963   - """ don't allow WindowManager close
964   - """
965   - x = Tk()
966   - x.withdraw()
967   - x.bell()
968   - x.destroy()
969   -
970   -
971   -
972   -#-------------------------------------------------------------------
973   -# multchoicebox
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   -# choicebox
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   -# __choicebox
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   - # If choices is a tuple, we make it a list so we can sort it.
1034   - # If choices is already a list, we make a new list, so that when
1035   - # we sort the choices, we don't affect the list object that we
1036   - # were given.
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   - # make sure all choices are strings
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   - # Initialize __choiceboxResults
1053   - # This is the value that will be returned if the user clicks the close icon
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   - # ---------------- put the frames in the window -----------------------------------------
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   - #messageFrame.pack(side=TOP, fill=X, expand=YES)
1081   -
1082   - buttonsFrame = Frame(message_and_buttonsFrame)
1083   - buttonsFrame.pack(side=RIGHT, expand=NO, pady=0)
1084   - #buttonsFrame.pack(side=TOP, expand=YES, pady=0)
1085   -
1086   - choiceboxFrame = Frame(master=boxRoot)
1087   - choiceboxFrame.pack(side=BOTTOM, fill=BOTH, expand=YES)
1088   -
1089   - # -------------------------- put the widgets in the frames ------------------------------
1090   -
1091   - # ---------- put a msg widget in the msg frame-------------------
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   - # -------- put the choiceboxWidget in the choiceboxFrame ---------------------------
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   - # add a vertical scrollbar to the frame
1110   - rightScrollbar = Scrollbar(choiceboxFrame, orient=VERTICAL, command=choiceboxWidget.yview)
1111   - choiceboxWidget.configure(yscrollcommand = rightScrollbar.set)
1112   -
1113   - # add a horizontal scrollbar to the frame
1114   - bottomScrollbar = Scrollbar(choiceboxFrame, orient=HORIZONTAL, command=choiceboxWidget.xview)
1115   - choiceboxWidget.configure(xscrollcommand = bottomScrollbar.set)
1116   -
1117   - # pack the Listbox and the scrollbars. Note that although we must define
1118   - # the textArea first, we must pack it last, so that the bottomScrollbar will
1119   - # be located properly.
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   - # sort the choices
1128   - # eliminate duplicates
1129   - # put the choices into the choiceboxWidget
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())) # case-insensitive sort
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   - # put the buttons in the buttonsFrame
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   - # for the commandButton, bind activation events to the activation event handler
1157   - commandButton = okButton
1158   - handler = __choiceboxGetChoice
1159   - for selectionEvent in STANDARD_SELECTION_EVENTS:
1160   - commandButton.bind("<%s>" % selectionEvent, handler)
1161   -
1162   - # now bind the keyboard events
1163   - choiceboxWidget.bind("<Return>", __choiceboxGetChoice)
1164   - choiceboxWidget.bind("<Double-Button-1>", __choiceboxGetChoice)
1165   - else:
1166   - # now bind the keyboard events
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   - # for the commandButton, bind activation events to the activation event handler
1175   - commandButton = cancelButton
1176   - handler = __choiceboxCancel
1177   - for selectionEvent in STANDARD_SELECTION_EVENTS:
1178   - commandButton.bind("<%s>" % selectionEvent, handler)
1179   -
1180   -
1181   - # add special buttons for multiple select features
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   - # -------------------- bind some keyboard events ----------------------------
1199   - boxRoot.bind("<Escape>", __choiceboxCancel)
1200   -
1201   - # --------------------- the action begins -----------------------------------
1202   - # put the focus on the choiceboxWidget, and the select highlight on the first item
1203   - choiceboxWidget.select_set(0)
1204   - choiceboxWidget.focus_force()
1205   -
1206   - # --- run it! -----
1207   - boxRoot.mainloop()
1208   -
1209   - boxRoot.destroy()
1210   - return __choiceboxResults
1211   -
1212   -
1213   -def __choiceboxGetChoice(event):
1214   - global boxRoot, __choiceboxResults, choiceboxWidget
1215   -
1216   - if __choiceboxMultipleSelect:
1217   - __choiceboxResults = [choiceboxWidget.get(index) for index in choiceboxWidget.curselection()]
1218   -
1219   - else:
1220   - choice_index = choiceboxWidget.curselection()
1221   - __choiceboxResults = choiceboxWidget.get(choice_index)
1222   -
1223   - # writeln("Debugging> mouse-event=", event, " event.type=", event.type)
1224   - # writeln("Debugging> choice=", choice_index, __choiceboxResults)
1225   - boxRoot.quit()
1226   -
1227   -
1228   -def __choiceboxSelectAll(event):
1229   - global choiceboxWidget, choiceboxChoices
1230   -
1231   - choiceboxWidget.selection_set(0, len(choiceboxChoices)-1)
1232   -
1233   -def __choiceboxClearAll(event):
1234   - global choiceboxWidget, choiceboxChoices
1235   -
1236   - choiceboxWidget.selection_clear(0, len(choiceboxChoices)-1)
1237   -
1238   -
1239   -
1240   -def __choiceboxCancel(event):
1241   - global boxRoot, __choiceboxResults
1242   -
1243   - __choiceboxResults = None
1244   - boxRoot.quit()
1245   -
1246   -
1247   -def KeyboardListener(event):
1248   - global choiceboxChoices, choiceboxWidget
1249   - key = event.keysym
1250   - if len(key) <= 1:
1251   - if key in string.printable:
1252   - # Find the key in the list.
1253   - # before we clear the list, remember the selected member
1254   - try:
1255   - start_n = int(choiceboxWidget.curselection()[0])
1256   - except IndexError:
1257   - start_n = -1
1258   -
1259   - ## clear the selection.
1260   - choiceboxWidget.selection_clear(0, 'end')
1261   -
1262   - ## start from previous selection +1
1263   - for n in range(start_n+1, len(choiceboxChoices)):
1264   - item = choiceboxChoices[n]
1265   - if item[0].lower() == key.lower():
1266   - choiceboxWidget.selection_set(first=n)
1267   - choiceboxWidget.see(n)
1268   - return
1269   - else:
1270   - # has not found it so loop from top
1271   - for n in range(len(choiceboxChoices)):
1272   - item = choiceboxChoices[n]
1273   - if item[0].lower() == key.lower():
1274   - choiceboxWidget.selection_set(first = n)
1275   - choiceboxWidget.see(n)
1276   - return
1277   -
1278   - # nothing matched -- we'll look for the next logical choice
1279   - for n in range(len(choiceboxChoices)):
1280   - item = choiceboxChoices[n]
1281   - if item[0].lower() > key.lower():
1282   - if n > 0:
1283   - choiceboxWidget.selection_set(first = (n-1))
1284   - else:
1285   - choiceboxWidget.selection_set(first = 0)
1286   - choiceboxWidget.see(n)
1287   - return
1288   -
1289   - # still no match (nothing was greater than the key)
1290   - # we set the selection to the first item in the list
1291   - lastIndex = len(choiceboxChoices)-1
1292   - choiceboxWidget.selection_set(first = lastIndex)
1293   - choiceboxWidget.see(lastIndex)
1294   - return
1295   -
1296   -#-----------------------------------------------------------------------
1297   -# exception_format
1298   -#-----------------------------------------------------------------------
1299   -def exception_format():
1300   - """
1301   - Convert exception info into a string suitable for display.
1302   - """
1303   - return "".join(traceback.format_exception(
1304   - sys.exc_info()[0]
1305   - , sys.exc_info()[1]
1306   - , sys.exc_info()[2]
1307   - ))
1308   -
1309   -#-----------------------------------------------------------------------
1310   -# exceptionbox
1311   -#-----------------------------------------------------------------------
1312   -def exceptionbox(msg=None, title=None):
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   -# codebox
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   -# textbox
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   - # ---- put frames in the window -----------------------------------
1396   - # we pack the textboxFrame first, so it will expand first
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   - # -------------------- put widgets in the frames --------------------
1410   -
1411   - # put a textArea in the top frame
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   - # some simple keybindings for scrolling
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   - # add a vertical scrollbar to the frame
1443   - rightScrollbar = Scrollbar(textboxFrame, orient=VERTICAL, command=textArea.yview)
1444   - textArea.configure(yscrollcommand = rightScrollbar.set)
1445   -
1446   - # add a horizontal scrollbar to the frame
1447   - bottomScrollbar = Scrollbar(textboxFrame, orient=HORIZONTAL, command=textArea.xview)
1448   - textArea.configure(xscrollcommand = bottomScrollbar.set)
1449   -
1450   - # pack the textArea and the scrollbars. Note that although we must define
1451   - # the textArea first, we must pack it last, so that the bottomScrollbar will
1452   - # be located properly.
1453   -
1454   - # Note that we need a bottom scrollbar only for code.
1455   - # Text will be displayed with wordwrap, so we don't need to have a horizontal
1456   - # scroll for it.
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   - # ---------- put a msg widget in the msg frame-------------------
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   - # put the buttons in the buttonsFrame
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   - # for the commandButton, bind activation events to the activation event handler
1474   - commandButton = okButton
1475   - handler = __textboxOK
1476   - for selectionEvent in ["Return","Button-1","Escape"]:
1477   - commandButton.bind("<%s>" % selectionEvent, handler)
1478   -
1479   -
1480   - # ----------------- the action begins ----------------------------------------
1481   - try:
1482   - # load the text into the textArea
1483   - if type(text) == type("abc"): pass
1484   - else:
1485   - try:
1486   - text = "".join(text) # convert a list or a tuple to a string
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   - # this line MUST go before the line that destroys boxRoot
1505   - areaText = textArea.get(0.0,END)
1506   - boxRoot.destroy()
1507   - return areaText # return __replyButtonText
1508   -
1509   -#-------------------------------------------------------------------
1510   -# __textboxOK
1511   -#-------------------------------------------------------------------
1512   -def __textboxOK(event):
1513   - global boxRoot
1514   - boxRoot.quit()
1515   -
1516   -
1517   -
1518   -#-------------------------------------------------------------------
1519   -# diropenbox
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   -# getFileDialogTitle
1552   -#-------------------------------------------------------------------
1553   -def getFileDialogTitle(msg
1554   - , title
1555   - ):
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 # no message and no title
1560   -
1561   -#-------------------------------------------------------------------
1562   -# class FileTypeObject for use with fileopenbox
1563   -#-------------------------------------------------------------------
1564   -class FileTypeObject:
1565   - def __init__(self,filemask):
1566   - if len(filemask) == 0:
1567   - raise AssertionError('Filetype argument is empty.')
1568   -
1569   - self.masks = []
1570   -
1571   - if type(filemask) == type("abc"): # a string
1572   - self.initializeFromString(filemask)
1573   -
1574   - elif type(filemask) == type([]): # a list
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   -
1584   - def __eq__(self,other):
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   -
1593   - def toTuple(self):
1594   - return (self.name,tuple(self.masks))
1595   -
1596   - def isAll(self):
1597   - if self.name == "All files": return True
1598   - return False
1599   -
1600   - def initializeFromString(self, filemask):
1601   - # remove everything except the extension from the filemask
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   -
1608   - def getName(self):
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   -# fileopenbox
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   - # if initialfile contains no wildcards; we don't want an
1691   - # initial file. It won't be used anyway.
1692   - # Also: if initialbase is simply "*", we don't want an
1693   - # initialfile; it is not doing any useful work.
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   -# filesavebox
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   -# fileboxSetup
1752   -#
1753   -#-------------------------------------------------------------------
1754   -def fileboxSetup(default,filetypes):
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 # remember this
1773   -
1774   - if fto == initialFileTypeObject:
1775   - initialFileTypeObject.add(fto) # add fto to initialFileTypeObject
1776   - else:
1777   - filetypeObjects.append(fto)
1778   -
1779   - #------------------------------------------------------------------
1780   - # make sure that the list of filetypes includes the ALL FILES type.
1781   - #------------------------------------------------------------------
1782   - if ALL_filetypes_was_specified:
1783   - pass
1784   - elif allFileTypeObject == initialFileTypeObject:
1785   - pass
1786   - else:
1787   - filetypeObjects.insert(0,allFileTypeObject)
1788   - #------------------------------------------------------------------
1789   - # Make sure that the list includes the initialFileTypeObject
1790   - # in the position in the list that will make it the default.
1791   - # This changed between Python version 2.5 and 2.6
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   -# utility routines
1810   -#-------------------------------------------------------------------
1811   -# These routines are used by several other functions in the EasyGui module.
1812   -
1813   -def __buttonEvent(event):
1814   - """
1815   - Handle an event that is generated by a person clicking a button.
1816   - """
1817   - global boxRoot, __widgetTexts, __replyButtonText
1818   - __replyButtonText = __widgetTexts[event.widget]
1819   - boxRoot.quit() # quit the main loop
1820   -
1821   -
1822   -def __put_buttons_in_buttonframe(choices):
1823   - """Put the buttons in the buttons frame
1824   - """
1825   - global __widgetTexts, __firstWidget, buttonsFrame
1826   -
1827   - __firstWidget = None
1828   - __widgetTexts = {}
1829   -
1830   - i = 0
1831   -
1832   - for buttonText in choices:
1833   - tempButton = Button(buttonsFrame, takefocus=1, text=buttonText)
1834   - bindArrows(tempButton)
1835   - tempButton.pack(expand=YES, side=LEFT, padx='1m', pady='1m', ipadx='2m', ipady='1m')
1836   -
1837   - # remember the text associated with this widget
1838   - __widgetTexts[tempButton] = buttonText
1839   -
1840   - # remember the first widget, so we can put the focus there
1841   - if i == 0:
1842   - __firstWidget = tempButton
1843   - i = 1
1844   -
1845   - # for the commandButton, bind activation events to the activation event handler
1846   - commandButton = tempButton
1847   - handler = __buttonEvent
1848   - for selectionEvent in STANDARD_SELECTION_EVENTS:
1849   - commandButton.bind("<%s>" % selectionEvent, handler)
1850   -
1851   -#-----------------------------------------------------------------------
1852   -#
1853   -# class EgStore
1854   -#
1855   -#-----------------------------------------------------------------------
1856   -class EgStore:
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   -"""
1915   - def __init__(self, filename): # obtaining filename is required
1916   - self.filename = None
1917   - raise NotImplementedError()
1918   -
1919   - def restore(self):
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   -
1965   - def store(self):
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   -
1976   - def kill(self):
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   -
1984   - def __str__(self):
1985   - """
1986   - return my contents as a string in an easy-to-read format.
1987   - """
1988   - # find the length of the longest attribute name
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() # sort the attribute names
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) # return a string showing the attributes
2002   -
2003   -
2004   -
2005   -
2006   -#-----------------------------------------------------------------------
2007   -#
2008   -# test/demo easygui
2009   -#
2010   -#-----------------------------------------------------------------------
2011   -def egdemo():
2012   - """
2013   - Run the EasyGui demo.
2014   - """
2015   - # clear the console
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   - #========================================== END DEMONSTRATION DATA
2025   -
2026   -
2027   - while 1: # do forever
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 = [] # we start with blanks for the values
2152   - fieldValues = multenterbox(msg,title, fieldNames)
2153   -
2154   - # make sure that none of the fields was left blank
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 # no problems found
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 = [] # we start with blanks for the values
2171   - fieldValues = multpasswordbox(msg,title, fieldNames)
2172   -
2173   - # make sure that none of the fields was left blank
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 # no problems found
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   -
2242   -def _demo_codebox(reply):
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   -
2258   -def _demo_buttonbox_with_image():
2259   -
2260   - msg = "Do you like this picture?\nIt is "
2261   - choices = ["Yes","No","No opinion"]
2262   -
2263   - for image in [
2264   - "python_and_check_logo.gif"
2265   - ,"python_and_check_logo.jpg"
2266   - ,"python_and_check_logo.png"
2267   - ,"zzzzz.gif"]:
2268   -
2269   - reply=buttonbox(msg + image,image=image,choices=choices)
2270   - writeln("Reply was: %s" % repr(reply))
2271   -
2272   -
2273   -def _demo_help():
2274   - savedStdout = sys.stdout # save the sys.stdout file object
2275   - sys.stdout = capturedOutput = StringIO()
2276   - help("easygui")
2277   - sys.stdout = savedStdout # restore the sys.stdout file object
2278   - codebox("EasyGui Help",text=capturedOutput.getvalue())
2279   -
2280   -def _demo_filesavebox():
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   -
2288   -def _demo_diropenbox():
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   -
2301   -def _demo_fileopenbox():
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   -
2331   -def _dummy():
2332   - pass
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   -
2461   -def abouteasygui():
2462   - """
2463   - shows the easygui revision history
2464   - """
2465   - codebox("About EasyGui\n"+egversion,"EasyGui",EASYGUI_ABOUT_INFORMATION)
2466   - return None
2467   -
2468   -
2469   -
2470   -if __name__ == '__main__':
2471   - if True:
2472   - egdemo()
2473   - else:
2474   - # test the new root feature
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()
setup.py
... ... @@ -24,6 +24,8 @@ to install this package.
24 24 # 2017-01-18 v0.51 PL: - added package zipfile27 (issue #121)
25 25 # 2017-10-18 v0.52 PL: - added msodde
26 26 # 2018-03-19 v0.52.3 PL: - added install_requires, removed thirdparty.pyparsing
  27 +# 2018-09-11 v0.54 PL: - olefile is now a dependency
  28 +# 2018-09-15 PL: - easygui is now a dependency
27 29  
28 30 #--- TODO ---------------------------------------------------------------------
29 31  
... ... @@ -79,7 +81,6 @@ packages=[
79 81 "oletools",
80 82 "oletools.common",
81 83 'oletools.thirdparty',
82   - 'oletools.thirdparty.easygui',
83 84 'oletools.thirdparty.xxxswf',
84 85 'oletools.thirdparty.prettytable',
85 86 'oletools.thirdparty.xglob',
... ... @@ -165,9 +166,6 @@ package_data={
165 166 'oletools.thirdparty.xglob': [
166 167 'LICENSE.txt',
167 168 ],
168   - 'oletools.thirdparty.easygui': [
169   - 'LICENSE.txt',
170   - ],
171 169 'oletools.thirdparty.xxxswf': [
172 170 'LICENSE.txt',
173 171 ],
... ... @@ -319,6 +317,7 @@ def main():
319 317 install_requires=[
320 318 "pyparsing>=2.2.0",
321 319 "olefile>=0.46",
  320 + "easygui",
322 321 ],
323 322 )
324 323  
... ...