Module easygui |
[hide private]
[frames] | no frames]

# Source Code for Module easygui

       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()
    2493