"Fossies" - the Fresh Open Source Software Archive

Member "twander-3.231/twander.py" (1 Jul 2009, 167748 Bytes) of package /linux/privat/old/twander-3.231.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 #!/usr/bin/env python
    2 # twander - Wander around the file system
    3 # Copyright (c) 2002-2009 TundraWare Inc.  All Rights Reserved.
    4 # For Updates See:  http://www.tundraware.com/Software/twander
    5 
    6 # Program Information
    7 
    8 PROGNAME = "twander"
    9 RCSID    = "$Id: twander.py,v 3.231 2009/07/01 07:29:20 tundra Exp $"
   10 VERSION  = RCSID.split()[2]
   11 
   12 # Copyright Information
   13 
   14 DATE         = "2002-2009"
   15 CPRT         = "(c)"
   16 OWNER        = "TundraWare Inc."
   17 RIGHTS       = "All Rights Reserved."
   18 COPYRIGHT    = "Copyright %s %s %s  %s" % (CPRT, DATE, OWNER, RIGHTS)
   19 
   20 
   21 #----------------------------------------------------------#
   22 #                     Imports                              #
   23 #----------------------------------------------------------#
   24 
   25 from socket import getfqdn
   26 from stat import *
   27 import getopt
   28 from fnmatch import fnmatch
   29 import mutex
   30 import os
   31 import re
   32 import sys
   33 import thread
   34 import time
   35 
   36 from Tkinter import *
   37 from tkMessageBox import askyesno, showerror, showinfo, showwarning
   38 from tkSimpleDialog import askinteger, askstring
   39 
   40 #####
   41 # Imports conditional on OS
   42 #####
   43 
   44 # Set OS type - this allows us to trigger OS-specific code
   45 # where needed.
   46 
   47 OSNAME     = os.name
   48 OSPLATFORM = sys.platform
   49 
   50 # If we're on Win32, try to load win32all stuff if possible
   51 
   52 WIN32ALL = False
   53 if OSPLATFORM == 'win32':
   54     try:
   55         from win32api import GetLogicalDriveStrings as GetDrives
   56         from win32api import GetUserName, GetFileAttributes, GetComputerName, GetDiskFreeSpace, GetVolumeInformation
   57         from win32file import GetDriveType
   58         from win32wnet import WNetGetUniversalName
   59         import win32con
   60         from win32security import *
   61         WIN32HOST = GetComputerName()
   62         WIN32ALL = True
   63     except:
   64         WIN32ALL = False
   65 
   66 
   67 # Get unix password and group features
   68 
   69 if OSNAME == 'posix':
   70     import grp
   71     import pwd
   72 
   73 
   74 #----------------------------------------------------------#
   75 #          Variables User Might Change                     #
   76 #----------------------------------------------------------#
   77 
   78 #####
   79 # Default Key Assignments
   80 #####
   81 
   82 # General Program Commands
   83 
   84 CLRHIST       = '<Control-y>'              # Clear Command History
   85 FONTDECR      = '<Control-bracketleft>'    # Decrease Font Size
   86 FONTINCR      = '<Control-bracketright>'   # Increase Font Size
   87 MOUSECTX      = '<ButtonRelease-3>'        # Pop-up Command Menu
   88 MOUSEDIR      = '<Shift-ButtonRelease-3>'  # Pop-up Directory Menu
   89 MOUSEHIST     = '<Shift-Control-ButtonRelease-3>' # Pop-up History Menu
   90 MOUSESC       = '<Alt-Control-ButtonRelease-1>'    # Pop-up Shortcut Menu
   91 MOUSESORT     = '<Alt-Shift-ButtonRelease-3>'     # Pop-up Sort Menu
   92 KEYPRESS      = '<KeyPress>'               # Any keypress (for commands)
   93 QUITPROG      = '<Control-q>'              # Quit the program
   94 READCONF      = '<Control-r>'              # Re-read the configuration file
   95 REFRESH       = '<Control-l>'              # Refresh screen
   96 TOGAUTO       = '<Control-o>'              # Toggle autorefreshing
   97 TOGDETAIL     = '<Control-t>'              # Toggle detail view
   98 TOGLENGTH     = '<Control-0>'              # Toggle length display between actual and normalized
   99 TOGSYMDIR     = '<Control-asciitilde>'     # Toggle sorting of symbolic links pointing to directories
  100 TOGSYMEXPAND  = '<Control-exclam>'         # Toggle symbolic link expansion
  101 TOGSYMRESOLV  = '<Control-at>'             # Toggle absolute symbolic link resolution
  102 TOGWIN32ALL   = '<Control-w>'              # Toggle win32all features, if available
  103 
  104 # Directory Navigation
  105 
  106 CHANGEDIR   = '<Control-x>'              # Enter a new path
  107 DIRHOME     = '<Control-h>'              # Goto $HOME
  108 DIRBACK     = '<Control-b>'              # Goto previous directory
  109 DIRROOT     = '<Control-j>'              # Goto root directory
  110 DIRSTART    = '<Control-s>'              # Goto starting directory
  111 DIRUP       = '<Control-u>'              # Go up one directory level
  112 DRIVELIST   = '<Control-k>'              # On Win32, display Drive List View if possible
  113 MOUSEBACK   = '<Control-Double-ButtonRelease-1>'  # Go back one directory with mouse
  114 MOUSEUP     = '<Control-Double-ButtonRelease-3>'  # Go up one directory with mouse
  115 
  116 # Selection Keys
  117 
  118 SELALL      = '<Control-comma>'          # Select all items
  119 SELINV      = '<Control-i>'              # Invert the current selection
  120 SELNONE     = '<Control-period>'         # Unselect all items
  121 SELNEXT     = '<Control-n>'              # Select next item
  122 SELPREV     = '<Control-p>'              # Select previous item
  123 SELEND      = '<Control-e>'              # Select bottom item
  124 SELTOP      = '<Control-a>'              # Select top item
  125 
  126 # Scrolling Commands
  127 
  128 PGDN        = '<Control-v>'              # Move page down
  129 PGUP        = '<Control-c>'              # Move page up
  130 PGRT        = '<Control-g>'              # Move page right
  131 PGLFT       = '<Control-f>'              # Move page left
  132 
  133 
  134 # Execute Commands
  135 
  136 RUNCMD      = '<Control-z>'              # Run arbitrary user command
  137 SELKEY      = '<Return>'                 # Select item w/keyboard
  138 MOUSESEL    = '<Double-ButtonRelease-1>' # Select item w/mouse
  139 
  140 # Directory Shortcuts
  141 
  142 KDIRSC1     = '<F1>'
  143 KDIRSC2     = '<F2>'
  144 KDIRSC3     = '<F3>'
  145 KDIRSC4     = '<F4>'
  146 KDIRSC5     = '<F5>'
  147 KDIRSC6     = '<F6>'
  148 KDIRSC7     = '<F7>'
  149 KDIRSC8     = '<F8>'
  150 KDIRSC9     = '<F9>'
  151 KDIRSC10    = '<F10>'
  152 KDIRSC11    = '<F11>'
  153 KDIRSC12    = '<F12>'
  154 KDIRSCSET   = "<Control-8>"
  155 
  156 # Program Memories
  157 
  158 MEMCLR1     = '<Control-F1>'
  159 MEMCLR2     = '<Control-F2>'
  160 MEMCLR3     = '<Control-F3>'
  161 MEMCLR4     = '<Control-F4>'
  162 MEMCLR5     = '<Control-F5>'
  163 MEMCLR6     = '<Control-F6>'
  164 MEMCLR7     = '<Control-F7>'
  165 MEMCLR8     = '<Control-F8>'
  166 MEMCLR9     = '<Control-F9>'
  167 MEMCLR10    = '<Control-F10>'
  168 MEMCLR11    = '<Control-F11>'
  169 MEMCLR12    = '<Control-F12>'
  170 
  171 MEMCLRALL   = '<Control-m>'              # Clear all memories
  172 
  173 MEMSET1     = '<Alt-F1>'                 # Set program memories
  174 MEMSET2     = '<Alt-F2>'
  175 MEMSET3     = '<Alt-F3>'
  176 MEMSET4     = '<Alt-F4>'
  177 MEMSET5     = '<Alt-F5>'
  178 MEMSET6     = '<Alt-F6>'
  179 MEMSET7     = '<Alt-F7>'
  180 MEMSET8     = '<Alt-F8>'
  181 MEMSET9     = '<Alt-F9>'
  182 MEMSET10    = '<Alt-F10>'
  183 MEMSET11    = '<Alt-F11>'
  184 MEMSET12    = '<Alt-F12>'
  185 
  186 # Sorting Keys
  187 
  188 SORTBYNONE   = '<Shift-F10>'             # Select sorting parameters
  189 SORTBYPERMS  = '<Shift-F1>'
  190 SORTBYLINKS  = '<Shift-F2>'
  191 SORTBYOWNER  = '<Shift-F3>'
  192 SORTBYGROUP  = '<Shift-F4>'
  193 SORTBYLENGTH = '<Shift-F5>'
  194 SORTBYTIME   = '<Shift-F6>'
  195 SORTBYNAME   = '<Shift-F7>'
  196 SORTREV      = '<Shift-F11>'
  197 SORTSEP      = '<Shift-F12>'
  198 
  199 # Wildcard Features
  200 
  201 MOUSEWILDFILTER = '<Alt-Control-ButtonRelease-2>'   # Pop-up Filter Wildcard Menu
  202 MOUSEWILDSEL    = '<Alt-Control-ButtonRelease-3>'   # Pop-up Selection Wildcard Menu
  203 FILTERWILD      = '<Control-equal>'          # Filter file list with wildcard
  204 SELWILD         = '<Control-backslash>'      # Select using wildcards
  205 TOGFILT         = '<Control-minus>'          # Invert the filter wildcard logic
  206 TOGHIDEDOT      = '<Control-9>'              # Toggle display of dotfiles
  207 
  208 #####
  209 # GUI Defaults
  210 #####
  211 
  212 #####
  213 # Initial Size And Postition In Pixels
  214 #####
  215 
  216 HEIGHT   = 600
  217 WIDTH    = 800
  218 STARTX   = 0
  219 STARTY   = 0
  220 
  221 #####
  222 # Default Colors
  223 #####
  224 
  225 # Main Display Colors
  226 
  227 BCOLOR  = "black"
  228 FCOLOR  = "green"
  229 
  230 # Menu Colors
  231 
  232 MBARCOL = "beige"
  233 MBCOLOR = "beige"
  234 MFCOLOR = "black"
  235 
  236 # Help Screen Colors
  237 
  238 HBCOLOR = "lightgreen"
  239 HFCOLOR = "black"
  240 
  241 #####
  242 # Default Display Fonts
  243 #####
  244 
  245 # Main Display Font
  246 
  247 FNAME = "Courier"
  248 FSZ   = 12
  249 FWT   = "bold"
  250 
  251 # Menu Font
  252 
  253 MFNAME = "Courier"
  254 MFSZ   = 12
  255 MFWT   = "bold"
  256 
  257 # Help Screen Font
  258 
  259 HFNAME = "Courier"
  260 HFSZ   = 10
  261 HFWT   = "italic"
  262 
  263 
  264 #------------------- Nothing Below Here Should Need Changing ------------------#
  265 
  266 
  267 #----------------------------------------------------------#
  268 #              Constants & Literals                        #
  269 #----------------------------------------------------------#
  270 
  271 
  272 
  273 #####
  274 # Booleans
  275 #####
  276 
  277 # Don't need to define True & False - they are defined in the Tkinter module
  278 
  279 
  280 #####
  281 # Symbolic Constants Needed Below
  282 #####
  283 
  284 #####
  285 # Defaults
  286 #####
  287 
  288 ACTUALLENGTH    = False           # Show actual file lengths
  289 ADAPTREFRESH    = True            # Dynamically adjust refresh intervals
  290 AFTERCLEAR      = True            # Clear all selections following REFRESHAFTER
  291 AFTERWAIT       = 1               # Seconds to wait before REFRESHAFTER
  292 AUTOREFRESH     = True            # Automatically refresh the directory display?
  293 CMDMENUSORT     = False           # Sort the command menu?
  294 CMDSHELL        = ""              # No CMDSHELL processing
  295 DEBUGLEVEL      = 0               # No debug output
  296 DEFAULTSEP      = "==>"           # Default separator in PROMPT and YES definitions
  297 DOTFILE         = '.'             # Leading string of files suppressed by HIDEDOTFILES
  298 FORCEUNIXPATH   = False           # Force Unix path separators regardless of OS
  299 HIDEDOTFILES    = False           # Suppress display of files begining with DOTFILE
  300 INVERTFILTER    = False           # Invert wildcard filtering logic
  301 ISODATE         = False           # Display date/time in ISO 8601 Format
  302 MAXMENU         = 32              # Maximum length of displayed menu
  303 MAXMENUBUF      = 250             # Maximum size of internal menu buffer
  304 MAXNESTING      = 32              # Maximum depth of nested variable definitions
  305 NODETAILS       = False           # True means details can never be displayed
  306 NONAVIGATE      = False           # True means that all directory navigation is prevented
  307 QUOTECHAR       = '\"'            # Character to use when quoting Built-In Variables
  308 REFRESHINT      = 5000            # Interval (ms) for automatic refresh
  309 SCALEPRECISION  = 1               # Precision of scaled length representation
  310 SORTBYFIELD     = "Name"          # Field to use as sort key
  311 SORTREVERSE     = False           # Reverse specified sort order?
  312 SORTSEPARATE    = True            # Separate Directories and Files in sorted displays?
  313 SYMDIR          = True            # Sort symlinks pointing to directories as directories
  314 SYMEXPAND       = True            # Expand symlink to show its target
  315 SYMRESOLV       = False           # Show absolute path of symlink target
  316 USETHREADS      = False           # Use threads on Unix?
  317 USEWIN32ALL     = True            # Use win32all features if available?
  318 WARN            = True            # Warnings on?
  319 WILDNOCASE      = False           # Turns on case-insensitive wildcard matching
  320 WIN32ALLON      = True            # Flag for toggling win32all features while running
  321 
  322 # Wildcards are case-insensitive on Win32 by default
  323 
  324 if OSPLATFORM == 'win32':
  325     WILDNOCASE = True
  326 
  327 #####
  328 # Constants
  329 #####
  330 
  331 # General Constants
  332 
  333 
  334 CMDESCAPE     = '"'             # Character to force literal dialog processing
  335 CMDSHELLESC   = CMDESCAPE       # Disable CMDSHELL processing for a  manual command entry
  336 KB            = 1024            # 1 KB constant
  337 MB            = KB * KB         # 1 MB constant
  338 GB            = MB * KB         # 1 GB constant
  339 HOMEDIRMARKER = '~'             # Shortcut string used to indicate home directory
  340 NUMFUNCKEY    = 12              # Number of function keys
  341 NUMPROGMEM    = 12              # Number of program memories
  342 POLLINT       = 250             # Interval (ms) the poll routine should run
  343 PSEP          = os.sep          # Character separating path components
  344 REFRESHINDI   = "*"             # Titlebar character used to indicate refresh underway
  345 REFRESHAFTER  = '+'             # Indicate we want a refresh after a command runs
  346 SHOWDRIVES    = '\\\\'          # Logical directory name for Win32 Drive Lists
  347 STRICTMATCH   = CMDESCAPE       # Tells wildcard system to enforce strict matching
  348 STRIPNL       = '-'             # Tells variable execution to replace newlines with spaces
  349 TTLMAXDIR     = 60              # Maximum length of current directory path to show in titlebar
  350 TTLDIR2LONG   = "..."           # String to place at front of long dir paths in titlebar
  351 
  352 # Sort Field Names In Normal View
  353 
  354 fNONE        = "No Sort"
  355 fPERMISSIONS = "Permissions"
  356 fLINKS       = "Links"
  357 fOWNER       = "Owner"
  358 fGROUP       = "Group"
  359 fLENGTH      = "Length"
  360 fDATE        = "Time"
  361 fNAME        = "Name"
  362 
  363 # Sort Field Names In Drive List View
  364 
  365 dlvNONE      = "No Sort"
  366 dlvLABEL     = "Label/Share"
  367 dlvTYPE      = "Drive Type"
  368 dlvFREE      = "Free Space"
  369 dlvTOTAL     = "Total Space"
  370 dlvLETTER    = "Drive Letter"
  371 
  372 # 'Fake' sorting field used to set SORTREVERSE and SORTSEPARATE
  373 
  374 fREVERSE     = "Reverse"
  375 fSEPARATE    = "Separate"
  376 
  377 # Build a dictionary whose keys are the names of all the legal sort fields
  378 # and whose entries are tuples in the form:
  379 #
  380 #   (field #,
  381 #    Name Of Sort Option In Normal View,
  382 #    Name Of Sort Option In Drive List View)
  383 #
  384 # When preparing to actually sort, BuildDirList() gets information on
  385 # each file from FileDetails() in the form of a set of fields in a
  386 # list.  The field # entered loaded here, tells BuildDirList() just
  387 # *which* of the fields (list position) is relevant to each of the
  388 # sort types.
  389 #
  390 # Placing a None value in either of the last two tuple entries causes the
  391 # keystroke combination associated with that sort option to be ignored AND
  392 # prevents that option from appearing in the Sort Menu.
  393 #
  394 # Note that fNONE indicates that no sorting is to be done, so there
  395 # is no real associated sortkey field.
  396 
  397 Name2Key = {}
  398 index = -1
  399 
  400 for x, y in [(fNONE, dlvNONE), (fPERMISSIONS, dlvLABEL), (fLINKS, dlvTYPE), (fOWNER, dlvFREE),
  401              (fGROUP, dlvTOTAL), (fLENGTH, dlvLETTER), (fDATE, None), (fNAME, None),
  402              (fREVERSE, fREVERSE), (fSEPARATE, None)]:
  403     Name2Key[x.lower()] = (index, x, y)
  404     index += 1
  405 
  406 # Highest key index needed by Drive List View
  407 
  408 MAXDLVKEY = 4
  409 
  410 
  411 #####
  412 # System-Related Defaults
  413 #####
  414 
  415 # Default startup directory
  416 STARTDIR = os.path.abspath("." + os.sep)
  417 
  418 # Home directory
  419 
  420 
  421 ENVHOME = os.getenv("HOME")
  422 HOME = ENVHOME or STARTDIR
  423 
  424 # Get hostname
  425 
  426 # Try the local environment first when looking for the
  427 # hostname.  Only use the socket call as a last
  428 # resort because a misconfigured or malfunctioning
  429 # network will cause this call to be *very* slow
  430 # and 'twander' will take forever to start-up.
  431 
  432 HOSTNAME = os.getenv("HOSTNAME") or getfqdn()
  433 
  434 # Get the user name
  435 
  436 if OSPLATFORM == 'win32':
  437     if WIN32ALL and USEWIN32ALL and WIN32ALLON:
  438         USERNAME = GetUserName()
  439     else:
  440         USERNAME = os.getenv("LOGNAME")
  441     
  442 
  443 elif OSNAME == 'posix':
  444     USERNAME = os.getenv("USER")
  445 
  446 else:
  447     USERNAME = ""
  448 
  449 # Concatenate them if we got a user name
  450 
  451 if USERNAME:
  452     FULLNAME = "%s@%s" % (USERNAME, HOSTNAME)
  453 else:
  454     FULLNAME = HOSTNAME
  455 
  456 
  457 
  458 #####
  459 # GUI-Related Stuff
  460 #####
  461 
  462 # Constants
  463 
  464 CMDMENU_WIDTH = 16
  465 
  466 
  467 ROOTBORDER    =  1
  468 MENUBORDER    =  2
  469 MENUPADX      =  2
  470 MENUOFFSET    = ROOTBORDER + MENUBORDER + MENUPADX
  471 
  472 # Key & Button Event Masks
  473 
  474 ShiftMask     = (1<<0)
  475 LockMask      = (1<<1)
  476 ControlMask   = (1<<2)
  477 Mod1Mask      = (1<<3)
  478 Mod2Mask      = (1<<4)
  479 Mod3Mask      = (1<<5)
  480 Mod4Mask      = (1<<6)
  481 Mod5Mask      = (1<<7)
  482 Button1Mask   = (1<<8)
  483 Button2Mask   = (1<<9)
  484 Button3Mask   = (1<<10)
  485 Button4Mask   = (1<<11)
  486 Button5Mask   = (1<<12)
  487 
  488 # There are some event bits we don't care about -
  489 # We'll use the following constant to mask them out
  490 # later in the keyboard and mouse handlers.
  491 
  492 DontCareMask  = LockMask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask
  493 
  494 # Some things are OS-dependent
  495 
  496 if OSPLATFORM == 'win32':
  497     AltMask = (1<<17)
  498     DontCareMask |= Mod1Mask   # Some versions of Win32 set this when Alt is pressed
  499 else:
  500     AltMask = Mod1Mask
  501 
  502 
  503 # Name The Key/Mouse Assignments Which We Do Not Allow To Be Rebound In The Config File
  504 
  505 NOREBIND =  ["MOUSECTX", "MOUSEDIR", "MOUSEHIST", "MOUSESC", "MOUSESORT", "MOUSEWILDFILTER", "MOUSEWILDSEL", "MOUSEBACK","MOUSEUP", "MOUSESEL"]
  506 
  507 
  508 #####
  509 # Stat-Related Stuff
  510 #####
  511 
  512 # Misc. Stat-Related Strings
  513 
  514 FILEGROUP     = "group"
  515 FILEOWNER     = "owner"
  516 NODRIVE       = "<Drive Empty>"
  517 NOLABEL       = "<No Label>"
  518 SYMPTR        = " -> "
  519 UNAVAILABLE   = "Unavailable"
  520 WIN32GROUP    = "win32" + FILEGROUP
  521 WIN32OWNER    = "win32" + FILEOWNER
  522 WIN32FREE     = "free"
  523 WIN32TOTAL    = "total  "        # Leave trailing space - drive letter follows
  524 
  525 MAX_SZ_CHARS      = 17    # Number of digits needed to display max drive/file size - including commas
  526 SZ_TRAILING_SPACE = 2     # Number of trailing spaces to add after a drive/file size field.
  527 
  528 MAX_SZ_FIELD  =  MAX_SZ_CHARS + SZ_TRAILING_SPACE  # Biggest a drive/file size string can be
  529 
  530 
  531 if WIN32ALL:
  532     
  533     # Used with win32all-based permissions logic.
  534     # Format for each entry is: (mask to test, symbol if true).
  535     # Position in tuple determines position in display.
  536 
  537     win32all_mode = ((win32con.FILE_ATTRIBUTE_DIRECTORY,  'd'),
  538                      (win32con.FILE_ATTRIBUTE_ARCHIVE,    'A'),
  539                      (win32con.FILE_ATTRIBUTE_COMPRESSED, 'C'),
  540                      (win32con.FILE_ATTRIBUTE_HIDDEN,     'H'),
  541                      (win32con.FILE_ATTRIBUTE_NORMAL,     'N'),
  542                      (win32con.FILE_ATTRIBUTE_READONLY,   'R'),
  543                      (win32con.FILE_ATTRIBUTE_SYSTEM,     'S'))
  544 
  545     win32all_type = ((win32con.DRIVE_CDROM,     'CD/DVD'),
  546                      (win32con.DRIVE_FIXED,     'Fixed'),
  547                      (win32con.DRIVE_RAMDISK,   'Ramdisk'),
  548                      (win32con.DRIVE_REMOTE,    'Remote'),
  549                      (win32con.DRIVE_REMOVABLE, 'Removable'))
  550 
  551     # Size of each of the drive detail fields, including room for trailing space.
  552     
  553 
  554     SZ_DRIVE_SHARE  = 24   # Can be label or share string - leave plenty of room
  555     SZ_DRIVE_TYPE   = 20
  556     SZ_DRIVE_FREE   = MAX_SZ_FIELD
  557     SZ_DRIVE_TTL    = MAX_SZ_FIELD
  558 
  559     SZ_DRIVE_TOTAL = SZ_DRIVE_SHARE + SZ_DRIVE_TYPE + SZ_DRIVE_FREE + len(WIN32FREE) + \
  560                      SZ_DRIVE_TTL + len(WIN32TOTAL)
  561 
  562 # Constants used with the more usual Unix-style details
  563 # Used both by Unix and Win32 when win32all is not present or disabled.
  564 
  565 
  566 # Month Name -> Number Conversion Needed For ISO Representation
  567 
  568 ST_MONTHS     = {"Jan":"01", "Feb":"02", "Mar":"03", "Apr":"04",
  569                  "May":"05", "Jun":"06", "Jul":"07", "Aug":"08",
  570                  "Sep":"09", "Oct":"10", "Nov":"11", "Dec":"12"
  571                 }
  572 
  573 
  574 # Permissions
  575 
  576 ST_PERMIT     = ["---", "--x", "-w-", "-wx",
  577                  "r--", "r-x", "rw-", "rwx"]
  578 
  579 # Special file type lookup
  580 
  581 ST_SPECIALS   = {"01":"p", "02":"c", "04":"d", "06":"b",
  582                  "10":"-", "12":"l", "14":"s"}
  583 
  584 # Size of each status display field including trailing space
  585 # These are sized for the worst-case: Win32 with win32all features
  586 
  587 ST_SZMODE     = 12
  588 ST_SZNLINK    = 5
  589 ST_SZUNAME    = 18
  590 ST_SZGNAME    = 18
  591 ST_SZLEN      = MAX_SZ_FIELD
  592 ST_SZMTIME    = 18
  593 
  594 ST_SZTOTAL    = ST_SZMODE + ST_SZNLINK + ST_SZUNAME + ST_SZGNAME + \
  595                 ST_SZLEN + ST_SZMTIME
  596 
  597 
  598 # Special Bit Masks
  599 
  600 STICKY_MASK   = 1
  601 SETGID_MASK   = 2
  602 SETUID_MASK   = 4
  603 
  604 
  605 #####
  606 # Configuration File-Related Constants & Globals
  607 #####
  608 
  609 ASSIGN      = "="               # Assignment for variable definitions
  610 ASSOCBLANK  = "RESETASSOC"      # Internally used to indicate a blank ASSOC RHS
  611 ASSOCDFLT   = "*"               # Symbol for default association action
  612 ASSOCEXCL   = "!"               # Symbol for association exclusion action
  613 ASSOCIATE   = "ASSOC"           # Association keyword
  614 ASSOCNOCASE = "/"               # Introducer used to indicate case-insensitive ASSOCiations
  615 CONF        = ""                # Config file user selected with -c option
  616 COMMENT     = r"#"              # Comment introducer string
  617 ENVVBL      = r'$'              # Symbol denoting an environment variable
  618 FAKEFIELD   = r'#FAKEFIELD'     # Unsplittable field used to preserve PROMPT/YESNO content
  619 STARTUP     = r'Starting Up'    # Used when doing parse of first config file
  620 VAREXECUTE  = r'`'              # Indicate we want content of variable name to be executed
  621 
  622 
  623 # Names Of Conditionals, Directives, And Pre-Defined Symbols
  624 
  625 CONDENDIF    = '.endif'
  626 CONDEQUAL    = '=='
  627 CONDIF       = '.if'
  628 CONDNOTEQUAL = '!='
  629 DIRECTINC    = '.include'
  630 SYMOS        = '.OS'
  631 SYMPLATFORM  = '.PLATFORM'
  632 
  633 # Globals Supporting Configutration File Conditional Processing
  634 
  635 ConditionalStack  = []         # Stack for tracking conditional state
  636 
  637 
  638 # Variable Name Pattern Matching Stuff
  639 
  640 DIRSC      = "DIRSC"                            # Directory Shortcut naming
  641 reDIRSC    = r'^' + DIRSC + r'([1-9]|1[0-2])$'  # Regex describing Directory Shortcut names
  642 rePROMPT   = r'\+{PROMPT:.*?\}'                 # Regex describing prompt builtin
  643 reVAR      = r"\[.+?\]"                         # Regex describing variable notation
  644 reYESNO    = r'\{YESNO:.*?\}'                   # Regex describing yes or no builtin
  645 WILDFILTER = "WILDFILTER"                       # Configuration statement for pre-loading Filter list
  646 WILDSELECT = "WILDSELECT"                       # Configuration statement for pre-loading Selection list
  647 
  648 
  649 # Create actual regex matching engines
  650 
  651 REDIRSC    = re.compile(reDIRSC)
  652 REPROMPT   = re.compile(rePROMPT)
  653 REVAR      = re.compile(reVAR)
  654 REYESNO    = re.compile(reYESNO)
  655 CONDVAR    = re.compile(r'^' + reVAR + r'$')
  656 
  657 # Built-In Variables
  658 
  659 DIR         = r'[DIR]'
  660 DSELECTION  = r'[DSELECTION]'
  661 DSELECTIONS = r'[DSELECTIONS]'
  662 HASH        = r'[HASH]'
  663 MEM1        = r'[MEM1]'
  664 MEM2        = r'[MEM2]'
  665 MEM3        = r'[MEM3]'
  666 MEM4        = r'[MEM4]'
  667 MEM5        = r'[MEM5]'
  668 MEM6        = r'[MEM6]'
  669 MEM7        = r'[MEM7]'
  670 MEM8        = r'[MEM8]'
  671 MEM9        = r'[MEM9]'
  672 MEM10       = r'[MEM10]'
  673 MEM11       = r'[MEM11]'
  674 MEM12       = r'[MEM12]'
  675 PROMPT      = r'{PROMPT:'
  676 SELECTION   = r'[SELECTION]'
  677 SELECTIONS  = r'[SELECTIONS]'
  678 YESNO       = r'{YESNO:'
  679 
  680 # Shortcuts to the builtins available in RUNCMD
  681 
  682 RUNCMD_SC = {"[D]"  : DIR,
  683              "[DN]" : DSELECTION,
  684              "[DS]" : DSELECTIONS,
  685              "[SN]" : SELECTION,
  686              "[SS]" : SELECTIONS,
  687              "[1]"  : MEM1,
  688              "[2]"  : MEM2,
  689              "[3]"  : MEM3,
  690              "[4]"  : MEM4,
  691              "[5]"  : MEM5,
  692              "[6]"  : MEM6,
  693              "[7]"  : MEM7,
  694              "[8]"  : MEM8,
  695              "[9]"  : MEM9,
  696              "[10]" : MEM10,
  697              "[11]" : MEM11,
  698              "[12]" : MEM12
  699              }
  700               
  701 
  702 #----------------------------------------------------------#
  703 #            Prompts, & Application Strings                #
  704 #----------------------------------------------------------#
  705 
  706 
  707 #####
  708 # Menu, Error, Information, & Warning  Messages
  709 #####
  710 
  711 # Title-Bar Strings
  712 
  713 TTLAUTO       = "Auto:"
  714 TTLHIDEDOT    = "HideDot:"
  715 TTLFILES      = "Files:"
  716 TTLFILTER     = "Filter:"
  717 TTLSIZE       = "Size:"
  718 TTLSORTFLD    = "Sort:"
  719 TTLSORTREV    = "Rev:"
  720 TTLSORTSEP    = "Sep:"
  721 TTLSYMLINKS   = "Symlinks:"
  722 
  723 
  724 # Convert Logical Values Into Yes/No String
  725 
  726 YesOrNo       = {True:"Y", False:"N"}
  727 
  728 
  729 # Menu Button Titles
  730 
  731 COMMANDMENU   = 'Commands'        # Title for Command Menu button
  732 DIRMENU       = 'Directories'     # Title for Directory Menu button
  733 HISTMENU      = 'History'         # Title for History Menu button
  734 SCMENU        = 'Shortcuts'       # Title for Shortcut Menu button
  735 SORTMENU      = 'Sorting'         # Title for Sort Menu button
  736 FILTERMENU    = 'Filter'          # Title for Wildcard Filter Menu button
  737 SELECTMENU    = 'Select'          # Title for Wildcard Selection Menu button
  738 HELPMENU      = 'Help'            # Title for Help Menu button
  739 
  740 # Sort Menu-Related
  741 
  742 # And their names - used in Sorting Menu
  743 
  744 sSORTBY        = "Sort By"
  745 sREVERSE       = "Reverse Sort"
  746 sSEPARATE      = "Separate Dirs/Files"
  747 
  748 
  749 
  750 
  751 # Help Menu-Related
  752 
  753 WEBSITE       = "Homepage:\n\nhttp://www.tundraware.com/Software/twander"
  754 ABOUT         = "%s %s\n\nCopyright %s %s\n%s\n\n%s\n\n%s"  % (PROGNAME, VERSION, CPRT, DATE, OWNER, RIGHTS, WEBSITE)
  755 hABOUT        = 'About'
  756 hASSOC        = 'Associations'
  757 hCOMMANDS     = 'Command Definitions'
  758 hINTVBLS      = 'Internal Program Variables'
  759 hKEYS         = 'Keyboard Assignments'
  760 hNONE         = 'No %s Found.'
  761 hOPTVBLS      = 'User-Settable Options'
  762 hUSERVBLS     = 'User-Defined Variables'
  763 
  764 
  765 # Errors
  766 
  767 eBADEXEC      = "Cannot Run %s.\n File Is Not Executable And Has No Application Association"
  768 eBADROOT      = "%s Is Not A Directory"
  769 eDIRRD        = "Cannot Open Directory : %s  ---  Check Permissions."
  770 eERROR        = "ERROR"
  771 eINITDIRBAD   = "Cannot Open Starting Directory : %s - Check Permissions - ABORTING!."
  772 eOPEN         = "Cannot Open File: %s"
  773 eTOOMANY      = "You Can Only Specify One Starting Directory."
  774 
  775 # Information
  776 
  777 iNOSTAT       = "Details Unavailable For This File:  "
  778 
  779 # Prompts
  780 
  781 pCHPATH       = "Change Path"
  782 pSCCHANGE     = "Change Directory Shortcut"
  783 pSCNUM        = "Assign Current Directory To Shortcut # (1-12):"
  784 pENCMD        = "Enter Command To Run:"
  785 pENPATH       = "Enter New Path Desired:"
  786 pENWILD       = "Enter Wildcard - Use %s For Strict Matching:" % STRICTMATCH
  787 pMANUALCMD    = "Manual Command Entry"
  788 pRUNCMD       = "Run Command"
  789 pWILDFILT     = "Filter Files By Wildcard"
  790 pWILDSEL      = "Selection By Wildcard"
  791 
  792 # Warnings
  793 
  794 wBADCFGLINE   = "Ignoring Line %s.\nBogus Configuration Entry:\n\n%s"
  795 wBADCMD       = "Incorrect Command Syntax At Line %s Of File %s"
  796 wBADDEBUG     = "Ignoring Bogus Debug Level! - %s Is Not In Integer Or Hex Format."
  797 wBADENDIF     = "Ignoring Line %s!\nBogus End-Of-Block Statement:\n\n%s"
  798 wBADENVVBL    = "Ignoring Line %s.\nEnvironment Variable %s Not Set:\n\n%s"
  799 wBADEXE       = "Could Not Execute Command:\n\n%s"
  800 wBADIF        = "Ignoring Line %s.\nImproperly Formed Condition: \n\n%s"
  801 wBADRHS       = "Ignoring Line %s.\nOption Assignment Has Bad Righthand Side:\n\n%s"
  802 wBADSCNUM     = "Ignoring Line %s.\nShortcut Number Must Be From 1-12:\n\n%s"
  803 wBADSORTFLD   = "Don't Know How To Sort By: %s\n\nWill Sort By %s Instead."
  804 wBADVAREXEC   = "Ignoring Line %s.\nExecution Of Variable %s Failed:\n\n%s"
  805 wBADYESNODFLT = "Bad Default Argument For Yes/No Prompt: '%s'\nCommand '%s' Aborted"
  806 wCIRCULARREF  = "Circular .include Reference In '%s', Line '%s'!\n.include Not Processed!"
  807 wCONFOPEN     = "Cannot Open Configuration File:\n%s"
  808 wEXTRAENDIF   = "Ignoring Line %s!\nNo Conditional Block To End:\n\n%s"
  809 wLINKBACK     = "%s Points Back To Own Directory"
  810 wMISSENDIF    = "Configuration File Is Missing %s " + CONDENDIF +" Statement(s)"
  811 wNOCMDS       = "Running With No Commands Defined!"
  812 wNOREBIND     = "Ignoring Line %s.\nCannot Rebind This Keyboard Or Mouse Button Combination:\n\n%s"
  813 wREDEFVAR     = "Ignoring Line %s.\nCannot Redefine Built-In Variable %s:\n\n%s"
  814 wUNDEFVBL     = "Ignoring Line %s.\nUndefined Variable %s Referenced:\n\n%s"
  815 wVBLTOODEEP   = "Ignoring Line %s.\nVariable Definition Nested Too Deeply:\n\n%s"
  816 wWARN         = "WARNING"
  817 wWILDCOMP     = "Cannot Compile Wildcard Expression: %s"
  818 
  819 
  820 #####
  821 # Debug-Related Stuff
  822 #####
  823 
  824 # Debug Levels
  825 
  826 # Nibble #1
  827 
  828 DEBUGVARS     = (1<<0)   # Dump internal variables
  829 DEBUGSYMS     = (1<<1)   # Dump symbol table
  830 DEBUGCTBL     = (1<<2)   # Dump command table
  831 DEBUGKEYS     = (1<<3)   # Dump key bindings
  832 
  833 # Nibble #2
  834 
  835 DEBUGCMDS     = (1<<4)   # Dump command execution string
  836 DEBUGDIRS     = (1<<5)   # Dump directory stack contents as it changes
  837 DEBUGHIST     = (1<<6)   # Dump contents of command history stack after command execution
  838 DEBUGMEM      = (1<<7)   # Dump contents of program memories as they change
  839 
  840 # Nibble #3
  841 
  842 DEBUGWILD     = (1<<8)   # Dump contents of wildcard stack as it changes
  843 DEBUGASSOC    = (1<<9)   # Dump association table
  844 DEBUGRSRV3    = (1<<10)  # Reserved for future use
  845 DEBUGQUIT     = (1<<11)  # Dump debug info and quit program
  846 
  847 # Debug Strings
  848 
  849 dASSOC        = "ASSOCIATIONS"
  850 dCMD          = "COMMAND"
  851 dCMDTBL       = hCOMMANDS
  852 dDIRSTK       = "DIRECTORY STACK"
  853 dFALSE        = "False"
  854 dFUNCKEYS     = 'Directory Shortcuts'
  855 dHEADER       = "twander Debug Dump Run On: %s\n"
  856 dHIST         = "COMMAND HISTORY STACK"
  857 dINTVAR       = hINTVBLS
  858 dKEYBINDS     = hKEYS
  859 dMEM          = "CONTENTS OF MEMORY %s"
  860 dMEMALL       = "CONTENTS OF ALL PROGRAM MEMORIES"
  861 dNULL         = "None"
  862 dOPTVAR       = hOPTVBLS
  863 dSYMTBL       = hUSERVBLS
  864 dTRUE         = "True"
  865 dFILTER       = "FILTER"
  866 dSELECT       = "SELECT"
  867 dWILDLST      = "%s WILDCARDS"
  868 
  869 # Debug Formatting
  870 
  871 dASSOCWIDTH   = 10
  872 dCMDWIDTH     = 20
  873 dCOLNUM       = 3
  874 dCOLWIDTH     = 50
  875 dINTVARWIDTH  = 12
  876 dKEYWIDTH     = 16
  877 dOPTIONWIDTH  = 16
  878 dSCWIDTH      = 6
  879 dUSRVBLWIDTH  = 20
  880 
  881 # List of internal program variables to dump during debug sessions
  882 
  883 DebugVars = ["RCSID", "OSNAME", "HOSTNAME", "USERNAME", "OPTIONS", "CONF", "HOME", "PSEP", "POLLINT"]
  884 
  885 
  886 #####
  887 # Usage Information
  888 #####
  889 
  890 uTable = [PROGNAME + " " + VERSION + " - %s\n" % COPYRIGHT,
  891           "usage:  " + PROGNAME + " [-cdhqrstvwxy] [startdir] where,\n",
  892           "          startdir  name of directory in which to begin (default: current dir)",
  893           "          -c file   name of configuration file (default: $HOME/." + PROGNAME +
  894                      " or PROGDIR/." + PROGNAME + ")",
  895           "          -d level  set debugging level (default: 0, debugging off)",
  896           "                Bit Assignments:",
  897           "                         0 - Dump Internal Options & User-Settable Options (0x001)",
  898           "                         1 - Dump User-Defined Variables (0x002)",
  899           "                         2 - Dump Command Definitions (0x004)",
  900           "                         3 - Dump Key Bindings (0x008)",
  901           "                         4 - Display, Do Not Execute, Commands When Invoked (0x010)",
  902           "                         5 - Dump Directory Stack As It Changes (0x020)",
  903           "                         6 - Dump Command History Stack After Command Executes (0x040)",
  904           "                         7 - Dump Contents Of Program Memories As They Change (0x080)",
  905           "                         8 - Dump Contents Of Filter/Selection Wildcard Lists As They Change (0x100)",
  906           "                         9 - Dump Association Table (0x200)",
  907           "                        10 - Reserved (0x400)",
  908           "                        11 - Dump Requested Debug Information And Exit Immediately (0x800)",
  909           "          -h        print this help information",
  910           "          -q        quiet mode - no warnings (default: warnings on)",
  911           "          -r        turn off automatic content refreshing (default: refresh on)",
  912           "          -t        no quoting when substituting Built-In Variables (default: quoting on)",
  913           "          -v        print detailed version information",
  914           ]
  915 
  916 
  917 #---------------------------Code Begins Here----------------------------------#
  918 
  919 
  920 #----------------------------------------------------------#
  921 #             General Support Functions                    #
  922 #----------------------------------------------------------#
  923 
  924 
  925 #####
  926 # Print An Error Message
  927 #####
  928 
  929 def ErrMsg(emsg):
  930     showerror(PROGNAME + " " + VERSION + "    " + eERROR, emsg)
  931 
  932 # End of 'ErrMsg()'
  933 
  934 
  935 #####
  936 # Convert A Dictionary In A Multicolumn List Of Strings
  937 #####
  938 
  939 def FormatMultiColumn(dict, numcols=dCOLNUM, lhswidth=dKEYWIDTH, colwidth=dCOLWIDTH):
  940 
  941     retval = []
  942 
  943     # Get and sort list of keys
  944     
  945     keys = dict.keys()
  946     keys.sort()
  947 
  948     # Make sure it is of proper length for multi-column output
  949 
  950     while len(keys) % numcols:
  951         keys.append("")
  952 
  953     # Produce output
  954 
  955     k=0
  956     columnlen = len(keys)/numcols
  957     while k != columnlen:
  958 
  959         # Produce output 'numcols' at a time
  960         
  961         entry = []
  962         for i in range(numcols):
  963             
  964             key = keys[k+(i*columnlen)]
  965             if key:
  966                 val = dict[key]
  967             else:
  968                 val = ""
  969             entry.append(PadString(key, lhswidth) + val)
  970             
  971         # Turn it into a single string
  972 
  973         s=""
  974         for x in entry:
  975             s += PadString(x, colwidth)
  976 
  977         # And stuff it into the return object
  978 
  979         retval.append(s)
  980 
  981         # Point to the next tuple of entries
  982 
  983         k += 1
  984 
  985 
  986     # Return the results
  987 
  988     return retval
  989     
  990 # End of 'FormatMultiColumn()'
  991 
  992 
  993 #####
  994 # Run A Command, Returning Status And Output
  995 # This Version Runs On Win32 Unlike commands.getstatusoutput()
  996 #####
  997 
  998 def GetStatusOutput(command):
  999 
 1000     # Handle Windows variants
 1001 
 1002     if OSPLATFORM == 'win32':
 1003         pipe = os.popen(command, 'r')
 1004 
 1005     # Handle Unix variants
 1006     
 1007     else: 
 1008         pipe = os.popen('{ ' + command + '; } 2>&1', 'r')
 1009 
 1010     output = pipe.read()
 1011     status = pipe.close()
 1012 
 1013     if status == None:
 1014         status = 0
 1015         
 1016     if output[-1] == '\n':
 1017         output = output[:-1]
 1018         
 1019     return status, output
 1020 
 1021 # End of 'GetStatusOutput()'
 1022  
 1023 
 1024 #####
 1025 # Build List Of Win32 Drives
 1026 #####
 1027 
 1028 def GetWin32Drives():
 1029 
 1030     # Get Win32 drive string, split on nulls, and get
 1031     # rid of any resulting null entries.
 1032 
 1033     if WIN32ALL and USEWIN32ALL and WIN32ALLON:
 1034         return filter(lambda x : x, GetDrives().split('\x00'))
 1035     else:
 1036         return ""
 1037 
 1038 # End of 'GetWin32Drives()'
 1039 
 1040 
 1041 #####
 1042 # Convert A File Size Into Equivalent String With Scaling
 1043 # Files under 1 MB show actual length
 1044 # Files < 1 MB < 1 GB shown in KB
 1045 # Files 1 GB or greater, shown in MB
 1046 #####
 1047 
 1048 def FileLength(flen):
 1049 
 1050     # Return actual length of file
 1051 
 1052     if ACTUALLENGTH:
 1053 
 1054         # Insert commas for readability
 1055 
 1056         length = str(flen)
 1057         index  = len(length)-3
 1058         flen   = ""
 1059 
 1060         while index > -3:
 1061             if index <= 0:
 1062                 flen = length[:index+3] + flen
 1063             else:
 1064                 flen = ',' + length[index:index+3] + flen
 1065             index -= 3
 1066 
 1067     # Return normalized length of file
 1068 
 1069     else:
 1070 
 1071         # Set the scaling factor and indicator
 1072         
 1073         if flen >= GB:
 1074             norms = (GB, "g")
 1075         elif flen >= MB:
 1076             norms = (MB, "m")
 1077         elif flen >= KB:
 1078             norms = (KB, "k")
 1079         else:
 1080             norms = (1, "")
 1081 
 1082         # Scale the results and convert into a string
 1083         # displaying SCALEPRECISION worth of digits to
 1084         # the right of the decimal point
 1085         
 1086         sep = ""
 1087         if SCALEPRECISION:
 1088             sep = "."
 1089 
 1090         factor = norms[0]
 1091         if (factor > 1):
 1092             l, r = str(float(flen)/factor).split(".")
 1093             flen = l + sep + r[:SCALEPRECISION]
 1094         else:
 1095             flen = str(flen)
 1096 
 1097         flen += norms[1]
 1098 
 1099     return flen
 1100 
 1101 # End of 'FileLength()'
 1102 
 1103 #####
 1104 # Check to see if a passed string matches/does not match the currently
 1105 # active filtering wildcard.  If there is no active wildcard, everything
 1106 # passes.  This routine also filters any "dotfiles" if the HIDEDOTFILES
 1107 # option is enabled.
 1108 #####
 1109 
 1110 def FilterMatching(matchthis):
 1111 
 1112     # Check to see if dotfiles should be hidden.
 1113 
 1114     if HIDEDOTFILES:
 1115 
 1116         # For symlinks we want the link name not the target name
 1117         if matchthis.count(SYMPTR):
 1118             fname = matchthis.split(SYMPTR)[-2].split()[-1]
 1119 
 1120         # Otherwise just use the filename
 1121         else:
 1122             fname = matchthis.split()[-1]
 1123 
 1124         if fname.startswith(DOTFILE):
 1125             return False
 1126 
 1127     # Accomodate case-insensitive matching
 1128     # But strict matching overrides this
 1129 
 1130     if WILDNOCASE and not UI.FilterWildcard[3]:
 1131         wc = UI.FilterWildcard[2]
 1132         matchthis = matchthis.lower()
 1133 
 1134     else:
 1135         wc = UI.FilterWildcard[1]
 1136 
 1137     # If there's no active filter, everything matches
 1138 
 1139     if not wc:
 1140         matched = True
 1141 
 1142     elif wc.match(matchthis):
 1143         matched = True
 1144 
 1145     else:
 1146         matched = False
 1147 
 1148     # Invert the sense of the logic if so dictated, but
 1149     # only if there is actually a wildcard active.
 1150 
 1151     if wc and INVERTFILTER:
 1152         matched = not matched
 1153 
 1154     return matched
 1155 
 1156 # End of 'FilterMatching()'
 1157 
 1158 #####
 1159 # Pad A String With Spaces To Specified Width.
 1160 # Return either that padded string or, if the passed
 1161 # string is too large, truncate it to specified length.
 1162 # Defaults to left-justification, but can be overriden.
 1163 # Optionally can rotate a specified number of leading
 1164 # spaces to become trailing spaces after justification
 1165 # is complete.
 1166 #####
 1167 
 1168 def PadString(string, width, Rjust=False, Trailing=0):
 1169 
 1170     s = string[:(width-1)]
 1171     if Rjust:
 1172         s = s.rjust(width)
 1173     else:
 1174         s = s.ljust(width)
 1175 
 1176     # Rotate 'Trailing' number of spaces from left of string to right
 1177     
 1178     while (Trailing > 0) and (s[0] == ' ') :
 1179         s = s[1:] + ' '
 1180         Trailing -= 1
 1181 
 1182     return s
 1183 
 1184 # End of 'PadString()'
 1185 
 1186 
 1187 #####
 1188 # Process The Configuraton File
 1189 # This is called once at program start time
 1190 # and again any time someone hits the READCONF key
 1191 # while the program is running.
 1192 #####
 1193 
 1194 def ProcessConfiguration(event, DoOptionsProcessing=True):
 1195     global CONF, UI, ConditionalStack
 1196 
 1197     # Cleanout any old configuration data
 1198 
 1199     UI.Associations  = {ASSOCEXCL:[]}
 1200     UI.CmdTable      = {}
 1201     UI.Commands      = []
 1202     UI.ConfigVisited = []
 1203     UI.DirSCKeys     = ["", "", "", "", "", "",
 1204                         "", "", "", "", "", ""]
 1205     UI.SymTable      = {}
 1206 
 1207 
 1208     # Initialize internal parsing data structures
 1209 
 1210     ConditionalStack = [True,]      # This is a sentinel and guarantees there will
 1211                                     # will always be something in this stack
 1212 
 1213     # Load Symbol Table with predefined symbols
 1214 
 1215     UI.SymTable[SYMOS] = os.name
 1216     UI.SymTable[SYMPLATFORM] = OSPLATFORM
 1217 
 1218 
 1219     # Unbind all existing key bindings
 1220     for x in UI.KeyBindings.keys():
 1221         UI.DirList.unbind(UI.KeyBindings[x])
 1222 
 1223     # Initialize keyboard binding variables to their defaults
 1224     # These may be overriden in the configuration file
 1225     # parsing process.
 1226 
 1227     UI.KeyBindings = {"CLRHIST":CLRHIST,
 1228                       "FONTDECR":FONTDECR,
 1229                       "FONTINCR":FONTINCR,
 1230                       "MOUSECTX":MOUSECTX,
 1231                       "MOUSEDIR":MOUSEDIR,
 1232                       "MOUSEHIST":MOUSEHIST,
 1233                       "MOUSESC":MOUSESC,
 1234                       "MOUSESORT":MOUSESORT,
 1235                       "KEYPRESS":KEYPRESS,
 1236                       "QUITPROG":QUITPROG,
 1237                       "READCONF":READCONF,
 1238                       "REFRESH":REFRESH,
 1239                       "TOGAUTO":TOGAUTO,
 1240                       "TOGDETAIL":TOGDETAIL,
 1241                       "TOGLENGTH":TOGLENGTH,
 1242                       "TOGSYMDIR":TOGSYMDIR,
 1243                       "TOGSYMEXPAND":TOGSYMEXPAND,
 1244                       "TOGSYMRESOLV":TOGSYMRESOLV,
 1245                       "TOGWIN32ALL":TOGWIN32ALL,
 1246                       "CHANGEDIR":CHANGEDIR,
 1247                       "DIRHOME":DIRHOME,
 1248                       "DIRBACK":DIRBACK,
 1249                       "DIRROOT":DIRROOT,
 1250                       "DIRSTART":DIRSTART,
 1251                       "DIRUP":DIRUP,
 1252                       "DRIVELIST":DRIVELIST,
 1253                       "MOUSEBACK":MOUSEBACK,
 1254                       "MOUSEUP":MOUSEUP,
 1255                       "SELALL":SELALL,
 1256                       "SELINV":SELINV,
 1257                       "SELNONE":SELNONE,
 1258                       "SELNEXT":SELNEXT,
 1259                       "SELPREV":SELPREV,
 1260                       "SELEND":SELEND,
 1261                       "SELTOP":SELTOP,
 1262                       "PGDN":PGDN,
 1263                       "PGUP":PGUP,
 1264                       "PGRT":PGRT,
 1265                       "PGLFT":PGLFT,
 1266                       "RUNCMD":RUNCMD,
 1267                       "SELKEY":SELKEY,
 1268                       "MOUSESEL":MOUSESEL,
 1269                       "KDIRSC1":KDIRSC1,
 1270                       "KDIRSC2":KDIRSC2,
 1271                       "KDIRSC3":KDIRSC3,
 1272                       "KDIRSC4":KDIRSC4,
 1273                       "KDIRSC5":KDIRSC5,
 1274                       "KDIRSC6":KDIRSC6,
 1275                       "KDIRSC7":KDIRSC7,
 1276                       "KDIRSC8":KDIRSC8,
 1277                       "KDIRSC9":KDIRSC9,
 1278                       "KDIRSC10":KDIRSC10,
 1279                       "KDIRSC11":KDIRSC11,
 1280                       "KDIRSC12":KDIRSC12,
 1281                       "KDIRSCSET":KDIRSCSET,
 1282                       "MEMCLR1":MEMCLR1,
 1283                       "MEMCLR2":MEMCLR2,
 1284                       "MEMCLR3":MEMCLR3,
 1285                       "MEMCLR4":MEMCLR4,
 1286                       "MEMCLR5":MEMCLR5,
 1287                       "MEMCLR6":MEMCLR6,
 1288                       "MEMCLR7":MEMCLR7,
 1289                       "MEMCLR8":MEMCLR8,
 1290                       "MEMCLR9":MEMCLR9,
 1291                       "MEMCLR10":MEMCLR10,
 1292                       "MEMCLR11":MEMCLR11,
 1293                       "MEMCLR12":MEMCLR12,
 1294                       "MEMCLRALL":MEMCLRALL,
 1295                       "MEMSET1":MEMSET1,
 1296                       "MEMSET2":MEMSET2,
 1297                       "MEMSET3":MEMSET3,
 1298                       "MEMSET4":MEMSET4,
 1299                       "MEMSET5":MEMSET5,
 1300                       "MEMSET6":MEMSET6,
 1301                       "MEMSET7":MEMSET7,
 1302                       "MEMSET8":MEMSET8,
 1303                       "MEMSET9":MEMSET9,
 1304                       "MEMSET10":MEMSET10,
 1305                       "MEMSET11":MEMSET11,
 1306                       "MEMSET12":MEMSET12,
 1307                       "SORTBYNONE":SORTBYNONE,
 1308                       "SORTBYPERMS":SORTBYPERMS,
 1309                       "SORTBYLINKS":SORTBYLINKS,
 1310                       "SORTBYOWNER":SORTBYOWNER,
 1311                       "SORTBYGROUP":SORTBYGROUP,
 1312                       "SORTBYLENGTH":SORTBYLENGTH,
 1313                       "SORTBYTIME":SORTBYTIME,
 1314                       "SORTBYNAME":SORTBYNAME,
 1315                       "SORTREV":SORTREV,
 1316                       "SORTSEP":SORTSEP,
 1317                       "MOUSEWILDFILTER":MOUSEWILDFILTER,
 1318                       "MOUSEWILDSEL":MOUSEWILDSEL,
 1319                       "FILTERWILD":FILTERWILD,
 1320                       "SELWILD":SELWILD,
 1321                       "TOGFILT":TOGFILT,
 1322                       "TOGHIDEDOT":TOGHIDEDOT
 1323                       }
 1324 
 1325     # Set all the program options to their default values
 1326     # This means that a configuration file reload can
 1327     # override the options set previously in the environment
 1328     # variable or on the command line.
 1329 
 1330     for x in (UI.OptionsBoolean, UI.OptionsNumeric, UI.OptionsString):
 1331         for o in x.keys():
 1332             globals()[o] = x[o]
 1333 
 1334     # If user specified a config file, try that
 1335     # Otherwise use HOME == either $HOME or ./
 1336 
 1337     if not CONF:
 1338         CONF = os.path.join(HOME, "." + PROGNAME)
 1339 
 1340     # Actually read and parse the configuration file.
 1341     ReadConfFile(CONF, STARTUP, 0)
 1342 
 1343     MissingEndIfs = len(ConditionalStack) - 1
 1344 
 1345     if MissingEndIfs:
 1346         WrnMsg(wMISSENDIF % str(MissingEndIfs))
 1347 
 1348     # Make sure any options we've changed are implemented
 1349     if DoOptionsProcessing:
 1350         ProcessOptions()
 1351 
 1352     # Initialize the command menu
 1353     UI.CmdBtn.menu.delete(0,END)
 1354 
 1355     # And disable it
 1356     UI.CmdBtn.config(state=DISABLED)
 1357 
 1358     # Now load the menu with the final set of commands
 1359 
 1360     # First see if the user wanted the list sorted
 1361     
 1362     if CMDMENUSORT:
 1363         UI.Commands.sort()
 1364 
 1365     for cmdkey in UI.Commands:
 1366         cmdname = UI.CmdTable[cmdkey][0]
 1367         UI.CmdBtn.menu.add_command(label=PadString(cmdname, CMDMENU_WIDTH) + "(" + cmdkey + ")",
 1368                                    command=lambda cmd=cmdkey: CommandMenuSelection(cmd))
 1369     # Enable the menu if it has entries.
 1370     # If no commands are defined, warn the user.
 1371     
 1372     if UI.CmdBtn.menu.index(END):
 1373         UI.CmdBtn['menu'] = UI.CmdBtn.menu
 1374         UI.CmdBtn.configure(state=NORMAL)
 1375     else:
 1376         WrnMsg(wNOCMDS)
 1377 
 1378     
 1379 
 1380     return 'break'
 1381 
 1382 # End of 'ProcessConfiguration()'
 1383 
 1384 
 1385 #####
 1386 # Read & Parse A Configuration File
 1387 # Called By ProcessConfiguration() And Each Time
 1388 # A '.include' Is Encountered Within A Configuration File
 1389 #####
 1390 
 1391 def ReadConfFile(newfile, currentfile, linenum):
 1392 
 1393     # Keep track of every configuration file processed
 1394 
 1395     fqfilename = os.path.abspath(newfile)
 1396     
 1397     if fqfilename not in UI.ConfigVisited:
 1398         UI.ConfigVisited.append(fqfilename)
 1399 
 1400     # And prevent circular references
 1401 
 1402     else:
 1403         WrnMsg(wCIRCULARREF % (currentfile, linenum))
 1404         return
 1405 
 1406     # Keep track of the line number on a per-file basis
 1407     
 1408     linenum = 0
 1409     try:
 1410         cf = open(newfile)
 1411         # Successful open of config file - Begin processing it
 1412 
 1413         # Process and massage the configuration file
 1414         for line in cf.read().splitlines():
 1415             linenum += 1
 1416 
 1417             # Parse this line
 1418             if line:
 1419                 ParseLine(line, newfile, linenum)
 1420 
 1421         # Close the config file
 1422         cf.close()
 1423 
 1424     except:
 1425         WrnMsg(wCONFOPEN % newfile)
 1426 
 1427 # End of 'ReadConfFile()'
 1428 
 1429 
 1430 #####
 1431 # Parse A Line From A Configuration File
 1432 #####
 1433 
 1434 
 1435 def ParseLine(line, file, num):
 1436     global UI, ConditionalStack
 1437 
 1438     ###
 1439     # Cleanup the line
 1440     ###
 1441     
 1442     # Get rid of trailing newline, if any
 1443 
 1444     if line[-1] == '\n':
 1445         line = line[:-1]
 1446 
 1447     # Strip comments out
 1448     
 1449     idx = line.find(COMMENT)
 1450     if idx > -1:
 1451         line = line[:idx]
 1452 
 1453     # Strip off leading/trailing spaces
 1454     cleanline = line.strip()
 1455 
 1456     # Preserve the contents of PROMPT or YESNO builtins.
 1457 
 1458     # If we didn't do this, the split() below would trash the
 1459     # whitespace within prompts/defaults.  The user may
 1460     # specifically *want* whitespace formatting in either the
 1461     # prompt or the default, so we need to preserve their
 1462     # entry exactly as-is.
 1463 
 1464     # Algorithm:
 1465     #
 1466     #   1) Find every instance of a PROMPT or YESNO builtin
 1467     #   2) Save it in 'saveliteral'
 1468     #   3) Replace it with a bogus,  *unsplittable* string ending with its 'saveliteral' index
 1469     #   4) Split the line
 1470     #   5) Replace original content in the appropriate spot(s)
 1471     
 1472 
 1473     # Steps 1-3
 1474     
 1475     saveliteral = {}
 1476     index = 0
 1477 
 1478     for matchtest in (REPROMPT, REYESNO):
 1479         for match in matchtest.finditer(cleanline):
 1480             fakefield = FAKEFIELD + str(index)
 1481             found=match.group()
 1482             saveliteral[fakefield] = found
 1483             cleanline = cleanline.replace(found, fakefield)
 1484             index += 1
 1485 
 1486 
 1487     # Step 4: Split what's left into separate fields again
 1488 
 1489     fields = cleanline.split()
 1490 
 1491     # Step 5: Now restore the strings we want to preserve literally (if any)
 1492 
 1493     # Scan through each field, replacing fake entries with the original text.
 1494 
 1495     for fake in saveliteral.keys():
 1496         index = 0
 1497         while index < len(fields):
 1498             if fields[index].count(fake):
 1499                 fields[index] = fields[index].replace(fake, saveliteral[fake])
 1500             index += 1
 1501 
 1502     # Make a copy of the fields which is guaranteed to have at 
 1503     # least two fields for use in the variable declaration tests.
 1504     
 1505     dummy = fields[:]
 1506     dummy.append("")
 1507 
 1508 
 1509     # Before going on, make sure we're even supposed to process the line.
 1510     # If we are currently in the midst of a *false* conditional
 1511     # block, we must throw this line away.  The only thing
 1512     # we'll accept in that case is more conditional statements.
 1513 
 1514     if (not ConditionalStack[-1]) and (dummy[0] not in (CONDIF, CONDENDIF)):
 1515         return
 1516     
 1517     ###
 1518     # Blank Lines - Ignore
 1519     ###
 1520 
 1521     if len(fields) == 0:
 1522         pass
 1523 
 1524     ###
 1525     # Conditionals
 1526     ###
 1527 
 1528     # Legal conditional statements are in one of several forms:
 1529     #
 1530     # .if [SYMBOL]
 1531     # .if [SYMBOL] == string OR .if [SYMBOL] != string
 1532     # .if [SYMBOL]== string  OR .if [SYMBOL]!= string
 1533     # .if [SYMBOL] ==string  OR .if [SYMBOL] !=string
 1534     # .if [SYMBOL]==string   OR .if [SYMBOL]!=string
 1535     # .endif
 1536 
 1537     # Additionally, [SYMBOL] can also be an environment
 1538     # variable - [$SYMBOL]
 1539 
 1540     #####
 1541     # Process Conditional Beginning-Of-Block Statement
 1542     #####
 1543 
 1544     elif fields[0] == CONDIF:
 1545 
 1546         # Hack off the conditional statement so we can
 1547         # process what's left
 1548 
 1549         condline = cleanline[len(CONDIF):].strip()
 1550         
 1551         # Iterate through all legitimate possible
 1552         # beginning-of-block forms.  The iteration
 1553         # tuple is in the form: (condition, # of required arguments)
 1554 
 1555         args = []
 1556         condition, var, cmpstr = "", "", ""
 1557         
 1558         for condtype, numargs in [(CONDEQUAL, 2), (CONDNOTEQUAL, 2), (None, 1)]:
 1559 
 1560             # Process forms that have arguments following the variable reference
 1561             if condtype:
 1562                 if condline.count(condtype):
 1563                     args      = condline.split(condtype)
 1564                     condition = condtype
 1565                     break
 1566                 
 1567             # Process the existential conditional form
 1568 
 1569             else:
 1570                 args      = condline.split()
 1571                 condition = condtype
 1572                 break
 1573 
 1574         # Check for correct syntax
 1575         # We have to have the right number of arguments, AND
 1576         # the condition variable has to be in proper variable reference form: [VAR] AND
 1577         # the rightmost argument cannot be an empty string
 1578 
 1579         if (len(args) != numargs) or (not CONDVAR.match(args[0].strip())) or (not args[-1]):
 1580             WrnMsg(wBADIF % (num, line), fn=file)
 1581             return
 1582 
 1583         # Syntax OK, process the conditional test
 1584         else:
 1585 
 1586             # Assume the conditional test fails
 1587             conditional = False
 1588 
 1589             # Strip the reference syntax to get just the variable name
 1590             var    = args[0].strip()[1:-1]
 1591 
 1592             # Handle the equality tests
 1593             
 1594             if condition:
 1595                 
 1596                 # De-reference the variable's contents, accomodating
 1597                 # both Environment and User-Defined variable types
 1598 
 1599                 if var[0] == ENVVBL:
 1600                     var = os.getenv(var[1:])
 1601 
 1602                 else:
 1603                     var = UI.SymTable.get(var)
 1604 
 1605 
 1606                 # Get the comparison string
 1607                 cmpstr = args[1].strip()
 1608 
 1609                 # Now process each type of condition explicitly
 1610 
 1611                 if condition == CONDEQUAL:
 1612                     if var == cmpstr:
 1613                         conditional = True
 1614 
 1615                 elif condition == CONDNOTEQUAL:
 1616                     if var != cmpstr:
 1617                         conditional = True
 1618 
 1619             # Handle the existential conditional
 1620             else:
 1621 
 1622                 if var[0] == ENVVBL:
 1623                     if os.environ.has_key(var[1:]):
 1624                         conditional = True
 1625 
 1626                 elif UI.SymTable.has_key(var):
 1627                     conditional = True
 1628 
 1629         # Even if the current conditional is True, we do not
 1630         # process its contents if the *containing* scope is False.
 1631         # i.e., A given conditional's truth is determined by its
 1632         # own state AND the state of the containing scope.
 1633 
 1634         ConditionalStack.append(ConditionalStack[-1] and conditional)
 1635 
 1636     #####
 1637     # Process Conditional End-Of-Block Statement
 1638     #####
 1639     
 1640     elif fields[0] ==  CONDENDIF:
 1641 
 1642         # The end-of-block statement must be on a line by itself
 1643         
 1644         if len(fields) != 1:
 1645             WrnMsg(wBADENDIF % (num, line), fn=file)
 1646             
 1647         # The conditional stack must always have 1 value left in
 1648         # it *after* all conditional processing.  If it does not,
 1649         # it means there are more .endifs than .ifs.
 1650 
 1651         elif len(ConditionalStack) == 1:
 1652             WrnMsg(wEXTRAENDIF % (num, line), fn=file)
 1653 
 1654         else:
 1655             ConditionalStack.pop()
 1656 
 1657     #####
 1658     # Process Include Directive
 1659     #####
 1660 
 1661     elif fields[0] == DIRECTINC:
 1662         ReadConfFile(cleanline.split(DIRECTINC)[1].strip(), file, num)
 1663         
 1664 
 1665     ###
 1666     # Variable Definitions And Special Assignments
 1667     ###
 1668 
 1669     # A valid variable definition can
 1670     # be 1, 2, or 3 fields:
 1671     #
 1672     #  x=y    - 1 field
 1673     #  x= y   - 2 fields
 1674     #  x =y
 1675     #  x = y  - 3 fields
 1676     #
 1677     # But this is illegal
 1678     #
 1679     #  =.......
 1680     #
 1681     # However, the assignment character
 1682     # must always been in the 1st or second
 1683     # field.  If it is a 3rd field, it is not
 1684     # a variable definition, but a command definition.
 1685     #
 1686     # If the LHS is one of the Program Function Names
 1687     # used in key binding, the statement is understood
 1688     # to be a key rebinding, not a user variable definition.
 1689     #
 1690     # If the LHS is one of the Directory Shortcut variables,
 1691     # the RHS is added to the Directory Menu and assigned
 1692     # to the associated Function Key (1-12).
 1693     #
 1694     # Finally, the LHS cannot be one of the program
 1695     # Built-In Variables - it is an error, for example,
 1696     # to have something like:
 1697     #
 1698     #        DIR = string
 1699     #
 1700     # because "DIR" is a Built-In Variable name.
 1701     #
 1702 
 1703     elif ((dummy[0].count(ASSIGN) + dummy[1].count(ASSIGN)) > 0) and (fields[0][0] != ASSIGN):
 1704         
 1705         assign = cleanline.find(ASSIGN)
 1706         name = cleanline[:assign].strip()
 1707         val=cleanline[assign+1:].strip()
 1708 
 1709         # Error out on any attempt to "define" a Built-In Variable
 1710         
 1711         if UI.BuiltIns.has_key('[' + name + ']') or UI.BuiltIns.has_key('[' + name):
 1712             WrnMsg(wREDEFVAR % (num, name, line), fn=file)
 1713             return
 1714             
 1715         # Handle Directory Shortcut entries.
 1716         
 1717         if REDIRSC.match(name):
 1718 
 1719             # Get shortcut number
 1720             sc = int(name.split(DIRSC)[1])
 1721 
 1722             # Process if in range
 1723             if 0 < sc < NUMFUNCKEY + 1:
 1724 
 1725                 # Associate the directory with the correct shortcut key
 1726                 UI.DirSCKeys[sc-1] = val
 1727 
 1728             # User specified an invalid shortcut number
 1729             else:
 1730                 WrnMsg(wBADSCNUM % (num, line), fn=file)
 1731                 return
 1732 
 1733         # Process any wildcard definitions
 1734         
 1735         elif name == WILDFILTER:
 1736             if val and (val not in UI.FilterHist):
 1737                 UpdateMenu(UI.FilterBtn, UI.FilterHist, MAXMENU, MAXMENUBUF, KeyFilterWild, newentry=val, fakeevent=True)
 1738                 
 1739         elif name == WILDSELECT:
 1740             if val and (val not in UI.SelectHist):
 1741                 UpdateMenu(UI.SelectBtn, UI.SelectHist, MAXMENU, MAXMENUBUF, KeySelWild, newentry=val, fakeevent=True)
 1742                 
 1743         # Process any option variables - blank RHS is OK and means to leave
 1744         # option set to its default value.
 1745 
 1746         elif name in UI.OptionsBoolean.keys():
 1747             if val:
 1748                 val = val.capitalize()
 1749                 if val == 'True' or val == 'False':
 1750                     globals()[name] = eval(val)               # !!! Cheater's way to get to global variables.
 1751                 else:
 1752                     WrnMsg(wBADRHS % (num, line), fn=file)
 1753                     return
 1754 
 1755         elif name in UI.OptionsNumeric.keys():
 1756             if val:
 1757                 try:
 1758                     val = StringToNum(val)
 1759                     if val >= 0:
 1760                         globals()[name] = val
 1761                     else:
 1762                         WrnMsg(wBADRHS % (num, line), fn=file)
 1763                         return
 1764                 except:
 1765                     WrnMsg(wBADRHS % (num, line), fn=file)
 1766                     return
 1767 
 1768         elif name in UI.OptionsString.keys():
 1769             if val:
 1770                 globals()[name] = val
 1771 
 1772 
 1773         # Process other variable types.
 1774         # Distinguish between internal program variables and
 1775         # user-defined variables and act accordingly.  We inhibit
 1776         # the rebinding of certain, special assignments, however.
 1777 
 1778         elif name in UI.KeyBindings.keys():
 1779             if name in NOREBIND:
 1780                 WrnMsg(wNOREBIND % (num, line), fn=file)
 1781                 return
 1782             else:
 1783                 UI.KeyBindings[name] = val
 1784         else:
 1785             UI.SymTable[name] = val
 1786 
 1787     ###
 1788     # Command Definitions And Associations
 1789     ###
 1790 
 1791     elif (len(fields[0]) == 1) or (fields[0] == ASSOCIATE):
 1792 
 1793         # Handle the case of association statements w/blank RHS
 1794 
 1795         if (len(fields) == 2) and (fields[0] == ASSOCIATE):
 1796             fields.append(ASSOCBLANK)
 1797 
 1798         # Must have at least 3 fields for a valid command definition
 1799         if len(fields) < 3:
 1800             WrnMsg(wBADCFGLINE % (num, line), fn=file)
 1801             return
 1802         else:
 1803             cmdkey = fields[0]
 1804             cmdname = fields[1]
 1805             cmd = " ".join(fields[2:])
 1806 
 1807             # A null return means there was a problem - abort
 1808             if not cmd:
 1809                 return
 1810 
 1811             # Store associations for use at execution time
 1812 
 1813             if cmdkey == ASSOCIATE:
 1814 
 1815                 # Blank RHS implies user wants association removed
 1816 
 1817                 if cmd == ASSOCBLANK:
 1818                     if cmdname == ASSOCEXCL:               # Null out the exclusion list
 1819                         UI.Associations[cmdname] = []
 1820                     else:
 1821                         if cmdname in UI.Associations:  # Only blank out entries if they actually exist
 1822                             del UI.Associations[cmdname]
 1823                 
 1824                 # Process association exclusions
 1825                 
 1826                 elif cmdname == ASSOCEXCL:
 1827                     for exclude in cmd.split():
 1828                         if exclude not in UI.Associations[cmdname]:  # Avoid duplicates
 1829                             UI.Associations[cmdname].append(exclude)
 1830 
 1831                 # Process normal association statements
 1832 
 1833                 else:
 1834                     UI.Associations[cmdname] = cmd
 1835                 return
 1836 
 1837 
 1838             # Add the command entry to the command table.
 1839             # If the key is a duplicate, the older definition is
 1840             # overwritten.
 1841 
 1842             UI.CmdTable[cmdkey] = [cmdname, cmd]
 1843 
 1844             # Keep track of the order in which the commands
 1845             # were defined - we want them to show up that
 1846             # way in the Command Menu so user can put
 1847             # most-used commands near the top.
 1848             # Do this suppressing duplicates.
 1849 
 1850             if cmdkey in UI.Commands:
 1851                 UI.Commands.remove(cmdkey)
 1852 
 1853             UI.Commands.append(cmdkey)
 1854 
 1855     else:
 1856         WrnMsg(wBADCFGLINE % (num, line), fn=file)
 1857 
 1858 # End of 'ParseLine()'
 1859 
 1860 
 1861 #####
 1862 # Print Debug Information On stdout
 1863 #####
 1864 
 1865 def PrintDebug(title, content):
 1866 
 1867     print '<%s>\n' % title.upper()
 1868     if content:
 1869         for i in content:
 1870             print i
 1871     else:
 1872         print dNULL
 1873     print
 1874 
 1875 # End of 'PrintDebug()'
 1876 
 1877 
 1878 #####
 1879 # Setup The GUI Visual Parameters, Menus, & Help Information 
 1880 #####
 1881 
 1882 def SetupGUI():
 1883 
 1884     # Start in detailed mode unless details are inhibited
 1885     UI.SetDetailedView(not NODETAILS)
 1886 
 1887     # Rebind all the handlers
 1888     UI.BindAllHandlers()
 1889 
 1890     # Any user-set options have now been read, set the GUI
 1891 
 1892     for i in (UI.CmdBtn, UI.DirBtn, UI.HistBtn, UI.ShortBtn, UI.SortBtn, UI.FilterBtn, UI.SelectBtn, UI.HelpBtn):
 1893         i.config(foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 1894         i.menu.config(foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 1895 
 1896     # Set Menu Bar background to match buttons
 1897     UI.mBar.config(background=MBARCOL)
 1898 
 1899     UI.DirList.config(font=(FNAME, FSZ, FWT),
 1900                       foreground=FCOLOR, background=BCOLOR)
 1901 
 1902 
 1903     # Make sure menus conform to max lengths (which may have changed).
 1904 
 1905     UpdateMenu(UI.DirBtn, UI.AllDirs, MAXMENU, MAXMENUBUF, LoadDirList, sort=True)    
 1906     UpdateMenu(UI.HistBtn, UI.CmdHist, MAXMENU, MAXMENUBUF, KeyRunCommand, fakeevent=True)
 1907     UpdateMenu(UI.FilterBtn, UI.FilterHist, MAXMENU, MAXMENUBUF, KeyFilterWild, fakeevent=True)
 1908     UpdateMenu(UI.SelectBtn, UI.SelectHist, MAXMENU, MAXMENUBUF, KeySelWild, fakeevent=True)
 1909 
 1910     # Initialize the Sorting Menu
 1911 
 1912     LoadSortMenu()
 1913 
 1914     # Initialize the Help Menu
 1915     LoadHelpMenu()
 1916 
 1917     # Initialize the Shortcut Menu
 1918     LoadShortcutMenu()
 1919 
 1920     # Size and position the display
 1921     UIroot.geometry("%sx%s+%s+%s" % (WIDTH,  HEIGHT, STARTX, STARTY))
 1922 
 1923 # End of 'SetupGUI()'
 1924 
 1925 
 1926 #####
 1927 # Load The Sorting Menu with the latest information
 1928 #####
 1929 
 1930 def LoadSortMenu():
 1931 
 1932     # Sort Menu content is different if in Drive List View
 1933     # Show options appropriate to the current view
 1934 
 1935     if UI.CurrentDir == SHOWDRIVES:
 1936         idx = 2
 1937     else:
 1938         idx = 1
 1939 
 1940     # Clear out any old entries
 1941 
 1942     UI.SortBtn.config(state=DISABLED)
 1943     UI.SortBtn.menu.delete(0,END)
 1944     
 1945     # Add the menu selections
 1946     
 1947     for entry in [fNONE, fPERMISSIONS, fLINKS, fOWNER, fGROUP, fLENGTH, fDATE, fNAME, fREVERSE, fSEPARATE]:
 1948 
 1949         t = Name2Key[entry.lower()]
 1950         if t[idx]:
 1951             UI.SortBtn.menu.add_command(label=t[idx], command=lambda parm=t[1] : KeySetSortParm(parm))
 1952 
 1953     # Store the current directory - used in subsequent calls to this function to
 1954     # determine whether or not we're moving between Normal <--> Drive List Views
 1955 
 1956     UI.SortBtn.CurrentDir = UI.CurrentDir
 1957 
 1958     # Enable the menu selections
 1959     
 1960     UI.SortBtn['menu'] = UI.SortBtn.menu
 1961     UI.SortBtn.config(state=NORMAL)
 1962 
 1963 # End of 'LoadSortMenu()'
 1964 
 1965 
 1966 #####
 1967 # Load Help Menu with latest information
 1968 #####
 1969 
 1970 def LoadHelpMenu():
 1971     
 1972     # Clear out existing content
 1973     
 1974     UI.HelpBtn.config(state=DISABLED)
 1975     UI.HelpBtn.menu.delete(0,END)
 1976 
 1977 
 1978     # Update the cascading submenus
 1979     # We iterate across tuples of (Menu Name, Menu Variable, List Of Items)
 1980 
 1981     for mname, mvbl, mlist in ((hINTVBLS,  UI.IntVbls,  GetIntVars()),
 1982                                (hOPTVBLS,  UI.OptVbls,  GetOptions()),
 1983                                (hKEYS,     UI.Keys,     FormatMultiColumn(UI.KeyBindings)),
 1984                                (hUSERVBLS, UI.UserVbls, GetUserVbls()),
 1985                                (hCOMMANDS, UI.CmdDefs,  GetCommandTable()),
 1986                                (hASSOC,    UI.Assocs,   GetAssocTable())
 1987                               ):
 1988 
 1989         mvbl.delete(0,END)                       
 1990         
 1991         # Indicated if there is nothing to display for this class of help
 1992         if not mlist:
 1993             mvbl.add_command(label=hNONE % mname, command=None, foreground=HFCOLOR, background=HBCOLOR,
 1994                              font=(HFNAME, HFSZ, HFWT))
 1995 
 1996         # Load the help class with relevant information
 1997         else:
 1998             for l in mlist:
 1999                 mvbl.add_command(label=l, command=None, foreground=HFCOLOR, background=HBCOLOR, font=(HFNAME, HFSZ, HFWT))
 2000 
 2001         UI.HelpBtn.menu.add_cascade(label=mname, menu=mvbl)
 2002 
 2003     # Setup the About item
 2004 
 2005     UI.HelpBtn.menu.add_command(label=hABOUT, command=lambda title=hABOUT, text=ABOUT : showinfo(title, text))
 2006 
 2007     # Enable the menu content
 2008     
 2009     UI.HelpBtn['menu'] = UI.HelpBtn.menu
 2010     UI.HelpBtn.config(state=NORMAL)
 2011 
 2012 # End of 'LoadHelpMenu()'
 2013 
 2014 
 2015 
 2016 #####
 2017 # Load The Shortcut Menu with the latest information
 2018 #####
 2019 
 2020 def LoadShortcutMenu():
 2021 
 2022     UI.ShortBtn.config(state=DISABLED)
 2023     UI.ShortBtn.menu.delete(0,END)
 2024     
 2025     # Add Standard Navigation Shortcuts
 2026     
 2027     UI.ShortBtn.menu.add_command(label="Up", command=lambda: KeyUpDir(None))
 2028     UI.ShortBtn.menu.add_command(label="Back", command=lambda: KeyBackDir(None))
 2029     UI.ShortBtn.menu.add_command(label="Home", command=lambda: KeyHomeDir(None))
 2030     UI.ShortBtn.menu.add_command(label="Startdir", command=lambda: KeyStartDir(None))
 2031     UI.ShortBtn.menu.add_command(label="Root", command=lambda: KeyRootDir(None))
 2032 
 2033     # If were on Win32 and have the extensions loaded also offer the drive list
 2034     
 2035     if OSPLATFORM == 'win32' and GetWin32Drives():
 2036         UI.ShortBtn.menu.add_command(label="DriveList", command=lambda: KeyDriveList(None))
 2037 
 2038     # Add Shortcut Key Definitions
 2039 
 2040     idx=1
 2041     for entry in UI.DirSCKeys:
 2042         if entry:
 2043             pad=" "
 2044             if idx < 10:
 2045                 pad += " "
 2046             UI.ShortBtn.menu.add_command(label="SC%s:%s%s" % (idx, pad, entry),  command=lambda parm=idx: DirSCKeyPress(None, parm))
 2047         idx += 1
 2048 
 2049     # Enable the menu selections
 2050     
 2051     UI.ShortBtn['menu'] = UI.ShortBtn.menu
 2052     UI.ShortBtn.config(state=NORMAL)
 2053 
 2054 # End of 'LoadShortcutMenu()'
 2055 
 2056 
 2057 #####
 2058 # Convert A String In Integer Or Hex Format To An Equivalent Numeric
 2059 # We assume that the string is either in correct format or that
 2060 # the calling routine will catch any error.
 2061 #####
 2062 
 2063 def StringToNum(string):
 2064 
 2065     if string.lower().startswith('0x'):
 2066         value = int(string, 16)
 2067     else:
 2068         value = int(string, 10)
 2069 
 2070     return value
 2071 
 2072 # End of 'StringToNum()
 2073 
 2074 
 2075 #####
 2076 # Strip Trailing Path Separator
 2077 #####
 2078 
 2079 def StripPSEP(s):
 2080 
 2081     if s and s[-1] == PSEP:
 2082         return s[:-1]
 2083     else:
 2084         return s
 2085 
 2086 # End of 'StripPSEP()'
 2087 
 2088 
 2089 #####
 2090 # Print Usage Information
 2091 #####
 2092 
 2093 def Usage():
 2094 
 2095     for x in uTable:
 2096         print x
 2097 
 2098 # End of 'Usage()'
 2099 
 2100 
 2101 #####
 2102 # Check Debug Level To See If It Is A Properly Formed Integer Or Hex Value
 2103 # If So, Convert To Numeric, If Not, Warn User, And Set To 0
 2104 #####
 2105 
 2106 def ValidateDebugLevel():
 2107     global DEBUGLEVEL
 2108 
 2109     d = DEBUGLEVEL  # Save, in case of error
 2110     try:
 2111         DEBUGLEVEL = StringToNum(DEBUGLEVEL)
 2112     except:
 2113         DEBUGLEVEL = 0
 2114         WrnMsg(wBADDEBUG % d)
 2115 
 2116 # End of 'ValidateDebugLevel()'
 2117 
 2118 
 2119 #####
 2120 # Print A Warning Message
 2121 #####
 2122 
 2123 def WrnMsg(wmsg, fn=""):
 2124     if WARN:
 2125         showwarning("%s %s    %s    %s" % (PROGNAME, VERSION, wWARN, fn), wmsg)
 2126 
 2127 # End of 'WrnMsg()'
 2128 
 2129 
 2130 #----------------------------------------------------------#
 2131 #                    GUI Definition                        #
 2132 #----------------------------------------------------------#
 2133 
 2134 
 2135 
 2136 #####
 2137 # Enacapsulate the UI in a class
 2138 #####
 2139 
 2140 
 2141 class twanderUI:
 2142 
 2143     def __init__(self, root):
 2144 
 2145         # Setup Menubar frame
 2146         
 2147         self.mBar = Frame(root, relief=RAISED, borderwidth=MENUBORDER)
 2148         self.mBar.pack(fill=X)
 2149         
 2150         # Setup the Command Menu
 2151 
 2152         self.CmdBtn = Menubutton(self.mBar, text=COMMANDMENU, underline=0, state=DISABLED)
 2153         self.CmdBtn.menu = Menu(self.CmdBtn)
 2154         self.CmdBtn.pack(side=LEFT, padx=MENUPADX)
 2155 
 2156         # Setup the History Menu
 2157 
 2158         self.HistBtn = Menubutton(self.mBar, text=HISTMENU, underline=0, state=DISABLED)
 2159         self.HistBtn.menu = Menu(self.HistBtn)
 2160         self.HistBtn.pack(side=LEFT, padx=MENUPADX)
 2161 
 2162         # Setup the Directory Menu
 2163 
 2164         self.DirBtn = Menubutton(self.mBar, text=DIRMENU, underline=0, state=DISABLED)
 2165         self.DirBtn.menu = Menu(self.DirBtn)
 2166         self.DirBtn.pack(side=LEFT, padx=MENUPADX)
 2167 
 2168         # Setup the Shortcut Menu
 2169 
 2170         self.ShortBtn = Menubutton(self.mBar, text=SCMENU, underline=6, state=DISABLED)
 2171         self.ShortBtn.menu = Menu(self.ShortBtn)
 2172         self.ShortBtn.pack(side=LEFT, padx=MENUPADX)
 2173 
 2174         # Setup the Filter Wildcard Menu
 2175 
 2176         self.FilterBtn = Menubutton(self.mBar, text=FILTERMENU, underline=0, state=DISABLED)
 2177         self.FilterBtn.menu = Menu(self.FilterBtn)
 2178         self.FilterBtn.pack(side=LEFT, padx=MENUPADX)
 2179 
 2180         # Setup the Selection Wildcard Menu
 2181 
 2182         self.SelectBtn = Menubutton(self.mBar, text=SELECTMENU, underline=5, state=DISABLED)
 2183         self.SelectBtn.menu = Menu(self.SelectBtn)
 2184         self.SelectBtn.pack(side=LEFT, padx=MENUPADX)
 2185 
 2186         # Setup the Sort Menu
 2187 
 2188         self.SortBtn = Menubutton(self.mBar, text=SORTMENU, underline=0, state=DISABLED)
 2189         self.SortBtn.menu = Menu(self.SortBtn)
 2190         self.SortBtn.pack(side=LEFT, padx=MENUPADX)
 2191 
 2192         # Setup the Help Menu
 2193 
 2194         self.HelpBtn = Menubutton(self.mBar, text=HELPMENU, underline=2, state=DISABLED)
 2195         self.HelpBtn.menu = Menu(self.HelpBtn)
 2196         self.HelpBtn.pack(side=LEFT, padx=MENUPADX)
 2197         
 2198         # Setup the cascading submenus
 2199         
 2200         self.IntVbls  = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 2201         self.OptVbls  = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 2202         self.Keys     = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 2203         self.UserVbls = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 2204         self.CmdDefs  = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 2205         self.Assocs   = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT))
 2206 
 2207         # Setup the Directory Listing and Scrollbars
 2208 
 2209         self.hSB = Scrollbar(root, orient=HORIZONTAL)
 2210         self.vSB = Scrollbar(root, orient=VERTICAL)
 2211         self.DirList = Listbox(root, selectmode=EXTENDED, exportselection=0,
 2212                                xscrollcommand=self.hSB.set, yscrollcommand=self.vSB.set)
 2213 
 2214         # Make them visible by packing
 2215         
 2216         self.hSB.config(command=self.DirList.xview)
 2217         self.hSB.pack(side=BOTTOM, fill=X)
 2218         self.vSB.config(command=self.DirList.yview)
 2219         self.vSB.pack(side=RIGHT, fill=Y)
 2220         self.DirList.pack(side=LEFT, fill=BOTH, expand=1)
 2221 
 2222         # End of method 'twanderUI.__init__()'
 2223 
 2224 
 2225     ###
 2226     # Bind the relevant event handlers
 2227     ###
 2228         
 2229     def BindAllHandlers(self):
 2230 
 2231         ###
 2232         # General Program Commands
 2233         ###
 2234 
 2235         # Bind handler to invoke Clear Command History
 2236         self.DirList.bind(self.KeyBindings["CLRHIST"], ClearHistory)
 2237 
 2238         # Bind handler to invoke Decrement Font Size
 2239         self.DirList.bind(self.KeyBindings["FONTDECR"], FontDecr)
 2240 
 2241         # Bind handler to invoke Increment Font Size
 2242         self.DirList.bind(self.KeyBindings["FONTINCR"], FontIncr)
 2243 
 2244         # Bind handler to invoke Command Menu
 2245         self.DirList.bind(self.KeyBindings["MOUSECTX"], MouseClick)
 2246 
 2247         # Bind handler to invoke Directory Menu
 2248         self.DirList.bind(self.KeyBindings["MOUSEDIR"], MouseClick)
 2249 
 2250         # Bind handler to invoke Directory Menu
 2251         self.DirList.bind(self.KeyBindings["MOUSEHIST"], MouseClick)
 2252 
 2253         # Bind handler to invoke Directory Menu
 2254         self.DirList.bind(self.KeyBindings["MOUSESC"], MouseClick)
 2255 
 2256         # Bind handler to invoke Directory Menu
 2257         self.DirList.bind(self.KeyBindings["MOUSESORT"], MouseClick)
 2258 
 2259         # Bind handler for individual keystrokes
 2260         self.DirList.bind(self.KeyBindings["KEYPRESS"], KeystrokeHandler)
 2261 
 2262         # Bind handler for "Quit Program"
 2263         self.DirList.bind(self.KeyBindings["QUITPROG"], KeyQuitProg)
 2264 
 2265         # Bind handler for "Read Config File"
 2266         self.DirList.bind(self.KeyBindings["READCONF"], ProcessConfiguration)
 2267 
 2268         # Bind handler for "Refresh Screen" 
 2269         self.DirList.bind(self.KeyBindings["REFRESH"], lambda event : RefreshDirList(event, ClearFilterWildcard=True))
 2270 
 2271         # Bind handler for "Toggle Autorefresh" 
 2272         self.DirList.bind(self.KeyBindings["TOGAUTO"], KeyToggleAuto)
 2273 
 2274         # Bind handler for "Toggle Detail" 
 2275         self.DirList.bind(self.KeyBindings["TOGDETAIL"], KeyToggleDetail)
 2276 
 2277         # Bind handler for "Toggle Length Display" 
 2278         self.DirList.bind(self.KeyBindings["TOGLENGTH"],lambda event :  KeyToggle(event, "ACTUALLENGTH"))
 2279 
 2280         # Bind handler for "Toggle Sorting Of Symlinks Pointing To Directories"
 2281         self.DirList.bind(self.KeyBindings["TOGSYMDIR"],lambda event :  KeyToggle(event, "SYMDIR"))
 2282         
 2283         # Bind handler for "Toggle Expand Symlinks"
 2284         self.DirList.bind(self.KeyBindings["TOGSYMEXPAND"],lambda event :  KeyToggle(event, "SYMEXPAND"))
 2285         
 2286         # Bind handler for "Toggle Resolve Symlinks"
 2287         self.DirList.bind(self.KeyBindings["TOGSYMRESOLV"],lambda event :  KeyToggle(event, "SYMRESOLV"))
 2288         
 2289 
 2290         # Bind handler for "Toggle win32all Features" 
 2291         self.DirList.bind(self.KeyBindings["TOGWIN32ALL"], KeyToggleWin32All)
 2292 
 2293         ###
 2294         # Directory Navigation
 2295         ###
 2296 
 2297         # Bind handler for "Change Directory"
 2298         self.DirList.bind(self.KeyBindings["CHANGEDIR"], KeyChangeDir)
 2299 
 2300         # Bind handler for "Home Dir"
 2301         self.DirList.bind(self.KeyBindings["DIRHOME"], KeyHomeDir)
 2302 
 2303         # Bind handler for "Previous Dir"
 2304         self.DirList.bind(self.KeyBindings["DIRBACK"], KeyBackDir)
 2305 
 2306         # Bind handler for "Root Dir"
 2307         self.DirList.bind(self.KeyBindings["DIRROOT"], KeyRootDir)
 2308 
 2309         # Bind handler for "Starting Dir"
 2310         self.DirList.bind(self.KeyBindings["DIRSTART"], KeyStartDir)
 2311 
 2312         # Bind handler for "Up Dir"
 2313         self.DirList.bind(self.KeyBindings["DIRUP"], KeyUpDir)
 2314 
 2315         # Bind handler for "Display Drive View"
 2316         self.DirList.bind(self.KeyBindings["DRIVELIST"], KeyDriveList)
 2317 
 2318         # Bind handler for "Mouse Back Dir"
 2319         self.DirList.bind(self.KeyBindings["MOUSEBACK"], MouseDblClick)
 2320 
 2321         # Bind handler for "Mouse Up Dir"
 2322         self.DirList.bind(self.KeyBindings["MOUSEUP"], MouseDblClick)
 2323 
 2324         ###
 2325         # Selection Keys
 2326         ###
 2327 
 2328         # Bind handler for "Select All"
 2329         self.DirList.bind(self.KeyBindings["SELALL"], KeySelAll)
 2330 
 2331         # Bind handler for "Invert Current Selection"
 2332         self.DirList.bind(self.KeyBindings["SELINV"], KeySelInv)
 2333 
 2334         # Bind handler for "Select No Items"
 2335         self.DirList.bind(self.KeyBindings["SELNONE"], KeySelNone)
 2336 
 2337         # Bind handler for "Next Item"
 2338         self.DirList.bind(self.KeyBindings["SELNEXT"], KeySelNext)
 2339 
 2340         # Bind handler for "Previous Item"
 2341         self.DirList.bind(self.KeyBindings["SELPREV"], KeySelPrev)
 2342 
 2343         # Bind handler for "Last Item"
 2344         self.DirList.bind(self.KeyBindings["SELEND"], KeySelEnd)
 2345 
 2346         # Bind handler for "First Item"
 2347         self.DirList.bind(self.KeyBindings["SELTOP"], KeySelTop)
 2348 
 2349 
 2350         ###
 2351         # Scrolling Keys
 2352         ###
 2353 
 2354         # Bind Handler for "Move Page Down
 2355         self.DirList.bind(self.KeyBindings["PGDN"], KeyPageDown)
 2356 
 2357         # Bind Handler for "Move Page Up"
 2358         self.DirList.bind(self.KeyBindings["PGUP"], KeyPageUp)
 2359 
 2360         # Bind Handler for "Move Page Right"
 2361         self.DirList.bind(self.KeyBindings["PGRT"], KeyPageRight)
 2362 
 2363         # Bind Handler for "Move Page Up"
 2364         self.DirList.bind(self.KeyBindings["PGLFT"], KeyPageLeft)
 2365 
 2366         ###
 2367         # Execute Commands
 2368         ###
 2369 
 2370         # Bind handler for "Run Command"
 2371         self.DirList.bind(self.KeyBindings["RUNCMD"], lambda event : KeyRunCommand(event, DoCmdShell=True))
 2372 
 2373         # Bind handler for "Item Select"
 2374         self.DirList.bind(self.KeyBindings["SELKEY"], DirListHandler)
 2375 
 2376         # Bind handler for "Mouse Select"
 2377         self.DirList.bind(self.KeyBindings["MOUSESEL"], MouseDblClick)
 2378 
 2379 
 2380         ###
 2381         # Directory Shortcut Keys - All Bound To A Common Handler
 2382         ###
 2383 
 2384         self.DirList.bind(self.KeyBindings["KDIRSC1"],  lambda event :DirSCKeyPress(event, 1))
 2385         self.DirList.bind(self.KeyBindings["KDIRSC2"],  lambda event :DirSCKeyPress(event, 2))
 2386         self.DirList.bind(self.KeyBindings["KDIRSC3"],  lambda event :DirSCKeyPress(event, 3))
 2387         self.DirList.bind(self.KeyBindings["KDIRSC4"],  lambda event :DirSCKeyPress(event, 4))
 2388         self.DirList.bind(self.KeyBindings["KDIRSC5"],  lambda event :DirSCKeyPress(event, 5))
 2389         self.DirList.bind(self.KeyBindings["KDIRSC6"],  lambda event :DirSCKeyPress(event, 6))
 2390         self.DirList.bind(self.KeyBindings["KDIRSC7"],  lambda event :DirSCKeyPress(event, 7))
 2391         self.DirList.bind(self.KeyBindings["KDIRSC8"],  lambda event :DirSCKeyPress(event, 8))
 2392         self.DirList.bind(self.KeyBindings["KDIRSC9"],  lambda event :DirSCKeyPress(event, 9))
 2393         self.DirList.bind(self.KeyBindings["KDIRSC10"], lambda event :DirSCKeyPress(event, 10))
 2394         self.DirList.bind(self.KeyBindings["KDIRSC11"], lambda event :DirSCKeyPress(event, 11))
 2395         self.DirList.bind(self.KeyBindings["KDIRSC12"], lambda event :DirSCKeyPress(event, 12))
 2396 
 2397         # Open Dialog To Load Current Directory Into User-Selected Shortcut
 2398 
 2399         self.DirList.bind(self.KeyBindings["KDIRSCSET"], lambda event :DirSCSet(event))
 2400 
 2401         
 2402         ###
 2403         # Memory Keys - All Features Bound To A Common Handler
 2404         ###
 2405 
 2406         self.DirList.bind(self.KeyBindings["MEMCLR1"],   lambda event : KeyMemHandler(mem=1,  clear=True))
 2407         self.DirList.bind(self.KeyBindings["MEMCLR2"],   lambda event : KeyMemHandler(mem=2,  clear=True))
 2408         self.DirList.bind(self.KeyBindings["MEMCLR3"],   lambda event : KeyMemHandler(mem=3,  clear=True))
 2409         self.DirList.bind(self.KeyBindings["MEMCLR4"],   lambda event : KeyMemHandler(mem=4,  clear=True))
 2410         self.DirList.bind(self.KeyBindings["MEMCLR5"],   lambda event : KeyMemHandler(mem=5,  clear=True))
 2411         self.DirList.bind(self.KeyBindings["MEMCLR6"],   lambda event : KeyMemHandler(mem=6,  clear=True))
 2412         self.DirList.bind(self.KeyBindings["MEMCLR7"],   lambda event : KeyMemHandler(mem=7,  clear=True))
 2413         self.DirList.bind(self.KeyBindings["MEMCLR8"],   lambda event : KeyMemHandler(mem=8,  clear=True))
 2414         self.DirList.bind(self.KeyBindings["MEMCLR9"],   lambda event : KeyMemHandler(mem=9,  clear=True))
 2415         self.DirList.bind(self.KeyBindings["MEMCLR10"],  lambda event : KeyMemHandler(mem=10,  clear=True))
 2416         self.DirList.bind(self.KeyBindings["MEMCLR11"],  lambda event : KeyMemHandler(mem=11,  clear=True))
 2417         self.DirList.bind(self.KeyBindings["MEMCLR12"],  lambda event : KeyMemHandler(mem=12,  clear=True))
 2418         self.DirList.bind(self.KeyBindings["MEMCLRALL"], lambda event : KeyMemHandler(mem=13, clear=True))
 2419         self.DirList.bind(self.KeyBindings["MEMSET1"],   lambda event : KeyMemHandler(mem=1))
 2420         self.DirList.bind(self.KeyBindings["MEMSET2"],   lambda event : KeyMemHandler(mem=2))
 2421         self.DirList.bind(self.KeyBindings["MEMSET3"],   lambda event : KeyMemHandler(mem=3))
 2422         self.DirList.bind(self.KeyBindings["MEMSET4"],   lambda event : KeyMemHandler(mem=4))
 2423         self.DirList.bind(self.KeyBindings["MEMSET5"],   lambda event : KeyMemHandler(mem=5))
 2424         self.DirList.bind(self.KeyBindings["MEMSET6"],   lambda event : KeyMemHandler(mem=6))
 2425         self.DirList.bind(self.KeyBindings["MEMSET7"],   lambda event : KeyMemHandler(mem=7))
 2426         self.DirList.bind(self.KeyBindings["MEMSET8"],   lambda event : KeyMemHandler(mem=8))
 2427         self.DirList.bind(self.KeyBindings["MEMSET9"],   lambda event : KeyMemHandler(mem=9))
 2428         self.DirList.bind(self.KeyBindings["MEMSET10"],  lambda event : KeyMemHandler(mem=10))
 2429         self.DirList.bind(self.KeyBindings["MEMSET11"],  lambda event : KeyMemHandler(mem=11))
 2430         self.DirList.bind(self.KeyBindings["MEMSET12"],  lambda event : KeyMemHandler(mem=12))
 2431 
 2432 
 2433         ###
 2434         # Sort Selection Keys - All Bound To A Common Handler
 2435         ###
 2436 
 2437         self.DirList.bind(self.KeyBindings["SORTBYNONE"], lambda event : KeySetSortParm(parm=fNONE))
 2438         self.DirList.bind(self.KeyBindings["SORTBYPERMS"], lambda event : KeySetSortParm(parm=fPERMISSIONS))
 2439         self.DirList.bind(self.KeyBindings["SORTBYLINKS"], lambda event : KeySetSortParm(parm=fLINKS))
 2440         self.DirList.bind(self.KeyBindings["SORTBYOWNER"], lambda event : KeySetSortParm(parm=fOWNER))
 2441         self.DirList.bind(self.KeyBindings["SORTBYGROUP"], lambda event : KeySetSortParm(parm=fGROUP))
 2442         self.DirList.bind(self.KeyBindings["SORTBYLENGTH"], lambda event : KeySetSortParm(parm=fLENGTH))
 2443         self.DirList.bind(self.KeyBindings["SORTBYTIME"], lambda event : KeySetSortParm(parm=fDATE))
 2444         self.DirList.bind(self.KeyBindings["SORTBYNAME"], lambda event : KeySetSortParm(parm=fNAME))
 2445         self.DirList.bind(self.KeyBindings["SORTREV"], lambda event : KeySetSortParm(parm=fREVERSE))
 2446         self.DirList.bind(self.KeyBindings["SORTSEP"], lambda event : KeySetSortParm(parm=fSEPARATE))
 2447         
 2448 
 2449         #####
 2450         # Wildcard Related Keys
 2451         #####
 2452 
 2453         # Bind handler to invoke Filter Wildcard Menu
 2454         self.DirList.bind(self.KeyBindings["MOUSEWILDFILTER"], MouseClick)
 2455 
 2456         # Bind handler to invoke Selection Wildcard Menu
 2457         self.DirList.bind(self.KeyBindings["MOUSEWILDSEL"], MouseClick)
 2458 
 2459         # Bind handler for "Filter With Wildcard"
 2460         self.DirList.bind(self.KeyBindings["FILTERWILD"], KeyFilterWild)
 2461 
 2462         # Bind handler for "Select With Wildcard"
 2463         self.DirList.bind(self.KeyBindings["SELWILD"], KeySelWild)
 2464 
 2465         # Bind handler for "Toggle Filter By Wildcard"
 2466         self.DirList.bind(self.KeyBindings["TOGFILT"], KeyToggleFilter)
 2467 
 2468         # Bind handler for "Toggle Dotfile Hiding"
 2469         self.DirList.bind(self.KeyBindings["TOGHIDEDOT"],lambda event :  KeyToggle(event, "HIDEDOTFILES"))
 2470 
 2471 
 2472         # Give the listbox focus so it gets keystrokes
 2473         self.DirList.focus()
 2474 
 2475     # End Of method 'twanderUI.BindAllHandlers()
 2476 
 2477 
 2478     #####
 2479     # Return tuple of all selected items
 2480     #####
 2481 
 2482     def AllSelection(self):
 2483 
 2484         sellist = []
 2485         nameindex = self.NameFirst
 2486 
 2487         for entry in self.DirList.curselection():
 2488             sellist.append(self.DirList.get(entry)[nameindex:].split(SYMPTR)[0])
 2489 
 2490         return sellist
 2491 
 2492     # End of method 'twanderUI.AllSelection()'
 2493         
 2494 
 2495     #####
 2496     # Return name of currently selected item
 2497     #####
 2498 
 2499     def LastInSelection(self):
 2500 
 2501         nameindex = self.NameFirst
 2502         index = self.DirList.curselection()
 2503 
 2504         if index:
 2505             return self.DirList.get(index[-1])[nameindex:].split(SYMPTR)[0]
 2506         else:
 2507             return ""
 2508 
 2509     # End of method 'twanderUI.LastInSelection()'
 2510 
 2511 
 2512     #####
 2513     # Support periodic polling to make sure widget stays
 2514     # in sync with reality of current directory.
 2515     #####
 2516 
 2517     def poll(self):
 2518 
 2519         # If new dir entered via mouse, force correct activation
 2520         if self.MouseNewDir:
 2521             self.DirList.activate(0)
 2522             self.MouseNewDir = False
 2523 
 2524         # Do autorefreshing as required
 2525 
 2526         if AUTOREFRESH:
 2527 
 2528             # Is it time for a refresh?
 2529 
 2530             elapsed = int((time.time() - self.LastRefresh) * 1000)
 2531             if elapsed >= REFRESHINT:
 2532 
 2533                 # Don't autorefresh on drive list views
 2534                 if UI.CurrentDir != SHOWDRIVES:
 2535 
 2536                     # Don't autorefresh if there is a lock outstanding
 2537 
 2538                     if not UI.DirListMutex.test():
 2539                         RefreshDirList(None)
 2540 
 2541         # Setup next polling event
 2542         self.DirList.after(POLLINT, self.poll)
 2543 
 2544     # End of method 'twanderUI.poll()'
 2545 
 2546 
 2547     #####
 2548     # Set Detailed View -> False == No Details, True == Details
 2549     #####
 2550 
 2551     def SetDetailedView(self, details):
 2552 
 2553         # See if we're forcing details to always be off
 2554         if NODETAILS:
 2555             self.DetailsOn = False
 2556         else:
 2557             self.DetailsOn = details
 2558         
 2559     # End of method 'twanderUI.SetDetailedView()'
 2560 
 2561 
 2562     #####
 2563     # Set a particular selection, w/bounds checking
 2564     # Note that 'selection' is passed as a string
 2565     # but 'active' is passed as a number.
 2566     #####
 2567 
 2568     def SetSelection(self, selection, active):
 2569 
 2570         # Clear all current selection(s)
 2571         self.DirList.selection_clear(0, END)
 2572 
 2573         # Get current maximum index
 2574         maxindex =  self.DirList.size() - 1
 2575 
 2576         # And bounds check/adjust
 2577 
 2578         if active > maxindex:
 2579             active = maxindex
 2580 
 2581         # Set desired selected items, if any
 2582 
 2583         if selection:
 2584             for entry in selection:
 2585                 self.DirList.select_set(entry)
 2586             self.DirList.see(selection[-1])
 2587 
 2588         # Now set the active entry
 2589         self.DirList.activate(active)
 2590 
 2591     # End of method 'twanderUI.SetSelection()'
 2592 
 2593 
 2594     #####
 2595     # Update titlebar with most current information
 2596     #####
 2597 
 2598     def UpdateTitle(self, mainwin, refreshing=""):
 2599 
 2600         if UI.CurrentDir == SHOWDRIVES:
 2601 
 2602             # SORTBYFIELD can have values not meaningful
 2603             # in Drive List View - these are always mapped to fNAME
 2604             
 2605             if Name2Key[SORTBYFIELD.lower()][0] > MAXDLVKEY:
 2606                 srtfld = dlvLETTER.upper()
 2607             else:
 2608                 srtfld = Name2Key[SORTBYFIELD.lower()][2].upper()
 2609 
 2610             sepsort = ""
 2611             ttlfiles = str(self.DirList.size())
 2612             
 2613         else:
 2614             srtfld = SORTBYFIELD.upper()
 2615             srtsep = YesOrNo[SORTSEPARATE]
 2616             sepsort = "%s %s" % (TTLSORTSEP, srtsep)
 2617             ttlfiles = str(self.DirList.size()-1)     # We never count the ".." entry
 2618 
 2619 
 2620         # Limit width of current directory name display in case
 2621         # user get's *really* deeply nested in a file system.
 2622         # This keeps the other stuff of interest visible on the titlebar
 2623 
 2624         # First, find out if current directory is descendant of this
 2625         # user's home directory.  If so, substitute a shortcut to
 2626         # save titlebar space.
 2627         
 2628 
 2629         CurrentDir = UI.CurrentDir
 2630         if ENVHOME:
 2631             CurrentDir = os.path.realpath(UI.CurrentDir)
 2632             envhome = os.path.realpath(ENVHOME)
 2633             if CurrentDir.startswith(envhome):
 2634                 CurrentDir = CurrentDir.replace(envhome, HOMEDIRMARKER)
 2635         
 2636         # And make sure whatever we ended up with has an ending
 2637         # separator character.
 2638 
 2639         if CurrentDir[-1] != PSEP:
 2640             CurrentDir += PSEP
 2641         
 2642         pathlen = len(CurrentDir)
 2643         if pathlen > TTLMAXDIR:
 2644             CurrentDir = TTLDIR2LONG + CurrentDir[pathlen-TTLMAXDIR:]
 2645 
 2646         # Indicate Reverse sort by appending a '-' to the sort field name
 2647         
 2648         if SORTREVERSE:
 2649             srtfld += "-"
 2650             
 2651         sortedby  = "%s %s    " % (TTLSORTFLD, srtfld)
 2652         autostate = YesOrNo[AUTOREFRESH] + refreshing
 2653 
 2654         filterwc = UI.FilterWildcard[0]
 2655         if WILDNOCASE and not UI.FilterWildcard[3]:
 2656             filterwc = filterwc.lower()
 2657         if INVERTFILTER:
 2658             filterwc = "NOT " + filterwc
 2659         filter    = "%s %s" % (TTLFILTER, filterwc)
 2660 
 2661         hidedotfile = "%s %s" % (TTLHIDEDOT, YesOrNo[HIDEDOTFILES])
 2662 
 2663         # Show state of symlink handling
 2664 
 2665         symlinks = "F"
 2666         if SYMDIR:
 2667             symlinks = "D"
 2668         if SYMEXPAND:
 2669             symlinks += "E"
 2670             if SYMRESOLV:
 2671                 symlinks += "R"
 2672 
 2673         symlinks = "%s %s" % (TTLSYMLINKS, symlinks)
 2674             
 2675         mainwin.title("%s %s        %s:        %s        %s   %s     %s     %s  %s      %s  %s      %s%s  %s %s" % 
 2676                       (PROGNAME, VERSION, FULLNAME, CurrentDir, symlinks, filter, hidedotfile,  TTLFILES, 
 2677                        ttlfiles, TTLSIZE, FileLength(self.TotalSize), sortedby, sepsort, TTLAUTO, autostate))
 2678 
 2679         # Make sure the titlebar gets updated
 2680         mainwin.update_idletasks()
 2681 
 2682     # End of method 'twanderUI.UpdateTitle()'
 2683 
 2684 # End of class definition, 'twanderUI'
 2685 
 2686 
 2687 #----------------------------------------------------------#
 2688 #                   Handler Functions                      #
 2689 #----------------------------------------------------------#
 2690 
 2691 #---------------- Mouse Click Dispatchers -----------------#
 2692 
 2693 # We intercept all mouse clicks (of interest) here so it
 2694 # is easy to uniquely handle the Control, Shift, Alt,
 2695 # variations of button presses.  We use Tkinter itself
 2696 # keep track of single- vs. double-clicks and hand-off
 2697 # the event to the corresponding Mouse Click Dispatcher.
 2698 
 2699 #####
 2700 # Event Handler: Single Mouse Clicks
 2701 #####
 2702 
 2703 def MouseClick(event):
 2704 
 2705     event.state &= ~DontCareMask                     # Kill the bits we don't care about
 2706     
 2707     if event.state == Button3Mask:                   # Button-3 / No Modifier
 2708         x, y = UI.DirList.winfo_pointerxy()          # Position near mouse
 2709         PopupMenu(UI.CmdBtn.menu, x, y)              # Display Command Menu
 2710 
 2711     elif event.state == (Button3Mask | ShiftMask | ControlMask):     # Shift-Control-Button-3
 2712         x, y = UI.DirList.winfo_pointerxy()          # Position near mouse
 2713         PopupMenu(UI.HistBtn.menu, x, y)             # Display History Menu
 2714 
 2715     elif event.state == (Button3Mask | ShiftMask):   # Shift-Button-3
 2716         x, y = UI.DirList.winfo_pointerxy()          # Position near mouse
 2717         PopupMenu(UI.DirBtn.menu, x, y)              # Display Directory Menu
 2718 
 2719     elif event.state == (Button1Mask | AltMask | ControlMask):     # Control-Button-1
 2720         x, y = UI.DirList.winfo_pointerxy()          # Position near mouse
 2721         PopupMenu(UI.ShortBtn.menu, x, y)            # Display Shortcut Menu
 2722 
 2723     elif event.state == (Button3Mask | AltMask | ShiftMask):     # Alt-Shift-Button-3
 2724         x, y = UI.DirList.winfo_pointerxy()          # Position near mouse
 2725         PopupMenu(UI.SortBtn.menu, x, y)             # Display Sort Menu
 2726 
 2727     elif event.state == (Button2Mask | AltMask | ControlMask):     # Alt-Control-Button-2
 2728         x, y = UI.DirList.winfo_pointerxy()          # Position near mouse
 2729         PopupMenu(UI.FilterBtn.menu, x, y)           # Display Filter Wildcard Menu
 2730 
 2731     elif event.state == (Button3Mask | AltMask | ControlMask):     # Alt-Control-Button-3
 2732         x, y = UI.DirList.winfo_pointerxy()          # Position near mouse
 2733         PopupMenu(UI.SelectBtn.menu, x, y)           # Display Selection Wildcard Menu
 2734 
 2735 # End Of 'MouseClick()'
 2736 
 2737 
 2738 #####
 2739 # Event Handler: Mouse Double-Clicks
 2740 #####
 2741 
 2742 def MouseDblClick(event):
 2743 
 2744     event.state &= ~DontCareMask                      # Kill the bits we don't care about
 2745     
 2746     if event.state == Button1Mask:                    # Double-Button-1 / No Modifier
 2747         DirListHandler(event)                         # Execute selected item
 2748 
 2749     elif event.state == (Button1Mask | ControlMask):  # Control-DblButton-1
 2750         KeyBackDir(event)                             # Move back one directory
 2751 
 2752     elif event.state == (Button3Mask | ControlMask):  # Control-DblButton-3
 2753         KeyUpDir(event)                               # Move up one directory
 2754 
 2755     return "break"
 2756 
 2757 # End Of 'MouseDblClick()
 2758 
 2759 
 2760 #--------------- General Program Commands -----------------#
 2761 
 2762 #####
 2763 # Event Handler: Clear Various Program Histories
 2764 #####
 2765 
 2766 def ClearHistory(event):
 2767     global UI
 2768     
 2769     UI.AllDirs          = []
 2770     UI.CmdHist          = []
 2771     UI.FilterHist       = []
 2772     UI.SelectHist       = []
 2773     UI.LastCmd          = ""
 2774     UI.LastDir          = []
 2775     UI.LastPathEntered  = ""
 2776     UI.LastFiltWildcard = ""
 2777     UI.LastSelWildcard  = ""
 2778 
 2779     for x in [UI.DirBtn, UI.HistBtn, UI.FilterBtn, UI.SelectBtn]:
 2780         x.menu.delete(0,END)
 2781         x['menu'] = x.menu
 2782         x.config(state=DISABLED)
 2783 
 2784     return 'break'
 2785     
 2786 # End of 'ClearHistory()'
 2787 
 2788 
 2789 #####
 2790 # Decrement Font Size
 2791 #####
 2792 
 2793 def FontDecr(event):
 2794     global FSZ, MFSZ, HFSZ
 2795     
 2796     if FSZ > 1:
 2797         FSZ  -= 1
 2798     if MFSZ > 1:
 2799         MFSZ -= 1
 2800     if HFSZ > 1:
 2801         HFSZ -= 1
 2802 
 2803     SetupGUI()
 2804     return 'break'
 2805 
 2806 # End of 'FontDecr()'
 2807 
 2808 
 2809 #####
 2810 # Increment Font Size
 2811 #####
 2812 
 2813 def FontIncr(event):
 2814     global FSZ, MFSZ, HFSZ
 2815 
 2816     FSZ  += 1
 2817     MFSZ += 1
 2818     HFSZ += 1
 2819 
 2820     SetupGUI()
 2821     return 'break'
 2822 
 2823 # End of 'FontIncr()'
 2824 
 2825 
 2826 #####
 2827 # Event Handler: Individual Keystrokes
 2828 #####
 2829 
 2830 def KeystrokeHandler(event):
 2831 
 2832     event.state &= ~DontCareMask                      # Kill the bits we don't care about
 2833 
 2834     # Check for, and handle accelerator keys
 2835 
 2836     if event.state == AltMask:
 2837         
 2838         # Set menu button associated with accelerator
 2839 
 2840         # Command Menu
 2841         if event.char == 'c':
 2842             button = UI.CmdBtn
 2843 
 2844         # Directory Menu
 2845         elif event.char == 'd':
 2846             button = UI.DirBtn
 2847 
 2848         # History Menu
 2849         elif event.char == 'h':
 2850             button = UI.HistBtn
 2851 
 2852         # Shortcut Menu
 2853         elif event.char == 'u':
 2854             button = UI.ShortBtn
 2855 
 2856         # Sort Menu
 2857         elif event.char == 's':
 2858             button = UI.SortBtn
 2859 
 2860         # Filter Menu
 2861         elif event.char == 'f':
 2862             button = UI.FilterBtn
 2863 
 2864         # Select Menu
 2865         elif event.char == 't':
 2866             button = UI.SelectBtn
 2867 
 2868         # Help Menu
 2869         elif event.char == 'l':
 2870             button = UI.HelpBtn
 2871 
 2872         # Unrecognized - Ignore
 2873         else:
 2874             return
 2875 
 2876         parts = button.winfo_geometry().split('+')     # Geometry returned as "WidthxHeight+X+Y"
 2877         dims  = parts[0].split('x')
 2878 
 2879         x, y = int(parts[1]), int(parts[2])
 2880         w, h = int(dims[0]), int(dims[1])
 2881 
 2882         x += UIroot.winfo_rootx()                      # This is relative to root window position
 2883         y += UIroot.winfo_rooty()                      # So adjust accordingly
 2884 
 2885         # Display the requested menu
 2886         PopupMenu(button.menu, x+MENUOFFSET, y+h)
 2887 
 2888         # Inhibit event from getting picked up by local accelerator key handlers
 2889         return "break"
 2890 
 2891     #####
 2892     # Otherwise, process single character command invocations.
 2893     #####
 2894 
 2895     # We *only* want to handle simple single-character
 2896     # keystrokes.  This means that there must be a character
 2897     # present and that the only state modifier permitted
 2898     # is the Shift key
 2899     
 2900     if not event.char or (event.state and event.state != 1):
 2901         return 
 2902 
 2903     # If the key pressed is a command key (i.e., it is in the table of
 2904     # defined commands), get its associated string and execute the command.
 2905 
 2906     cmd  = UI.CmdTable.get(event.char, ["", "", ""])[1]
 2907     name = UI.CmdTable.get(event.char, ["", "", ""])[0]
 2908 
 2909 
 2910     # cmd == null means no matching command key -  do nothing
 2911     # Otherwise, replace config tokens with actual file/dir names
 2912 
 2913     if cmd:
 2914         ExecuteCommand(cmd, name, ResolveVars=True)
 2915 
 2916    
 2917 # end of 'KeystrokeHandler()'
 2918     
 2919 
 2920 #####
 2921 # Event Handler: Program Quit
 2922 #####
 2923 
 2924 def KeyQuitProg(event):
 2925     sys.exit()
 2926 
 2927 # End of 'KeyQuitProg()'
 2928 
 2929 
 2930 #####
 2931 # Event Handler: Generic Option Toggle
 2932 #####
 2933 
 2934 def KeyToggle(event, option):
 2935     global SYMEXPAND, SYMRESOLV
 2936     
 2937     exec("global %s; %s = not %s" % (option, option, option))
 2938 
 2939     # We may have just updated SYMRESOLV.  Changing its state implies
 2940     # we want to see the link target (either expanded or resolved) so
 2941     # force symlink targets to be displayed one way or the other.
 2942 
 2943     if option=="SYMRESOLV":
 2944         SYMEXPAND = True
 2945         
 2946     RefreshDirList(event)
 2947 
 2948     # Update the help menu to reflect change
 2949     LoadHelpMenu()
 2950     
 2951     return 'break'
 2952 
 2953 # End of 'KeyToggle()'
 2954 
 2955 
 2956 #####
 2957 # Event Handler: Toggle Autorefresh
 2958 #####
 2959 
 2960 def KeyToggleAuto(event):
 2961 
 2962     global AUTOREFRESH
 2963     
 2964     # Toggle the state
 2965     AUTOREFRESH = not AUTOREFRESH
 2966 
 2967     # Update the titlebar to reflect this
 2968     UI.UpdateTitle(UIroot)
 2969 
 2970     # Update the help menu to reflect change
 2971     LoadHelpMenu()
 2972     
 2973     return 'break'
 2974 
 2975 # End of 'KeyToggleAuto()'
 2976 
 2977 
 2978 #####
 2979 # Event Handler: Toggle Detail View
 2980 #####
 2981 
 2982 def KeyToggleDetail(event):
 2983 
 2984     UI.SetDetailedView(not UI.DetailsOn)
 2985     RefreshDirList(event)
 2986 
 2987     return 'break'
 2988 
 2989 # End of 'KeyToggleDetail()'
 2990 
 2991 
 2992 #####
 2993 # Event Handler: Toggle Wildcard Filter Logic
 2994 #####
 2995 
 2996 def KeyToggleFilter(event):
 2997     global INVERTFILTER
 2998     
 2999     # Only toggle state if there is an active filtering wildcard
 3000     # If we do, reset the cursor to the top, but select nothing
 3001     
 3002     if UI.FilterWildcard[1]:
 3003         INVERTFILTER = not INVERTFILTER
 3004         RefreshDirList(event)
 3005         KeySelTop(event, clearsel=True)
 3006 
 3007     return 'break'
 3008 
 3009 # End of 'KeyToggleFilter()'
 3010 
 3011 
 3012 #####
 3013 # Event Handler: Toggle win32all Features, If Available
 3014 #####
 3015 
 3016 def KeyToggleWin32All(event):
 3017     global WIN32ALLON
 3018     
 3019     if USEWIN32ALL and (UI.CurrentDir != SHOWDRIVES):
 3020         WIN32ALLON = not WIN32ALLON
 3021         RefreshDirList(event)
 3022         LoadShortcutMenu()    # Decides whether or not to show drive list
 3023 
 3024     return 'break'
 3025     
 3026 # End of 'KeyToggleWin32All()'
 3027 
 3028 
 3029 #------------------- Directory Navigation -----------------#
 3030 
 3031 
 3032 #####
 3033 # Event Handler: Change Directory/Path
 3034 ####
 3035 
 3036 def KeyChangeDir(event):
 3037 
 3038     newpath = askstring(pCHPATH, pENPATH, initialvalue=UI.LastPathEntered)
 3039 
 3040     if newpath:
 3041         if MAXMENU > 0:
 3042             UI.LastPathEntered = newpath
 3043         LoadDirList(newpath)
 3044         KeySelTop(event)
 3045     UI.DirList.focus()
 3046 
 3047     return 'break'
 3048 
 3049 # End of 'KeyChangeDir()'
 3050 
 3051 
 3052 #####
 3053 # Event Handler: Goto $HOME
 3054 #####
 3055 
 3056 def KeyHomeDir(event):
 3057 
 3058     if HOME:
 3059         LoadDirList(HOME)
 3060 
 3061     return 'break'
 3062 
 3063 # End of 'KeyHomeDir()'
 3064 
 3065 
 3066 #####
 3067 # Event Handler: Move To Previous Directory
 3068 #####
 3069 
 3070 def KeyBackDir(event):
 3071 
 3072     # Move to last directory visited, if any - inhibit this from
 3073     # being placed on the directory traversal stack
 3074     if UI.LastDir:
 3075         LoadDirList(UI.LastDir.pop(), save=False)
 3076 
 3077     # No previous directory
 3078     else:
 3079         pass
 3080 
 3081     return 'break'
 3082 
 3083 # End of 'KeyBackDir()'
 3084 
 3085 
 3086 #####
 3087 # Event Handler: Go To Root Directory
 3088 #####
 3089 
 3090 def KeyRootDir(event):
 3091     LoadDirList(PSEP)
 3092 
 3093     return 'break'
 3094 
 3095 # End of 'KeyRootDir()'
 3096 
 3097 
 3098 #####
 3099 # Event Handler: Go Back To Initial Directory
 3100 #####
 3101 
 3102 def KeyStartDir(event):
 3103     LoadDirList(STARTDIR)
 3104 
 3105     return 'break'
 3106 
 3107 # End of 'KeyStartDir()'
 3108 
 3109 
 3110 #####
 3111 # Event Handler: Move Up One Directory
 3112 #####
 3113 
 3114 def KeyUpDir(event):
 3115 
 3116     # Move up one directory level unless we're already at the root
 3117     if UI.CurrentDir != os.path.abspath(PSEP):
 3118         LoadDirList(UI.CurrentDir + "..")
 3119 
 3120     # Unless we're running on Win32 and we are able to do
 3121     # a Drive List View
 3122 
 3123     elif OSPLATFORM == 'win32' and GetWin32Drives():
 3124         LoadDirList(SHOWDRIVES)
 3125 
 3126     return 'break'
 3127     
 3128 # End of 'KeyUpDir()'
 3129 
 3130 #####
 3131 # Event Handler: Display Drive List View On Win32, If Possible
 3132 #####
 3133 
 3134 def KeyDriveList(event):
 3135 
 3136     # This is only possible on Win32 and if there is a list of
 3137     # drives available - i.e, If Win32All is installed
 3138 
 3139     if OSPLATFORM == 'win32' and GetWin32Drives():
 3140         LoadDirList(SHOWDRIVES)
 3141 
 3142     return 'break'
 3143 
 3144 # End of 'KeyDriveList()
 3145 
 3146 
 3147 #---------------------- Selection Keys ---------------------#
 3148 
 3149 
 3150 #####
 3151 # Event Handler: Select All Items
 3152 #####
 3153 
 3154 def KeySelAll(event):
 3155 
 3156     # In the case of a Drive List View, we want to literally
 3157     # select everything.  In all other cases, we do not
 3158     # want this feature to select the first item which is ".."
 3159 
 3160     if UI.CurrentDir == SHOWDRIVES:
 3161         UI.DirList.selection_set(0, END)
 3162 
 3163     else:
 3164         # Unselect first item in case it was
 3165         UI.DirList.selection_clear(0)
 3166     
 3167         # We never want to select the first item which is ".."
 3168         UI.DirList.selection_set(1, END)
 3169 
 3170     return 'break'
 3171 
 3172 # End of 'KeySelAll()'
 3173 
 3174 
 3175 #####
 3176 # Event Handler: Invert Current Selection
 3177 #####
 3178 
 3179 def KeySelInv(event):
 3180 
 3181     # List of current selections
 3182     cs= UI.DirList.curselection()
 3183 
 3184     # Select everything
 3185     UI.DirList.selection_set(0, END)
 3186 
 3187     # And unselect what was selected
 3188     for v in cs:
 3189         UI.DirList.selection_clear(v)
 3190 
 3191     # If we're not in a Drive List View, we never
 3192     # select the first entry (".." this way)
 3193 
 3194     if UI.CurrentDir != SHOWDRIVES:
 3195         UI.DirList.selection_clear(0)
 3196 
 3197     return 'break'
 3198 
 3199 # End of 'KeySelInv()'
 3200 
 3201 
 3202 #####
 3203 # Event Handler: Select Next Item
 3204 #####
 3205 
 3206 def KeySelNext(event):
 3207 
 3208     next = UI.DirList.index(ACTIVE)
 3209 
 3210     # Don't increment if at end of list
 3211     if (next < UI.DirList.size() - 1):
 3212         next += 1
 3213 
 3214     UI.SetSelection((str(next),), next)
 3215 
 3216     return 'break'
 3217 
 3218 # End of 'KeySelNext()'
 3219 
 3220 
 3221 #####
 3222 # Event Handler: Select No Items
 3223 #####
 3224 
 3225 def KeySelNone(event):
 3226     UI.DirList.selection_clear(0, END)
 3227 
 3228     return 'break'
 3229 
 3230 # End of 'KeySelNone()'
 3231 
 3232 
 3233 #####
 3234 # Event Handler: Select Previous Item
 3235 #####
 3236 
 3237 def KeySelPrev(event):
 3238     prev = UI.DirList.index(ACTIVE)
 3239 
 3240     # Only decrement if > 0
 3241     if prev:
 3242         prev -= 1
 3243 
 3244     UI.SetSelection((str(prev),), prev)
 3245 
 3246     return 'break'
 3247 
 3248 # End of 'KeySelPrev()'
 3249 
 3250 
 3251 #####
 3252 # Event Handler: Select Last Item
 3253 #####
 3254 
 3255 def KeySelEnd(event):
 3256 
 3257     # Get index of last item in listbox
 3258     sz = UI.DirList.size() - 1
 3259 
 3260     # And setup to last item accordingly
 3261     UI.SetSelection((str(sz),), sz)
 3262 
 3263     return 'break'
 3264 
 3265 # End of 'KeySelEnd()'
 3266 
 3267 
 3268 #####
 3269 # Event Handler: Select First Item
 3270 #####
 3271 
 3272 def KeySelTop(event, clearsel=False):
 3273 
 3274     UI.SetSelection(('0',),0)
 3275 
 3276     # In some cases, we call this routine just to position the cursor
 3277     # at the top of the file list but we don't actually want anything
 3278     # selected
 3279 
 3280     if clearsel:
 3281         KeySelNone(event)
 3282 
 3283     return 'break'
 3284 
 3285 # End of 'KeySelTop()'
 3286 
 3287 
 3288 #####
 3289 # Event Handler: Filter Using Wildcard
 3290 #####
 3291 
 3292 def KeyFilterWild(event, initial=""):
 3293     global UI
 3294 
 3295     prompt = pWILDFILT
 3296 
 3297     # Ask the user for the wildcard pattern, using initial string, if any
 3298     if initial:
 3299         uwc = askstring(prompt, pENWILD, initialvalue=initial)
 3300     else:
 3301         uwc = askstring(prompt, pENWILD, initialvalue=UI.LastFiltWildcard)        
 3302     
 3303     # Return focus to the main interface
 3304     UI.DirList.focus()
 3305     
 3306     # Blank value means to abort
 3307     if not uwc:
 3308         return 'break'
 3309 
 3310     # Reposition cursor to top of display and deselect everything
 3311     KeySelTop(event, clearsel=True)
 3312     
 3313     # Unless the user indicates otherwise, cook the regex so
 3314     # a match can occur anywhere on the line.  If the user wants
 3315     # strict matching, save this fact so it can escape WILDNOCASE.
 3316 
 3317     strict = False
 3318     if uwc[0] == STRICTMATCH:
 3319         wc = uwc[1:]
 3320         strict = True
 3321     else:
 3322         wc = r".*" + uwc
 3323 
 3324     # Compile it - abort if compilation fails
 3325     # Build both a normal and lower-case version
 3326     # of the search engine so we can support case-insensitive
 3327     # matching elswehere.
 3328     
 3329     try:
 3330         wild = re.compile(wc)
 3331         wildl = re.compile(wc.lower())
 3332         
 3333     except:
 3334         WrnMsg(wWILDCOMP % wc)
 3335         return 'break'
 3336 
 3337     # Refresh the display only showing entries that match
 3338     
 3339     UI.FilterWildcard = (wc, wild, wildl, strict)
 3340     RefreshDirList(None)
 3341 
 3342     # Save the wildcard only if dynamic menus are enabled (MAXMENU > 0)
 3343     # AND one of two conditions exist:
 3344     #
 3345     #    1) No initial string was provided (The user entered a command manually).
 3346     #    2) An initial string was provided, but the user edited it.
 3347 
 3348     if (MAXMENU > 0) and ((not initial) or (uwc != initial)):
 3349             UI.LastFiltWildcard = uwc
 3350 
 3351     # Add this wildcard to the menu if its not there already
 3352     if uwc not in UI.FilterHist:
 3353         UpdateMenu(UI.FilterBtn, UI.FilterHist, MAXMENU, MAXMENUBUF, KeyFilterWild, newentry=uwc, fakeevent=True)
 3354 
 3355     # Dump wildcard stack if debug requested it
 3356     if DEBUGLEVEL & DEBUGWILD:
 3357         PrintDebug(dWILDLST % dFILTER, UI.FilterHist)
 3358 
 3359     return 'break'
 3360 
 3361 # End of 'KeyFilterWild()'
 3362 
 3363 
 3364 #####
 3365 # Event Handler: Filter Or Select Using Wildcard
 3366 #####
 3367 
 3368 def KeySelWild(event, initial=""):
 3369     global UI
 3370     
 3371     prompt = pWILDSEL
 3372 
 3373     # Ask the user for the wildcard pattern, using initial string, if any
 3374     if initial:
 3375         uwc = askstring(prompt, pENWILD, initialvalue=initial)
 3376     else:
 3377         uwc = askstring(prompt, pENWILD, initialvalue=UI.LastSelWildcard)        
 3378     
 3379     # Return focus to the main interface
 3380     UI.DirList.focus()
 3381     
 3382     # Blank value means to abort
 3383     if not uwc:
 3384         return 'break'
 3385 
 3386     # Clear current selections
 3387     KeySelNone(event)
 3388 
 3389     # Unless the user indicates otherwise, cook the regex so
 3390     # a match can occur anywhere on the line.  If the user wants
 3391     # strict matching, save this fact so it can escape WILDNOCASE.
 3392 
 3393     strict = False
 3394     if uwc[0] == STRICTMATCH:
 3395         wc = uwc[1:]
 3396         strict = True
 3397     else:
 3398         wc = r".*" + uwc
 3399 
 3400     # Compile it - abort if compilation fails
 3401     # Build both a normal and lower-case version
 3402     # of the search engine so we can support case-insensitive
 3403     # matching elswehere.
 3404     
 3405     try:
 3406         wild = re.compile(wc)
 3407         wildl = re.compile(wc.lower())
 3408         
 3409     except:
 3410         WrnMsg(wWILDCOMP % wc)
 3411         return 'break'
 3412 
 3413     # Iterate over the current directory listing saving the
 3414     # indexes of items which match.  We start at 1 not 0 because
 3415     # the first entry ("..") is never considered when doing
 3416     # wildcard matching.  This routine also observes the
 3417     # WILDNOCASE option - if set to True, case is collpased for
 3418     # matching purposes.
 3419 
 3420     matches = []
 3421     wc = wild
 3422     if WILDNOCASE and not strict:
 3423         wc = wildl
 3424 
 3425     for x in range(1,UI.DirList.size()):
 3426 
 3427         matchthis = UI.DirList.get(x)
 3428         if WILDNOCASE and not strict:
 3429             matchthis = matchthis.lower()
 3430 
 3431         if wc.match(matchthis):
 3432             matches.append(x)
 3433 
 3434     # If anything matched, select it
 3435     if matches:
 3436         UI.SetSelection(matches, matches[0])
 3437 
 3438     # Save the wildcard only if dynamic menus are enabled (MAXMENU > 0)
 3439     # AND one of two conditions exist:
 3440     #
 3441     #    1) No initial string was provided (The user entered a command manually).
 3442     #    2) An initial string was provided, but the user edited it.
 3443 
 3444     if (MAXMENU > 0) and ((not initial) or (uwc != initial)):
 3445             UI.LastSelWildcard = uwc
 3446 
 3447     # Add this wildcard to the menu if its not there already
 3448     if uwc not in UI.SelectHist:
 3449         UpdateMenu(UI.SelectBtn, UI.SelectHist, MAXMENU, MAXMENUBUF, KeySelWild, newentry=uwc, fakeevent=True)
 3450 
 3451     # Dump wildcard stack if debug requested it
 3452     if DEBUGLEVEL & DEBUGWILD:
 3453         PrintDebug(dWILDLST %dSELECT, UI.SelectHist)
 3454 
 3455     return 'break'
 3456 
 3457 # End of 'KeySelWild()'
 3458 
 3459 
 3460 #---------------------- Scrolling Keys ---------------------#
 3461 
 3462 
 3463 #####
 3464 # Event Handler: Move Down A Page
 3465 #####
 3466 
 3467 def KeyPageDown(event):
 3468     UI.DirList.yview_scroll(1, "pages")
 3469     UI.DirList.activate("@0,0")
 3470 
 3471     return 'break'
 3472 
 3473 # End of 'KeyPageDown()'
 3474 
 3475 
 3476 #####
 3477 # Event Handler: Move Up A Page
 3478 #####
 3479 
 3480 def KeyPageUp(event):
 3481     UI.DirList.yview_scroll(-1, "pages")
 3482     UI.DirList.activate("@0,0")
 3483 
 3484     return 'break'
 3485 
 3486 # End of 'KeyPageUp()'
 3487 
 3488 
 3489 #####
 3490 # Event Handler: Move Page Right
 3491 #####
 3492 
 3493 def KeyPageRight(event):
 3494     UI.DirList.xview_scroll(1, "pages")
 3495 
 3496     return 'break'
 3497 
 3498 # End of 'KeyPageRight()'
 3499 
 3500 
 3501 #####
 3502 # Event Handler: Move Page Left
 3503 #####
 3504 
 3505 def KeyPageLeft(event):
 3506     UI.DirList.xview_scroll(-1, "pages")
 3507 
 3508     return 'break'
 3509 
 3510 # End of 'KeyPageLeft()'
 3511 
 3512 
 3513 #---------------------- Execute Commands -------------------#
 3514 
 3515 
 3516 #####
 3517 # Event Handler: Run Manually Entered Command
 3518 ####
 3519 
 3520 def KeyRunCommand(event, initial="", DoCmdShell=False):
 3521     global UI
 3522 
 3523     # NOTE: DoCmdShell determines whether or not we do CMDSHELL
 3524     # processing (if enabled).  It is *off* by default because
 3525     # we do not want commands invoked via the command history
 3526     # mechanism to use this feature - doing so would cause
 3527     # cascading of the CMDSHELL string on each subsequent
 3528     # invocation.  So, the only time we want to do CMDSHELL
 3529     # processing is when the user presses the RUNCMD key.
 3530     # The binding for this key thus sets DoCmdShell to True.
 3531 
 3532     # Prompt with passed initial edit string
 3533     if initial:
 3534         cmd = askstring(pRUNCMD, pENCMD, initialvalue=initial)
 3535 
 3536     # Prompt with last manually entered command - doesn't matter if it's null
 3537     else:
 3538         cmd = askstring(pRUNCMD, pENCMD, initialvalue=UI.LastCmd )
 3539 
 3540     # Execute command (if any) - Blank entry means do nothing/return
 3541     if cmd:
 3542 
 3543         mycmd = cmd
 3544 
 3545         # Process any built-in shortcuts they may have used
 3546 
 3547         for sc in RUNCMD_SC.keys():
 3548             mycmd = mycmd.replace(sc, RUNCMD_SC[sc])
 3549 
 3550         # Keep track of whether or not the user asked for a refresh after command completion
 3551         # We have to strip it here of CMDSHELL processing will work.
 3552 
 3553         do_refresh_after = False
 3554         if mycmd[0] == REFRESHAFTER:
 3555             do_refresh_after = True
 3556             mycmd = mycmd[1:]
 3557 
 3558         # Do CMDSHELL Processing if enabled and requested
 3559 
 3560         if CMDSHELL and DoCmdShell:                      # See if feature is enabled and requested
 3561             if not mycmd.startswith(CMDSHELLESC):        # See if user is trying to escape the feature
 3562                 mycmd = "%s '%s'" % (CMDSHELL, mycmd)
 3563             else:                                        # User is escaping the feature
 3564                 mycmd = mycmd[1:]
 3565 
 3566         # Request refreshing after command completion if it was desired
 3567 
 3568         if do_refresh_after:
 3569             mycmd = REFRESHAFTER + mycmd
 3570             
 3571         ExecuteCommand(mycmd, pMANUALCMD, ResolveVars=True, SaveUnresolved=True)
 3572 
 3573         # Save the command only if Command History is enabled (MAXMENU > 0)
 3574         # AND one of two conditions exist:
 3575         #
 3576         #    1) No initial string was provided (The user entered a command manually).
 3577         #    2) An initial string was provided, but the user edited it.
 3578 
 3579         if (MAXMENU > 0) and ((not initial) or (cmd != initial)):
 3580             UI.LastCmd = cmd
 3581 
 3582     UI.DirList.focus()
 3583 
 3584     return 'break'
 3585 
 3586 # End of 'KeyRunCommand()'
 3587 
 3588 
 3589 #####
 3590 # Event Handler: Process Current Selection
 3591 #####
 3592 
 3593 def DirListHandler(event):
 3594     global UI
 3595     
 3596     # Get current selection.  If none, just return, otherwise process
 3597     selected =  UI.LastInSelection()
 3598     if not selected:
 3599         return
 3600     
 3601     # If we're on Win32 and we just selected ".." from the root of
 3602     # a drive, request a display of the Drive List.  LoadDirList()
 3603     # will check to see if there is anything in the Drive List and
 3604     # do nothing if it is empty (which happens if the user has not
 3605     # installed the Win32All package).
 3606 
 3607     if OSNAME =='nt' and \
 3608                os.path.abspath(UI.CurrentDir) == os.path.abspath(UI.CurrentDir + selected):
 3609 
 3610         LoadDirList(SHOWDRIVES, save=True)
 3611         UI.MouseNewDir = True
 3612 
 3613     # If selection is a directory, move there and list contents.
 3614 
 3615     elif os.path.isdir(os.path.join(UI.CurrentDir, selected)):
 3616 
 3617         # On Unix, don't follow links pointing back to themselves
 3618 
 3619         if OSNAME == 'posix' and os.path.samefile(UI.CurrentDir, UI.CurrentDir + selected):
 3620 
 3621             # Protect with try/except because Tk loses track of things
 3622             # if you keep hitting this selection very rapidly - i.e. Select
 3623             # the entry and lean on the Enter key.  The try/except
 3624             # prevents the error message (which is benign) from ever
 3625             # appearing on stdout.
 3626             
 3627             try:
 3628                 WrnMsg(wLINKBACK % (UI.CurrentDir + selected[:-1]))
 3629             except:
 3630                 pass
 3631             return
 3632 
 3633         # Build full path name
 3634         selected = os.path.join(os.path.abspath(UI.CurrentDir), selected)
 3635 
 3636         # Convert ending ".." into canonical path
 3637         if selected.endswith(".."):
 3638             selected = PSEP.join(selected.split(PSEP)[:-2])
 3639         
 3640         # Need to end the directory string with a path
 3641         # separator character so that subsequent navigation
 3642         # will work when we hit the root directory of the file
 3643         # system.  In the case of Unix, this means that
 3644         # if we ended up at the root directory, we'll just
 3645         # get "/".  In the case of Win32, we will get
 3646         # "DRIVE:/".
 3647 
 3648         if selected[-1] != PSEP:
 3649             selected += PSEP
 3650 
 3651         # Load UI with new directory
 3652         LoadDirList(selected, save=True)
 3653 
 3654         # Indicate that we entered a new directory this way.
 3655         # This is a workaround for Tk madness.  When this
 3656         # routine is invoked via the mouse, Tk sets the
 3657         # activation *when this routine returns*.  That means
 3658         # that the activation set at the end of LoadDirList
 3659         # gets overidden.  We set this flag here so that
 3660         # we can subsequently do the right thing in our
 3661         # background polling loop.  Although this routine
 3662         # can also be invoked via a keyboard selection,
 3663         # we run things this way regardless since no harm
 3664         # will be done in the latter case.
 3665 
 3666         UI.MouseNewDir = True
 3667 
 3668 
 3669     # No, a *file* was selected with a double-click
 3670     # We know what to do on Win32 and Unix.  We ignore
 3671     # the selection elsewhere.
 3672 
 3673     elif (OSPLATFORM == 'win32') or (OSNAME == 'posix'):
 3674 
 3675         # Find out if the OS thinks this is an executable file
 3676 
 3677         executable = os.stat(selected)[ST_MODE] & (S_IXUSR|S_IXGRP|S_IXOTH)
 3678 
 3679         # Apply any relevant association information, skipping types
 3680         # found in the exclusion list
 3681         
 3682 
 3683         # Ignore things that are on the exclusion list
 3684         
 3685         excluded = False
 3686         for exclude in UI.Associations[ASSOCEXCL]:
 3687 
 3688             # Handle case-insensitive exclusions
 3689 
 3690             if exclude[0] == ASSOCNOCASE:
 3691                 selected = selected.lower()
 3692                 exclude = exclude[1:].lower()
 3693 
 3694             # See if there is an exclusion
 3695 
 3696             if fnmatch(selected, exclude):
 3697                 excluded = True
 3698 
 3699         # Check the selection against every named association, but skip
 3700         # the ASSOCDFLT and ASSOCEXCL associations because they are
 3701         # special directives and not "real" associations.
 3702 
 3703         assocfound = False
 3704         if not excluded:
 3705             for assoc in UI.Associations:
 3706 
 3707                 # Handle case-insensitive associations
 3708                 
 3709                 tstassoc = assoc
 3710                 if tstassoc[0] == ASSOCNOCASE:
 3711                     selected = selected.lower()
 3712                     tstassoc = tstassoc[1:].lower()
 3713 
 3714                 # See if the selection matches the association
 3715 
 3716                 if (assoc != ASSOCDFLT) and (assoc != ASSOCEXCL) and fnmatch(selected, tstassoc):
 3717                     selected = UI.Associations[assoc]
 3718                     assocfound = True
 3719 
 3720             # If we did not find a file association and a default
 3721             # association is defined, apply it to everything
 3722             # except executable files.
 3723 
 3724             if not assocfound and (ASSOCDFLT in UI.Associations) and not executable:
 3725                 selected = UI.Associations[ASSOCDFLT]
 3726                 assocfound = True
 3727 
 3728         
 3729         # In the case of Unix-like OSs, if the file is non-executable
 3730         # and has no association, this is an error and needs to
 3731         # flagged as such.  In Windows, this may- or may-not be an
 3732         # error because of Windows own association mechanism.  So, we
 3733         # pass the request through on Windows and let it handle this
 3734         # case.
 3735 
 3736         if not executable and not assocfound and OSNAME == 'posix':
 3737             ErrMsg(eBADEXEC % selected)
 3738             
 3739         # Now execute the command
 3740 
 3741         else:
 3742 
 3743             usestartdir=False
 3744             # If there is NOT an association, create absolute path to
 3745             # selected command
 3746 
 3747             if not assocfound:
 3748                 selected = os.path.join(os.path.abspath(UI.CurrentDir), selected)
 3749 
 3750                 # Win32 has a special command interface wherein its internal associations
 3751                 # are applied.  We only invoke command execution this way when twander
 3752                 # found no associations of its own.
 3753 
 3754                 if OSPLATFORM == 'win32':
 3755                     usestartdir=True
 3756 
 3757             ExecuteCommand(selected, '', ResolveVars=True, ResolveBuiltIns=True, UseStartDir=usestartdir)
 3758 
 3759     return 'break'
 3760 
 3761 # End of 'DirListHandler()'
 3762 
 3763 
 3764 #####
 3765 # Event Handler: Process Directory Shortcut Request
 3766 #####
 3767 
 3768 def DirSCKeyPress(event, index):
 3769 
 3770     # Process the keypress
 3771     
 3772     dir = UI.DirSCKeys[index-1]
 3773     if dir:
 3774         LoadDirList(ProcessVariables(dir, 0, dir))
 3775 
 3776     # Inhibit further processing of key - some Function Keys
 3777     # have default behavior in Tk which we want to suppress.
 3778 
 3779     return "break"
 3780 
 3781 # End of 'DirSCKeyPress()'
 3782 
 3783 
 3784 #####
 3785 # Event Handler: Set Desired Directory Shortcut Key To Current Directory
 3786 #####
 3787 
 3788 def DirSCSet(event):
 3789 
 3790     index = askinteger(pSCCHANGE, pSCNUM, minvalue=1, maxvalue=12)
 3791     UI.DirList.focus()
 3792 
 3793     # Set the indicated shortcut key to the current directory
 3794     # And update the menus to reflect this fact
 3795 
 3796     if index:
 3797         UI.DirSCKeys[index-1] = UI.CurrentDir
 3798         LoadShortcutMenu()
 3799 
 3800     # Inhibit further processing of key - some Function Keys
 3801     # have default behavior in Tk which we want to suppress.
 3802 
 3803     return "break"
 3804 
 3805 # End of 'DirSCSet()'
 3806 
 3807 
 3808 #--------------------  Memory Features --------------------#
 3809 
 3810 #####
 3811 # Event Handler: Menu-Related Features Handled By Single Handler
 3812 #####
 3813 
 3814 def KeyMemHandler(mem, clear=False):
 3815     global UI
 3816     
 3817     # Clearing Memory
 3818     if clear:
 3819 
 3820         # Clear all
 3821         if mem == NUMPROGMEM + 1:
 3822             UI.ProgMem = [[], [], [], [], [], [], [], [], [], [], [], []]
 3823 
 3824         # Clear specific location
 3825         if 0 < mem < NUMPROGMEM + 1:
 3826             UI.ProgMem[mem-1] = []
 3827 
 3828     # Saving to memory
 3829     else:
 3830         for x in UI.AllSelection():
 3831             UI.ProgMem[mem-1].append(StripPSEP(os.path.abspath(x)))
 3832 
 3833     if DEBUGLEVEL & DEBUGMEM:
 3834         if 0 < mem < NUMPROGMEM + 1:
 3835             PrintDebug(dMEM % mem, UI.ProgMem[mem-1])
 3836         else:
 3837             PrintDebug(dMEMALL, UI.ProgMem)
 3838 
 3839 
 3840     # Inhibit further processing of keystroke so Tkinter
 3841     # defaults like Alt-F10 don't take hold.
 3842     
 3843     return "break"
 3844 
 3845 # End of 'KeyMemHandler()
 3846 
 3847 
 3848 #------------------  Sorting Features  --------------------#
 3849 
 3850 #####
 3851 # Event Handler: Set Sort Parameters
 3852 #####
 3853 
 3854 def KeySetSortParm(parm):
 3855     global SORTBYFIELD, SORTREVERSE, SORTSEPARATE
 3856 
 3857     # Which entry in the Name2Key 
 3858 
 3859     refresh = False
 3860 
 3861     if parm == fREVERSE:
 3862         SORTREVERSE = not SORTREVERSE
 3863         refresh = True
 3864 
 3865     # Separate Dirs/Files Means Nothing In Drive List View - Suppress
 3866     # this there to avoid an unnecessary refresh
 3867     
 3868     elif (parm == fSEPARATE):
 3869         if  (UI.CurrentDir != SHOWDRIVES):
 3870             SORTSEPARATE = not SORTSEPARATE
 3871             refresh = True
 3872 
 3873     # Sort By Selected Parameter
 3874     # In Drive List View only respond to those keys that have a
 3875     # corresponding Sort Menu Entry
 3876     
 3877     else:
 3878 
 3879         
 3880         # First determine whether we even want to act on this keystroke.
 3881         # A given keystroke is ignored if the Name of the sort option associated
 3882         # with the current view is None (the Python value None, not the string 'None').
 3883 
 3884         # Get index of desired entry in Name2Key tuple
 3885         
 3886         if UI.CurrentDir == SHOWDRIVES:
 3887             idx = 2
 3888         else:
 3889             idx = 1
 3890 
 3891         # Get the tuple associated with new key
 3892         n2k = Name2Key[parm.lower()]
 3893 
 3894         # Get the name associated with the new key appropriate for the current view
 3895         sortname = n2k[idx]
 3896 
 3897         # If the new key is the same as the old key, ignore - ignore, we're already
 3898         # sorted this way.  This avoids unnecessary refreshes.
 3899         #
 3900         # If the new key is (Python) None, it means we do not want to process this
 3901         # keystoke, so ignore it.
 3902 
 3903         if (parm == SORTBYFIELD) or not sortname:
 3904             return
 3905 
 3906         # Go ahead and do the new sort
 3907         else:
 3908             SORTBYFIELD = parm
 3909             refresh = True
 3910 
 3911     if refresh:
 3912         LoadHelpMenu()
 3913         RefreshDirList(None)
 3914 
 3915     return 'break'
 3916 
 3917 #End of 'KeySetSortParm()'
 3918 
 3919 
 3920 #-------------- Handler Utility Functions -----------------#
 3921 
 3922 #####
 3923 # Event Handler: Popup Menus
 3924 #####
 3925 
 3926 def PopupMenu(menu, x, y):
 3927 
 3928     # Popup requested menu at specified coordinates
 3929     # but only if the menu has at least one entry.
 3930 
 3931     if menu.index(END):
 3932         menu.tk_popup(x, y)
 3933 
 3934 # End of 'PopupMenu()'
 3935 
 3936 
 3937 #####
 3938 # Execute A Command
 3939 #####
 3940 
 3941 def ExecuteCommand(cmd, name, UseStartDir=False, ResolveVars=False, ResolveBuiltIns=True, SaveUnresolved=False):
 3942     global UI
 3943 
 3944     # Do nothing on blank commands
 3945     if not cmd.strip():
 3946         return
 3947 
 3948     # Work with a copy of the passed command
 3949     newcmd = cmd
 3950 
 3951     # Replace references to any Environment or User-Defined variables
 3952     # but only when asked to.  This needs to be done *before*
 3953     # we process the built-ins so that variable references within
 3954     # a PROMPT or YESNO are resolved before we handle the prompting.
 3955     
 3956     if newcmd and ResolveVars:
 3957         newcmd = ProcessVariables(newcmd, 0 , name)
 3958 
 3959     # Process references to any Built-In variables
 3960     if ResolveBuiltIns:
 3961         newcmd = ProcessBuiltIns(newcmd, name)
 3962 
 3963     # A leading REFRESHAFTER in the command string means the user wants
 3964     # a display refresh after the command returns
 3965 
 3966     do_refresh_after = False
 3967     if newcmd and newcmd[0] == REFRESHAFTER:
 3968         newcmd = newcmd[len(REFRESHAFTER):]
 3969         do_refresh_after = True
 3970 
 3971         
 3972     # Just dump command if we're debugging
 3973 
 3974     if DEBUGLEVEL & DEBUGCMDS:
 3975         PrintDebug(dCMD, [newcmd,])
 3976     
 3977     # A null return value means there was a problem - abort
 3978     # Otherwise,actually execute the command.
 3979     elif newcmd:
 3980         
 3981         # Run the command on Win32 using filename associations
 3982         if UseStartDir:
 3983             try:
 3984                 os.startfile(newcmd)
 3985             except:
 3986                 WrnMsg(wBADEXE % newcmd)
 3987 
 3988         # Normal command execution for both Unix and Win32
 3989         else:
 3990             try:
 3991                 if (OSNAME == 'posix') and not USETHREADS:
 3992                     os.system(newcmd + " &")
 3993                 else:
 3994                     thread.start_new_thread(os.system, (newcmd,))
 3995             except:
 3996                 WrnMsg(wBADEXE % newcmd)
 3997 
 3998 
 3999     # Update the Command History observing MAXMENU
 4000     # In most cases, we want to save the command with all the
 4001     # variables (Built-In, Environment, User-Defined) resolved (dereferenced).
 4002     # However, sometimes (e.g. manual command entry via KeyRunCommand()) we
 4003     # want to save the *unresolved* version.  We also preserve the REFRESHAFTER
 4004     # indicator for all commands - manual or from the config file.  This provides
 4005     # consistency in the history regardless of the soure of the command.
 4006 
 4007     if SaveUnresolved:
 4008         savecmd = cmd
 4009 
 4010     elif do_refresh_after:
 4011         savecmd = REFRESHAFTER + newcmd
 4012 
 4013     else:
 4014         savecmd = newcmd
 4015 
 4016     UpdateMenu(UI.HistBtn, UI.CmdHist, MAXMENU, MAXMENUBUF, KeyRunCommand, newentry=savecmd, fakeevent=True)
 4017                 
 4018     # Dump Command History stack if requested
 4019     
 4020     if DEBUGLEVEL & DEBUGHIST:
 4021         PrintDebug(dHIST, UI.CmdHist)
 4022 
 4023         
 4024     # Do a display refresh if the user wanted it
 4025     # Wait a while to give the command a chance to complete
 4026     # Then clear any selections
 4027     
 4028     if do_refresh_after:
 4029         time.sleep(AFTERWAIT)
 4030         if AFTERCLEAR:
 4031             KeySelNone(None)
 4032         RefreshDirList(None)
 4033         
 4034 # End of 'ExecuteCommand()
 4035 
 4036 
 4037 #####
 4038 # Load UI With Selected Directory
 4039 #####
 4040 
 4041 def LoadDirList(newdir, save=True, updtitle=True):
 4042 
 4043     # Make sure we're permitted to navigate - we have to allow initial entry into STARTDIR
 4044     if NONAVIGATE and (newdir != STARTDIR):
 4045         return
 4046 
 4047     # Reset any active filtering
 4048     UI.FilterWildcard = ("None", None, None, False)
 4049 
 4050     # Transform double forward-slashes into a single
 4051     # forward-slash.  This keeps the Directory Stack
 4052     # and Visited lists sane under Unix and prevents
 4053     # Win32 from attempting to enter a Drive List View
 4054     # when the user enters this string but Win32All has
 4055     # not been loaded.
 4056 
 4057     if newdir == '//':
 4058         newdir = '/'
 4059 
 4060     # Get path into canonical form unless we're trying
 4061     # to display a Win32 Drive List
 4062     
 4063     if newdir != SHOWDRIVES:
 4064         newdir = os.path.abspath(newdir)
 4065 
 4066         # Make sure newdir properly terminated
 4067         if newdir[-1] != PSEP:
 4068             newdir += PSEP
 4069 
 4070     # User has requested a Drive List View.  Make sure we're
 4071     # running on Win32 and see the feature is available.  If
 4072     # not available (which means Win32All is not installed),
 4073     # just ignore and return, doing nothing.
 4074 
 4075     elif OSPLATFORM != 'win32' or  not GetWin32Drives():
 4076         return
 4077 
 4078     # Indicate we are doing a refresh.
 4079     # This defaults to always being done.
 4080     # Exception is first call to this routine from setup logic.
 4081     
 4082     if updtitle:
 4083         UI.UpdateTitle(UIroot, refreshing=REFRESHINDI)
 4084 
 4085     # Check right now to see if we can read
 4086     # the directory.  If not, at least we
 4087     # haven't screwed up the widget's current
 4088     # contents or program state.
 4089 
 4090     try:
 4091         contents = BuildDirList(newdir)
 4092     except:
 4093         # If CurrentDir set, we're still there: error w/ recovery
 4094         if UI.CurrentDir:
 4095             ErrMsg(eDIRRD % newdir)
 4096             return
 4097 
 4098         # If not, we failed on the initial directory: error & abort
 4099         else:
 4100             ErrMsg(eINITDIRBAD % newdir)
 4101             sys.exit(1)
 4102 
 4103     # Push last directory visited onto the visited stack
 4104 
 4105     # We do NOT save this to the stack if:
 4106     #
 4107     #    1) We've been told not to. - Passed when we're called (save=False).
 4108     #    2) If we're trying to move into the current directory again.
 4109     #       This can happen either when the user does a manual directory
 4110     #       change or if they press ".." while in root.  We don't
 4111     #       actually want to save the directory until we *leave* it,
 4112     #       otherwise we'll end up with a stack top and current
 4113     #       directory which are the same, and we'll have to go
 4114     #       back *twice* to move to the previous directory.
 4115     
 4116     # Are we trying to move back into same directory?
 4117     if os.path.abspath(UI.CurrentDir) == os.path.abspath(newdir):
 4118         save = False
 4119 
 4120     # Now save if we're supposed to.
 4121     if save and UI.CurrentDir:
 4122         UI.LastDir.append(UI.CurrentDir)
 4123 
 4124     # Dump directory stack if debug requested it
 4125     if DEBUGLEVEL & DEBUGDIRS:
 4126         PrintDebug(dDIRSTK, UI.LastDir)
 4127 
 4128     # And select new directory to visit
 4129     UI.CurrentDir = newdir
 4130 
 4131     # Wait until we have exclusive access to the widget
 4132 
 4133     while not UI.DirListMutex.testandset():
 4134         pass
 4135 
 4136     # Clear out the old contents
 4137     UI.DirList.delete(0,END)
 4138 
 4139     # Load new directory contents into UI
 4140     for x in contents:
 4141         UI.DirList.insert(END, x)
 4142 
 4143     # Also move the program context to the new directory
 4144     # for everything except a Drive List View.  In that case
 4145     # the program context remains in the directory from
 4146     # which the Drive List View was selected
 4147 
 4148     if newdir != SHOWDRIVES:
 4149         os.chdir(newdir)
 4150 
 4151     # Keep list of all unique directories visited in the Directory Menu
 4152     UpdateDirMenu(newdir)
 4153 
 4154     # Nothing should be pre-selected in the new directory listing
 4155     KeySelNone(None)
 4156 
 4157     # Update Sort Menu to reflect whether we're in Normal or Drive List View
 4158     LoadSortMenu()
 4159 
 4160     # Update titlebar to reflect any changes
 4161     UI.UpdateTitle(UIroot)
 4162 
 4163     #Release the lock
 4164     UI.DirListMutex.unlock()
 4165 
 4166 # End of 'LoadDirList():
 4167 
 4168 
 4169 #####
 4170 # Return Ordered List Of Directories & Files For Current Root
 4171 # Posts A Warning Message If SORTBYFIELD Is Out Of Range
 4172 #####
 4173 
 4174 def BuildDirList(currentdir):
 4175     global UI, SORTBYFIELD, REFRESHINT
 4176 
 4177     # Set time of the refresh
 4178     begintime = time.time()
 4179 
 4180     # We'll need the nominal refresh interval later
 4181     nominal = UI.OptionsNumeric["REFRESHINT"]
 4182 
 4183     # Check to see if SORTBYFIELD makes sense
 4184 
 4185     if SORTBYFIELD.lower() not in Name2Key.keys():
 4186         default = UI.OptionsString["SORTBYFIELD"]
 4187         WrnMsg(wBADSORTFLD % (SORTBYFIELD, default))
 4188         SORTBYFIELD = default
 4189         LoadHelpMenu()
 4190 
 4191     UI.TotalSize = 0
 4192 
 4193     fileinfo = []
 4194     dKeys,  fKeys  = {}, {}
 4195     
 4196     # Indicate where in each display string the actual file name
 4197     # can be found.  This is used both in the code in this routine
 4198     # and other places in the program where just the file name is
 4199     # needed - for example, when we want to execute it or replace it
 4200     # in one of the built-ins.  This value depends on whether or
 4201     # not we are viewing details or not.
 4202     
 4203     if UI.DetailsOn:
 4204         if currentdir == SHOWDRIVES:
 4205             UI.NameFirst = SZ_DRIVE_TOTAL
 4206         else:
 4207             UI.NameFirst = ST_SZTOTAL
 4208     else:
 4209         UI.NameFirst = 0
 4210 
 4211     # Two possible cases have to be handled:
 4212     # A normal directory read and a Drive List View
 4213     # under Win32.
 4214 
 4215     #####
 4216     # Normal directory reads
 4217     #####
 4218     
 4219     if currentdir != SHOWDRIVES:
 4220 
 4221         # Save the current OS directory context and temporarily set it
 4222         # to the target directory
 4223 
 4224         cwd = os.path.abspath(os.path.curdir)
 4225         os.chdir(currentdir)
 4226 
 4227         # Get and sort directory contents
 4228 
 4229         filelist = os.listdir(currentdir)
 4230         keyindex = Name2Key[SORTBYFIELD.lower()][0]
 4231         dList, fList = [], []
 4232 
 4233         
 4234         for x in range(len(filelist)):
 4235 
 4236             # Get File/Dir name
 4237             
 4238             file = filelist[x]
 4239 
 4240             # Get detail display string as well as individual fields
 4241             
 4242             detail, fields = FileDetails(file, currentdir)
 4243 
 4244             # Add trailing path separator to directory entries
 4245             # Pay attention to pickup symlinks pointing to directories
 4246             
 4247             if detail[0] == ST_SPECIALS["04"] or os.path.isdir(detail.split(SYMPTR)[-1].strip()):
 4248                 file   += PSEP
 4249                 detail += PSEP
 4250                     
 4251             # Check against any active filtering by wildcard.  Only allow files through that match.
 4252 
 4253             matchthis = file
 4254             if UI.DetailsOn:
 4255                 matchthis = detail
 4256 
 4257             if not FilterMatching(matchthis):
 4258                 continue
 4259 
 4260             # Keep running tally of total files sizes
 4261 
 4262             UI.TotalSize += fields[Name2Key[fLENGTH.lower()][0]]
 4263 
 4264             # Decide whether we have to sort or not, act accordingly
 4265             
 4266             if SORTBYFIELD.lower() == fNONE.lower():
 4267 
 4268                 # Nope, no sorting, just load-up the return data structures
 4269                 # with either the file name or the details string depending
 4270                 # on whether or not we're displaying details at the moment.
 4271 
 4272                 if UI.DetailsOn:
 4273                     dList.append(detail)
 4274                 else:
 4275                     dList.append(file)
 4276 
 4277             # Yup, we're going to sort later - build index/return data structures
 4278             else:
 4279 
 4280                 # Get the keystring used for the actual sort
 4281                 # Collapse case on Win32 when sorting by name
 4282 
 4283                 sortkey = fields[keyindex]
 4284                 if OSPLATFORM == 'win32' and (SORTBYFIELD.lower() == fNAME.lower()):
 4285                     sortkey = sortkey.lower()
 4286 
 4287                 # Keep track of the file name and its details,
 4288                 # separating directories from files Treat symlinks
 4289                 # pointing to directories as directories for this
 4290                 # purpose (if that's what the user wants).
 4291                 # Build corresponding key list for sorting.
 4292                 
 4293                 fileinfo.append((file, detail))
 4294                 currentpos = len(fileinfo)-1
 4295                 
 4296                 if detail[0] == ST_SPECIALS["04"] or (SYMDIR and os.path.isdir(detail.split(SYMPTR)[-1].strip())):
 4297                     dKeys.setdefault(sortkey, []).append(currentpos)
 4298 
 4299                 else:
 4300                     fKeys.setdefault(sortkey, []).append(currentpos)
 4301                     
 4302         # If sorting has been requested, do so now
 4303         
 4304         if SORTBYFIELD.lower() != fNONE.lower():
 4305 
 4306             # Sort keys according to user's desire for Dir/File separation
 4307 
 4308             if SORTSEPARATE:
 4309                 dk = dKeys.keys()
 4310                 dk.sort()
 4311                 fk = fKeys.keys()
 4312                 fk.sort()
 4313 
 4314             else:
 4315                 # Combine the two key dicts into one composite
 4316                 # dictionary (dKeys)
 4317 
 4318                 for key in fKeys.keys():
 4319                     for val in fKeys[key]:
 4320                         dKeys.setdefault(key, []).append(val)
 4321 
 4322                 dk = dKeys.keys()
 4323                 dk.sort()
 4324                 fk = []
 4325 
 4326             # Reverse the sorts, if requested
 4327 
 4328             if SORTREVERSE:
 4329                 dk.reverse()
 4330                 fk.reverse()
 4331 
 4332             # Build corresponding dir/file lists, observing user's
 4333             # request for detail information
 4334 
 4335             for x in dk:
 4336 
 4337                 for index in dKeys[x]:
 4338 
 4339                     if UI.DetailsOn:
 4340                         dList.append(fileinfo[index][1])
 4341                     else:
 4342                         dList.append(fileinfo[index][0])
 4343 
 4344             for x in fk:
 4345 
 4346                 for index in fKeys[x]:
 4347 
 4348                     if UI.DetailsOn:
 4349                         fList.append(fileinfo[index][1])
 4350                     else:
 4351                         fList.append(fileinfo[index][0])
 4352 
 4353             # Sorting logic ends here
 4354 
 4355         # Now return results in their final form
 4356 
 4357         if UI.DetailsOn:
 4358             dotdot = [FileDetails(".." + PSEP, currentdir)[0],]
 4359         else:
 4360             dotdot = [".." + PSEP,]
 4361 
 4362 
 4363         # If the user doesn't want symbolic link expansion, get rid of it.
 4364 
 4365         if not SYMEXPAND:
 4366             for list in (fList, dList):
 4367 
 4368                 for i in range(len(list)):
 4369 
 4370                     # Only necessary for symlinks
 4371                     if list[i].count(SYMPTR):
 4372 
 4373                         # Preserve possible indication this is a directory
 4374                         tail = list[i][-1]        
 4375 
 4376                         # Remove target portion of any symlinks
 4377                         list[i] = list[i].split(SYMPTR)[0]
 4378 
 4379                         # If not already noted, indicate symlinks pointing to directories
 4380                         if tail == PSEP and not list[i].endswith(PSEP):
 4381                             list[i] += tail
 4382 
 4383         # Return the results
 4384         # Entry to move up one directory is always first,
 4385         # no matter what the sort.  This is necessary because
 4386         # OSs like Win32 like to use '$' in file names  which
 4387         # sorts before "."
 4388 
 4389         # Restore the original directory context
 4390 
 4391         os.chdir(cwd)
 4392 
 4393         # Dynamically adjust refresh interval if option enabled
 4394 
 4395         if ADAPTREFRESH:
 4396 
 4397             UI.LastRefresh    = time.time()
 4398             length  = int((time.time() - begintime) * 1000)
 4399 
 4400             if length > nominal:
 4401                 REFRESHINT = int((length - nominal) * 1.5) + nominal
 4402             else:
 4403                 REFRESHINT = nominal
 4404             
 4405         if SORTREVERSE:
 4406             return dotdot + fList + dList
 4407         else:
 4408             return dotdot + dList + fList
 4409 
 4410     #####
 4411     # The user requested Drive List View.
 4412     #####
 4413     
 4414     else:
 4415 
 4416 
 4417         dlv     = {}
 4418         dList   = []
 4419 
 4420         # There are more keys in normal view than in Drive List View
 4421         # If the user has selected one of these, map them to sort
 4422         # by name in Drive List View.        
 4423 
 4424         dlvkey = Name2Key[SORTBYFIELD.lower()][0]
 4425         if dlvkey > MAXDLVKEY:
 4426             dlvkey = MAXDLVKEY
 4427 
 4428         drivelist = GetWin32Drives()
 4429 
 4430         # Create a detailed entry for each drive on the system
 4431         # Store it as a string in the details list which will
 4432         # then be returned to the caller
 4433 
 4434         for drive in drivelist:
 4435 
 4436             
 4437             fields  = []      
 4438 
 4439             # Drive Label - Drive Might Not Be Available
 4440             try:
 4441                 label = GetVolumeInformation(drive)[0]
 4442             except:
 4443                 label = NODRIVE
 4444 
 4445             if not label:
 4446                 label = NOLABEL
 4447 
 4448             # Type Of Drive - We need this now to get hostname
 4449             drivetype = GetDriveType(drive)
 4450             typestring = ''
 4451 
 4452             for type, stype in win32all_type:
 4453                 if drivetype == type:
 4454                     typestring = stype
 4455 
 4456             # Machine Hosting The Drive
 4457             if drivetype == win32con.DRIVE_REMOTE:
 4458                 name = WNetGetUniversalName(drive, 1)
 4459             else:
 4460                 name = label
 4461 
 4462             entry = PadString(name, SZ_DRIVE_SHARE)
 4463             fields.append(name)
 4464 
 4465             # Add the drive type
 4466             entry += PadString(typestring, SZ_DRIVE_TYPE)
 4467             fields.append(typestring)
 4468 
 4469             # Get Drive Space Information - Drive Might Not Be Available
 4470             try:
 4471                 clustersz, sectorsz, freeclusters, totalclusters = GetDiskFreeSpace(drive)
 4472 
 4473                 # Free Space
 4474                 fspace = clustersz * sectorsz * freeclusters
 4475                 freespace = FileLength(fspace)
 4476 
 4477                 # Total Space
 4478                 tspace = clustersz * sectorsz * totalclusters
 4479                 totalspace = FileLength(tspace)
 4480 
 4481             except:
 4482                 fspace, tspace = (0, 0)
 4483                 freespace, totalspace = ('0', '0')
 4484 
 4485 
 4486             freespace  = PadString(freespace, SZ_DRIVE_FREE, Rjust=True, Trailing=SZ_TRAILING_SPACE)
 4487             totalspace = PadString(totalspace, SZ_DRIVE_TTL, Rjust=True, Trailing=SZ_TRAILING_SPACE)
 4488             entry += "%s%s%s%s" % (freespace, WIN32FREE, totalspace, WIN32TOTAL)
 4489 
 4490             fields.append(fspace)
 4491             fields.append(tspace)
 4492 
 4493 
 4494             # Finally, tack on the drive letter
 4495             entry += drive
 4496             fields.append(drive)
 4497 
 4498             # Filter through any active wildcard
 4499 
 4500             matchthis = drive
 4501             if UI.DetailsOn:
 4502                 matchthis = entry
 4503 
 4504             if not FilterMatching(matchthis):
 4505                 continue
 4506 
 4507             # Keep running total of available space
 4508             UI.TotalSize += tspace
 4509 
 4510             # If we're not going to sort later, just build the list
 4511             # of drives now
 4512 
 4513             if SORTBYFIELD.lower() == fNONE.lower():
 4514                 if UI.DetailsOn:
 4515                     dList.append(entry)
 4516                 else:
 4517                     dList.append(drive)
 4518 
 4519             # Nope, prepare to sort later
 4520             else:
 4521                 idx = fields[dlvkey]
 4522                 dlv.setdefault(idx,[]).append((drive, entry))
 4523 
 4524 
 4525         # Now that we've built the list, sort by indicated parameter
 4526         # if user has so indicated
 4527         
 4528         if SORTBYFIELD.lower() != fNONE.lower():
 4529 
 4530             
 4531             indexes = dlv.keys()
 4532             indexes.sort()
 4533 
 4534             if SORTREVERSE:
 4535                 indexes.reverse()
 4536 
 4537             # Now build output list in sorted order
 4538 
 4539             for x in indexes:
 4540 
 4541                 for entry in dlv[x]:
 4542 
 4543                     if UI.DetailsOn:
 4544                         dList.append(entry[1])
 4545                     else:
 4546                         dList.append(entry[0])
 4547 
 4548         # Dynamically adjust refresh interval if option enabled
 4549 
 4550         if ADAPTREFRESH:
 4551 
 4552             UI.LastRefresh    = time.time()
 4553             length  = int((time.time() - begintime) * 1000)
 4554 
 4555             if length > nominal:
 4556                 REFRESHINT = int((length - nominal) * 1.5) + nominal
 4557             else:
 4558                 REFRESHINT = nominal
 4559             
 4560         # Return the list
 4561 
 4562         return dList
 4563                     
 4564 
 4565     
 4566 # End of  'BuildDirList()'
 4567 
 4568 
 4569 #####
 4570 # Get Details For A File Or Directory
 4571 # Returns Both A Formatted Display String With Detail Information,
 4572 # And A List Containing The Individual Detail Fields
 4573 #####
 4574 
 4575 def FileDetails(name, currentdir):
 4576     
 4577     details = ""
 4578     fields  = []
 4579     
 4580     # Condition the name
 4581 
 4582     fn = os.path.join(currentdir, name)
 4583     if fn[-1] == PSEP:
 4584         fn =fn[:-1]
 4585 
 4586     # Get file details from OS
 4587     try:
 4588         stinfo =  os.lstat(fn)
 4589 
 4590     # 'lstat' failed - provide entry with some indication of this
 4591 
 4592     except:
 4593         pad  = (UI.NameFirst - len(iNOSTAT) - 1) * " "
 4594         details = pad + iNOSTAT + " " + name
 4595         fields  = ["-" * 10, 0, UNAVAILABLE, UNAVAILABLE, 0L, 0, name]
 4596 
 4597         # Done with this file
 4598         return details, fields
 4599 
 4600     # Do Win32-specific mode if win32all is loaded
 4601     if WIN32ALL and USEWIN32ALL and WIN32ALLON:
 4602         modlen = len(win32all_mode)
 4603 
 4604         try:
 4605             win32stat = GetFileAttributes(fn)
 4606             mode = ""
 4607         except:
 4608             mode = UNAVAILABLE
 4609 
 4610         if not mode:
 4611 
 4612             # Test each attribute and set symbol in respective
 4613             # position, if attribute is true.
 4614 
 4615             for i in range(modlen):
 4616                 mask, sym = win32all_mode[i]
 4617                 if win32stat & mask:
 4618                     mode += sym
 4619                 else:
 4620                     mode += '-'
 4621 
 4622     # We're either on Unix or Win32 w/o win32all available
 4623     else:
 4624         # Mode - 1st get into octal string
 4625 
 4626         mode = stinfo[ST_MODE]
 4627         modestr =  str("%06o" % mode)
 4628 
 4629         # Set the permission bits
 4630 
 4631         mode = ""
 4632         for x in [-3, -2, -1]:
 4633             mode +=  ST_PERMIT[int(modestr[x])]
 4634 
 4635         # Deal with the special permissions
 4636 
 4637         sp = int(modestr[-4])
 4638 
 4639         # Sticky Bit
 4640 
 4641         if sp & STICKY_MASK:
 4642             if mode[-1] == "x":
 4643                 mode = mode[:-1] + "t"
 4644             else:
 4645                 mode = mode[:-1] + "T"
 4646 
 4647         # Setgid Bit
 4648 
 4649         if sp & SETGID_MASK:
 4650             if mode[-4] == "x":
 4651                 mode = mode[:-4] + "s" + mode[-3:]
 4652             else:
 4653                 mode = mode[:-4] + "S" + mode[-3:]
 4654 
 4655         # Setuid Bit
 4656 
 4657         if sp & SETUID_MASK:
 4658             if mode[-7] == "x":
 4659                 mode = mode[:-7] + "s" + mode[-6:]
 4660             else:
 4661                 mode = mode[:-7] + "S" + mode[-6:]
 4662 
 4663         # Pickup the special file types
 4664         mode = ST_SPECIALS.get(modestr[0:2], "?") + mode
 4665 
 4666 
 4667     # Pad the result for column alignment
 4668     details += PadString(mode, ST_SZMODE)
 4669     fields.append(mode)
 4670 
 4671     # Number of links to entry
 4672     nlinks = stinfo[ST_NLINK]
 4673     details += PadString(str(nlinks), ST_SZNLINK)
 4674     fields.append(nlinks)
 4675 
 4676     # Get first ST_SZxNAME chars of owner and group names on unix
 4677 
 4678     if OSNAME == 'posix':
 4679 
 4680         # Convert UID to name, if possible
 4681         try:
 4682             owner = pwd.getpwuid(stinfo[ST_UID])[0][:ST_SZUNAME-1]
 4683 
 4684         # No valid name associated with UID, so use number instead
 4685         except:
 4686             owner = str(stinfo[ST_UID])
 4687 
 4688         # Convert GID to name, if possible
 4689         try:
 4690             group = grp.getgrgid(stinfo[ST_GID])[0][:ST_SZGNAME-1]
 4691 
 4692         # No valid name associated with GID, so use number instead
 4693         except:
 4694             group = str(stinfo[ST_GID])
 4695 
 4696 
 4697     # Handle Win32 systems
 4698     elif OSPLATFORM == 'win32':
 4699 
 4700         # Defaults
 4701         owner = WIN32OWNER
 4702         group = WIN32GROUP
 4703 
 4704         if WIN32ALL and USEWIN32ALL and WIN32ALLON:
 4705             try:
 4706                 # Get the internal Win32 security information for this file.
 4707                 ho     = GetFileSecurity(fn, OWNER_SECURITY_INFORMATION)
 4708                 hg     = GetFileSecurity(fn, GROUP_SECURITY_INFORMATION)
 4709                 sido   = ho.GetSecurityDescriptorOwner()
 4710                 sidg   = hg.GetSecurityDescriptorGroup()
 4711 
 4712                 # We have to know who is hosting the filesytem for this file
 4713 
 4714                 drive = fn[0:3]
 4715                 if GetDriveType(drive) == win32con.DRIVE_REMOTE:
 4716                     fnhost = WNetGetUniversalName(drive, 1).split('\\')[2]
 4717                 else:
 4718                     fnhost = WIN32HOST
 4719 
 4720                 # Now we can translate the sids into names
 4721 
 4722                 owner  = LookupAccountSid(fnhost, sido)[0]
 4723                 group  = LookupAccountSid(fnhost, sidg)[0]
 4724 
 4725             # We assume the values are unavailable on any error
 4726             except:
 4727                 owner = UNAVAILABLE
 4728                 group = UNAVAILABLE
 4729 
 4730      # Default names for all other OSs
 4731     else:
 4732         owner = OSNAME + FILEOWNER
 4733         group = OSNAME + FILEGROUP
 4734 
 4735     # Add them to the detail
 4736 
 4737     details += PadString(owner, ST_SZUNAME)
 4738     details += PadString(group, ST_SZGNAME)
 4739 
 4740     # Add them to the fields
 4741 
 4742     fields.append(owner)
 4743     fields.append(group)
 4744 
 4745     # Length
 4746 
 4747     rlen = stinfo[ST_SIZE]
 4748     flen = FileLength(rlen)
 4749     details += PadString(flen, ST_SZLEN, Rjust=True, Trailing=SZ_TRAILING_SPACE)
 4750     fields.append(rlen)
 4751 
 4752     # mtime
 4753 
 4754     rawtime = stinfo[ST_MTIME]
 4755     
 4756     # Get the whole time value
 4757     ftime = time.ctime(rawtime).split()[1:]
 4758 
 4759     # Pad single-digit dates differently for US vs. ISO
 4760     # date representation
 4761 
 4762     LEADFILL = " "
 4763     if ISODATE:
 4764         LEADFILL = "0"
 4765 
 4766     if len(ftime[1]) == 1:
 4767         ftime[1] = LEADFILL + ftime[1]
 4768 
 4769     # Drop the seconds
 4770 
 4771     ftime[-2] = ":".join(ftime[-2].split(":")[:-1])
 4772 
 4773     # Turn into a single string in either ISO or US format
 4774 
 4775     if ISODATE:
 4776         ftime = "-".join((ftime[3], ST_MONTHS[ftime[0]], ftime[1])) + \
 4777                 " " + ftime[2]
 4778 
 4779     # US Localized Format
 4780     
 4781     else:
 4782         ftime = " ".join(ftime)
 4783 
 4784     details += PadString(ftime, ST_SZMTIME)
 4785     fields.append(rawtime)
 4786 
 4787     # Add the File Name
 4788     details += name
 4789     fields.append(name)
 4790 
 4791     #  Include symlink details as necessary
 4792     if details[0] == 'l':
 4793 
 4794         # Symlink targets can be displayed as defined (default)
 4795         # or expanded to their absolute path string.
 4796         
 4797         if SYMRESOLV:
 4798             f = os.path.realpath(currentdir + name)
 4799         else:
 4800             f = os.readlink(currentdir + name)
 4801 
 4802         # Strip trailing path separator
 4803         # This will be handled by the caller
 4804 
 4805         if f[-1] == PSEP:
 4806             f = f[:-1]
 4807 
 4808         details += SYMPTR + f
 4809 
 4810     return details, fields
 4811 
 4812 # End of 'FileDetails()'
 4813 
 4814 
 4815 #####
 4816 # Process A Command Line Containing Built-In Variables
 4817 #####
 4818 
 4819 def ProcessBuiltIns(cmd, name):
 4820 
 4821     # Do files & directories first.  That way they can be embedded in
 4822     # prompting builtins.
 4823     
 4824     # Strip trailing path separators in each case to
 4825     # give the command author the maximum flexibility possible
 4826 
 4827     # We have to treat built-ins specially if we're in a Drive List View
 4828     # Most noteably, there is no meaning to [DIR] in this context
 4829 
 4830     if UI.CurrentDir == SHOWDRIVES:
 4831         currentdir = ""
 4832     else:
 4833         currentdir = StripPSEP(UI.CurrentDir)
 4834 
 4835 
 4836     selection = StripPSEP(UI.LastInSelection())
 4837     dselection = ""
 4838 
 4839     if selection:
 4840         dselection = QUOTECHAR + currentdir + PSEP + selection + QUOTECHAR
 4841         selection  = QUOTECHAR + selection + QUOTECHAR
 4842         
 4843     selections = ""
 4844     dselections = ""
 4845     for selected in UI.AllSelection():
 4846 
 4847         # Fill the various built-ins
 4848         selected = StripPSEP(selected)
 4849         dselections += QUOTECHAR + currentdir + PSEP + selected + QUOTECHAR + " "
 4850         selections  += QUOTECHAR + selected + QUOTECHAR + " "
 4851 
 4852 
 4853     # Force Unix-style path separators if requested
 4854 
 4855     if FORCEUNIXPATH and OSPLATFORM == 'win32':
 4856         currentdir   = currentdir.replace("\\", "/")
 4857         dselection   = dselection.replace("\\", "/")
 4858         dselections  = dselections.replace("\\", "/")
 4859                 
 4860 
 4861     # Now do the actual replacements
 4862 
 4863     cmd = cmd.replace(DIR, QUOTECHAR + currentdir + QUOTECHAR)
 4864     cmd = cmd.replace(SELECTION, selection)
 4865     cmd = cmd.replace(DSELECTION, dselection)
 4866     cmd = cmd.replace(SELECTIONS, selections)
 4867     cmd = cmd.replace(DSELECTIONS, dselections)
 4868     cmd = cmd.replace(HASH, COMMENT)
 4869     
 4870     # Process references to program memories
 4871 
 4872     for x in range(NUMPROGMEM):
 4873 
 4874         # Only do this if there is a reference to the memory in
 4875         # question.  This keeps the program from needlessly
 4876         # churning on long lists of memory contents which are not needed.
 4877 
 4878         vblref = "[MEM%s]" % str(x+1)
 4879         if cmd.count(vblref):
 4880             s = ""
 4881             for m in UI.ProgMem[x]:
 4882                 if FORCEUNIXPATH and OSPLATFORM == 'win32':
 4883                     m = m.replace("\\", "/")
 4884                 s += QUOTECHAR + m + QUOTECHAR + " "
 4885             cmd = cmd.replace(vblref, s)
 4886                     
 4887     # Now take care of the prompting
 4888 
 4889     for promptvar, handler, defaultarg, replace in ((YESNO, askyesno, "default", False),
 4890                                                  (PROMPT, askstring, "initialvalue", True)):
 4891 
 4892         for x in range(cmd.count(promptvar)):
 4893             b = cmd.find(promptvar)
 4894             e = cmd.find("}", b)
 4895             prompt = cmd[b + len(promptvar):e]
 4896 
 4897             # Pickup default value, if any
 4898 
 4899             default = {}
 4900             prompt = prompt.split(DEFAULTSEP)
 4901             prompt.append("")                                  # Guarantee a minimum of two entries
 4902             prompt, default[defaultarg] = prompt[0], prompt[1]
 4903 
 4904             # Condition the default if its a YESNO builtin
 4905 
 4906             if promptvar == YESNO:
 4907                 default[defaultarg] = default[defaultarg].strip().lower()
 4908 
 4909                 # YESNO dialogs can only accept two arguments (we just made them case-insensitive above)
 4910             
 4911                 if (default[defaultarg] not in ("yes", "no", "")):
 4912 
 4913                     # Display an error
 4914                     WrnMsg(wBADYESNODFLT % (default[defaultarg], name))
 4915 
 4916                     # We're done on an error
 4917                     return
 4918 
 4919             # Everything OK - Run the dialog
 4920 
 4921             # If there is no default argument, then don't pass anything at all - Tk gets confused if we do
 4922 
 4923             if not default[defaultarg]:
 4924                 default = {}
 4925 
 4926             val = handler(name, prompt, **default)
 4927 
 4928             # Make sure our program gets focus back
 4929             UI.DirList.focus()
 4930 
 4931             if val:
 4932                 if replace:
 4933                     cmd = cmd.replace(cmd[b:e+1], QUOTECHAR + val + QUOTECHAR)
 4934                 else:
 4935                     cmd = cmd.replace(cmd[b:e+1], '')
 4936 
 4937             # Null input means the command is being aborted
 4938             else:
 4939                 return
 4940 
 4941     return cmd
 4942 
 4943 # End of 'ProcessBuiltIns()'    
 4944 
 4945 
 4946 #####
 4947 # Process/Replace References To User-Defined & Environment Variables
 4948 #####
 4949 
 4950 def ProcessVariables(cmd, num, line):
 4951 
 4952     doeval = True
 4953     depth  = 0
 4954 
 4955     while doeval:
 4956 
 4957         # Bound the number of times we can nest a definition
 4958         # to prevent self-references which give infinite nesting depth
 4959 
 4960         depth += 1
 4961         if (depth > MAXNESTING):
 4962             doeval = False
 4963 
 4964             # See if there are still unresolved variable references.
 4965             # If so, let the user know
 4966 
 4967             if REVAR.findall(cmd):
 4968                 WrnMsg(wVBLTOODEEP % (num, cmd))
 4969                 return ""
 4970 
 4971         # Get a list of variable references
 4972         vbls = REVAR.findall(cmd)
 4973 
 4974         # Throw away references to Built-In Variables - these are
 4975         # processed at runtime and should be left alone here.
 4976 
 4977         # Note that we iterate over a *copy* of the variables
 4978         # list, because we may be changing that list contents
 4979         # as we go.  i.e., It is bogus to iterate over a list
 4980         # which we are changing during the iteration.
 4981 
 4982         for x in vbls[:]:
 4983 
 4984             # Ignore references to Built-In Variables here - They are
 4985             # processed at runtime.
 4986 
 4987             if UI.BuiltIns.has_key(x):
 4988                 vbls.remove(x)
 4989 
 4990             elif x.startswith(PROMPT):
 4991                 vbls.remove(x)
 4992 
 4993             elif x.startswith(YESNO):
 4994                 vbls.remove(x)
 4995 
 4996         if vbls:
 4997             for x in vbls:
 4998                 vbl = x[1:-1]
 4999 
 5000                 # Process ordinary variables
 5001                 if UI.SymTable.has_key(vbl):
 5002                     cmd = cmd.replace(x, UI.SymTable[vbl])
 5003 
 5004                 # Process environment variables.
 5005                 # If an environment variable is referenced,
 5006                 # but not defined, this is a fatal error
 5007 
 5008                 elif vbl[0] == ENVVBL:
 5009                     envvbl = os.getenv(vbl[1:])
 5010                     if envvbl:
 5011                         cmd = cmd.replace(x, envvbl)
 5012                     else:
 5013                         WrnMsg(wBADENVVBL % (num, x, line))
 5014                         return ""
 5015 
 5016                 # Process execution variables
 5017 
 5018                 elif vbl[0] == vbl[-1] == VAREXECUTE:
 5019 
 5020                     # Strip indicators
 5021                     
 5022                     vbl = vbl[1:-1]
 5023 
 5024                     # Find out if user wants newlines stripped
 5025 
 5026                     stripnl = False
 5027                     if vbl[0] == STRIPNL:
 5028                         stripnl = True
 5029                         vbl = vbl[1:]
 5030                         
 5031                     status, result = GetStatusOutput(vbl)
 5032 
 5033                     # Replace with results if there was no error
 5034                     
 5035                     if not status:
 5036 
 5037                         # Strip newlines if asked to
 5038                         if stripnl:
 5039                             result = result.replace('\n', ' ')
 5040                             
 5041                         # Place results into command string
 5042 
 5043                         cmd = cmd.replace(x, result)
 5044 
 5045                     # If there was an error, warn user, and terminate further processing
 5046                     else:
 5047                         WrnMsg(wBADVAREXEC % (num, x, line))
 5048                         return ""
 5049 
 5050                 # Process references to undefined variables
 5051                 else:
 5052                     WrnMsg(wUNDEFVBL % (num, x, line))
 5053                     return ""
 5054 
 5055         # No substitutions left to do
 5056         else:
 5057             doeval = False
 5058 
 5059     return cmd
 5060                         
 5061 # End of 'ProcessVariables()'
 5062 
 5063 
 5064 #####
 5065 # Refresh Contents Of Directory Listing To Stay In Sync With Reality
 5066 #####
 5067 
 5068 def RefreshDirList(event, ClearFilterWildcard=False):
 5069     global UI, INVERTFILTER
 5070 
 5071     # Indicate that we are doing an refresh
 5072     UI.UpdateTitle(UIroot, refreshing=REFRESHINDI)
 5073     
 5074     # Wait until we have exclusive access to the widget
 5075 
 5076     while not UI.DirListMutex.testandset():
 5077         pass
 5078 
 5079     # Clearout any active wildcard filtering if asked to
 5080 
 5081     if ClearFilterWildcard:
 5082         UI.FilterWildcard = ("None", None, None, False)
 5083         INVERTFILTER = False
 5084         
 5085     # Keep track of current selection and active line *by name*.  This
 5086     # will ensure correct reselection after a refresh where the
 5087     # contents of the directory have changed.  Since this uses simple
 5088     # string matching, the algorithm below has to be sensitive to
 5089     # expanded symbolic links so that the match is made on the symlink
 5090     # name, not it's expansion (which can change by toggling one of
 5091     # the SYM* options).
 5092 
 5093     selections = []
 5094     for sel in list(UI.DirList.curselection()) + [str(UI.DirList.index(ACTIVE))]:
 5095         selections.append(StripPSEP(UI.DirList.get(sel).split(SYMPTR)[0].split()[-1]))
 5096 
 5097     # Save current scroll positions
 5098 
 5099     xs = UI.hSB.get()
 5100     ys = UI.vSB.get()
 5101 
 5102     # Clean out old listbox contents
 5103 
 5104     UI.DirList.delete(0,END)
 5105 
 5106     # Get and load the new directory information, restoring selections
 5107     # and active marker
 5108 
 5109     try:
 5110         dirlist = BuildDirList(UI.CurrentDir)
 5111         UI.DirList.insert(0, *dirlist)
 5112 
 5113         # Build list of all file and directory names
 5114 
 5115         names = []
 5116         for entry in dirlist:
 5117             names.append(StripPSEP(entry.split(SYMPTR)[0].split()[-1]))
 5118 
 5119         # Get the active entry off the list and convert to an integer index
 5120         
 5121         active = selections.pop()
 5122         try:
 5123             active = names.index(active)
 5124         except:
 5125             active = 0
 5126 
 5127         # Build a list of strings describing selections, discarding
 5128         # references to files/directories that no longer exist
 5129         
 5130         sellist = []
 5131         for name in selections:
 5132             try:
 5133                 sellist.append(str(names.index(name)))
 5134             except:
 5135                 pass
 5136 
 5137         # Restore selection(s)
 5138 
 5139         UI.SetSelection(sellist, active)
 5140 
 5141         # Restore scroll positions
 5142 
 5143         UI.DirList.xview(MOVETO, xs[0])
 5144         UI.DirList.yview(MOVETO, ys[0])
 5145 
 5146 
 5147     # Cannot read current directory - probably media removed.
 5148     # Just revert back to the original starting directory
 5149     # This won't work if the original starting directory is
 5150     # no longer readable - i.e. *It* was removed.
 5151     
 5152     except:
 5153         UI.CurrentDir=STARTDIR
 5154         os.chdir(STARTDIR)
 5155         UI.DirList.insert(0, *BuildDirList(UI.CurrentDir))
 5156         KeySelTop(None, clearsel=True)
 5157 
 5158     # Update titlebar to reflect any changes
 5159     UI.UpdateTitle(UIroot)
 5160 
 5161     # Release the mutex
 5162     UI.DirListMutex.unlock()
 5163 
 5164     return 'break'
 5165 
 5166 # End of 'RefreshDirList()
 5167 
 5168 
 5169 #---------------- Menu Support Functions ------------------#
 5170 
 5171 #####
 5172 # Handle Command Menu Selections
 5173 #####
 5174 
 5175 def CommandMenuSelection(cmdkey):
 5176 
 5177     class event:
 5178         pass
 5179 
 5180     event.state = 0
 5181     event.char  = cmdkey
 5182     KeystrokeHandler(event)    
 5183 
 5184 # End Of 'CommandMenuSelection()'
 5185 
 5186 
 5187 #####
 5188 # Process Options
 5189 #####
 5190 
 5191 def ProcessOptions():
 5192 
 5193     #####
 5194     # Setup The GUI Visual Parameters, Menus, & Help Information
 5195     #####
 5196 
 5197     SetupGUI()
 5198 
 5199     #####
 5200     # Reflect Our Changes In The Interface
 5201     #####
 5202 
 5203     RefreshDirList(None)
 5204 
 5205     #####
 5206     # Dump requested debug information
 5207     #####
 5208     
 5209     # Keyboard Assignments
 5210     if DEBUGLEVEL & DEBUGKEYS:
 5211 
 5212         # Keyboard Bindings
 5213         PrintDebug(dKEYBINDS, FormatMultiColumn(UI.KeyBindings, numcols=1))
 5214         
 5215         # Function Keys (Directory Shortcuts)
 5216         PrintDebug(dFUNCKEYS, GetDirShortcuts())
 5217 
 5218     # User-Defined Variables
 5219     if DEBUGLEVEL & DEBUGSYMS:
 5220         PrintDebug(dSYMTBL, GetUserVbls())
 5221 
 5222     # Command Definitions
 5223     if DEBUGLEVEL & DEBUGCTBL:
 5224         PrintDebug(dCMDTBL, GetCommandTable())        
 5225 
 5226     # Internal Program Variables AndOptions
 5227     if DEBUGLEVEL & DEBUGVARS:
 5228 
 5229         # Internal variabled
 5230         PrintDebug(dINTVAR, GetIntVars())
 5231 
 5232         # User-Settable options
 5233         PrintDebug(dOPTVAR, GetOptions())
 5234 
 5235     # Associations Table
 5236 
 5237     if DEBUGLEVEL & DEBUGASSOC:
 5238         PrintDebug(dASSOC, GetAssocTable())
 5239 
 5240     # If we just wanted debug output, quit now
 5241     if DEBUGLEVEL & DEBUGQUIT:
 5242         sys.exit()
 5243 
 5244 # End of 'ProcessOptions()'
 5245 
 5246 
 5247 #####
 5248 # Add An Entry To The List Of All Directories Visited
 5249 # Do this only if it is not already on the list and
 5250 # observe the MAXMENU variable to keep list length bounded.
 5251 #####
 5252 
 5253 def UpdateDirMenu(newdir):
 5254     global UI
 5255     
 5256     # Win32 collapses case so that 'x' and 'X' refer to the same
 5257     # directory.  We want to preserve this in the user's display
 5258     # but we have to collapse case for the purposes of doing our
 5259     # checks below otherwise the same directory with different
 5260     # capitalization (if the user manually enteres it that way)
 5261     # can appear twice in the Directory Menu
 5262 
 5263     addentry = False
 5264     
 5265     
 5266     if OSPLATFORM == 'win32':
 5267 
 5268         # First make a case-collapsed copy of the existing list
 5269 
 5270         dlc = []
 5271         [dlc.append(d.lower()) for d in UI.AllDirs]
 5272 
 5273         # Now see if our new entry is already there
 5274 
 5275         if newdir.lower() not in dlc:
 5276             addentry = True
 5277 
 5278     elif newdir not in UI.AllDirs:
 5279         addentry = True
 5280 
 5281     # Now add the entry if we decided it was necessary. observing MAXMENU value.
 5282 
 5283     if addentry:
 5284         UpdateMenu(UI.DirBtn, UI.AllDirs, MAXMENU, MAXMENUBUF, LoadDirList, sort=True, newentry=newdir)
 5285 
 5286 # End of 'UpdateDirMenu()'
 5287 
 5288 
 5289 #####
 5290 # Generic Menu Update Routine
 5291 #####
 5292 
 5293 def UpdateMenu(menubtn, datastore, max, maxbuf, func, sort=False, newentry="", fakeevent=False):
 5294 
 5295     # First add the new data, if any, to the specified data storage stucture.
 5296 
 5297     if newentry:
 5298         datastore.append(newentry)
 5299     
 5300     # Now trim it to requested maximum length. 'max' sets how
 5301     # many entries we see in the menu, and 'maxbuf' sets how large
 5302     # the actual storage buffer is allowed to get.
 5303 
 5304     datastore[:] = datastore[-maxbuf:]
 5305 
 5306     # We do not sort the data storage itself if sorting has been requested.
 5307     # We sort the *copy*.  That way we get the 'last _max_ items in
 5308     # sorted order.' (If we sorted the master copy, we would lose
 5309     # track of the order in which things were placed there.)
 5310 
 5311     data = datastore[:]
 5312     if not max:
 5313         data = []
 5314     elif len(data) > max:
 5315         data = datastore[-max:]
 5316 
 5317     # Initialize the menu to empty
 5318 
 5319     menubtn.menu.delete(0,END)
 5320     menubtn.config(state=DISABLED)
 5321     
 5322     if len(data):
 5323         if sort:
 5324             data.sort()
 5325         for entry in data:
 5326             if fakeevent:
 5327                 menubtn.menu.add_command(label=entry, command=lambda item=entry: func(None, item))
 5328             else:
 5329                 menubtn.menu.add_command(label=entry, command=lambda item=entry: func(item))
 5330         menubtn['menu'] = menubtn.menu
 5331 
 5332         # Menu now has content, enable it
 5333         menubtn.config(state=NORMAL)
 5334 
 5335 # End of 'UpdateMenu()'
 5336 
 5337 
 5338 #---------------- Debug Support Functions -----------------#
 5339 
 5340 
 5341 #####
 5342 # Return List Of Association Table Entries
 5343 #####
 5344 
 5345 def GetAssocTable():
 5346 
 5347     debuginfo = []
 5348     for assocname in UI.Associations:
 5349         debuginfo.append(PadString(assocname, dASSOCWIDTH) + str(UI.Associations[assocname]))
 5350 
 5351         debuginfo.sort()
 5352     return debuginfo
 5353 
 5354 # End of 'GetAssocTable()'
 5355 
 5356 
 5357 #####
 5358 # Return List Of Command Table Entries
 5359 #####
 5360 
 5361 def GetCommandTable():
 5362 
 5363     debuginfo = []
 5364     for key in UI.CmdTable.keys():
 5365         name = UI.CmdTable[key][0]
 5366         cmd  = UI.CmdTable[key][1]
 5367         debuginfo.append(PadString(key + "   " + name, dCMDWIDTH) + cmd)
 5368 
 5369         debuginfo.sort()
 5370     return debuginfo
 5371 
 5372 # End of 'GetCommandTable()'
 5373 
 5374 
 5375 #####
 5376 # Return List Of Current Directory Shortcuts
 5377 #####
 5378 
 5379 def GetDirShortcuts():
 5380 
 5381     debuginfo = []
 5382     for x in range(len(UI.DirSCKeys)):
 5383         key = "F" + str(x+1)
 5384         path = UI.DirSCKeys[x]
 5385         if path:
 5386             debuginfo.append(PadString(key, dSCWIDTH) + path)
 5387 
 5388     return debuginfo
 5389 
 5390 # End of 'GetDirShortcuts()'
 5391 
 5392 
 5393 #####
 5394 # Return List Of Internal Variables
 5395 #####
 5396 
 5397 def GetIntVars():
 5398 
 5399     debuginfo = []
 5400     for v in DebugVars:
 5401             debuginfo.append(PadString(v, dINTVARWIDTH) + (str(eval(v)) or dNULL))
 5402 
 5403     debuginfo.sort()
 5404     return debuginfo
 5405 
 5406 # End of 'GetIntVars()'
 5407 
 5408 
 5409 #####
 5410 # Return List Of User-Settable Options
 5411 #####
 5412 
 5413 def GetOptions():
 5414 
 5415     options = {}
 5416     for l,f in ((UI.OptionsBoolean, True), (UI.OptionsNumeric, False), (UI.OptionsString, False)):
 5417         for v in l:
 5418             
 5419             value = eval(v)
 5420             # Translate Booleans into True/False strings
 5421             if f:
 5422                 if value:
 5423                     s = dTRUE
 5424                 else:
 5425                     s = dFALSE
 5426 
 5427             # Translate all others into string representations
 5428             else:
 5429                 s = str(value)
 5430 
 5431             options[v] = s
 5432  
 5433     return FormatMultiColumn(options, numcols=2)
 5434 
 5435 # End of 'GetOptions()'
 5436 
 5437 
 5438 #####
 5439 # Return List Of User-Defined Variables
 5440 #####
 5441 
 5442 def GetUserVbls():
 5443 
 5444     debuginfo = []
 5445     for sym in UI.SymTable.keys():
 5446         debuginfo.append(PadString(sym, dUSRVBLWIDTH) + UI.SymTable[sym])
 5447 
 5448     debuginfo.sort()
 5449     return debuginfo
 5450 
 5451 
 5452 # End of 'GetUserVbls()'
 5453 
 5454 
 5455 #----------------------------------------------------------#
 5456 #                  Program Entry Point                     #
 5457 #----------------------------------------------------------#
 5458 
 5459 #####
 5460 # Create an instance of the UI
 5461 #####
 5462 
 5463 UIroot = Tk()
 5464 UI = twanderUI(UIroot)
 5465 
 5466 # Make the Tk window the topmost in the Z stack.
 5467 # 'Gotta do this or Win32 will not return input
 5468 # focus to our program after a startup warning
 5469 # display.
 5470 
 5471 UIroot.tkraise()
 5472 
 5473 #####
 5474 # Setup global UI variables
 5475 #####
 5476 
 5477 # Setup Built-In Variables
 5478 UI.BuiltIns = {DIR:"", DSELECTION:"", DSELECTIONS:"", HASH:"",
 5479                MEM1:"", MEM2:"", MEM3:"", MEM4:"", MEM5:"", MEM6:"",
 5480                MEM7:"", MEM8:"", MEM9:"", MEM10:"", MEM11:"", MEM12:"", 
 5481                PROMPT:"", SELECTION:"", SELECTIONS:"", YESNO:""}
 5482 
 5483 # Options (and their default values) which can be set in the configuration file
 5484 
 5485 UI.OptionsBoolean = {"ACTUALLENGTH":ACTUALLENGTH,
 5486                      "ADAPTREFRESH":ADAPTREFRESH,
 5487                      "AFTERCLEAR":AFTERCLEAR,
 5488                      "AUTOREFRESH":AUTOREFRESH,
 5489                      "CMDMENUSORT":CMDMENUSORT,
 5490                      "FORCEUNIXPATH":FORCEUNIXPATH,
 5491                      "HIDEDOTFILES":HIDEDOTFILES,
 5492                      "INVERTFILTER":INVERTFILTER,
 5493                      "ISODATE":ISODATE,
 5494                      "NODETAILS":NODETAILS,
 5495                      "NONAVIGATE":NONAVIGATE,
 5496                      "SORTREVERSE":SORTREVERSE,
 5497                      "SORTSEPARATE":SORTSEPARATE,
 5498                      "SYMDIR":SYMDIR,
 5499                      "SYMEXPAND":SYMEXPAND,
 5500                      "SYMRESOLV":SYMRESOLV,
 5501                      "USETHREADS":USETHREADS,
 5502                      "USEWIN32ALL":USEWIN32ALL,
 5503                      "WARN":WARN,
 5504                      "WILDNOCASE":WILDNOCASE,
 5505                      }
 5506 
 5507 UI.OptionsNumeric = {"AFTERWAIT":AFTERWAIT,"DEBUGLEVEL":DEBUGLEVEL, "FSZ":FSZ, "MFSZ":MFSZ, "HFSZ":HFSZ,
 5508                      "HEIGHT":HEIGHT, "MAXMENU":MAXMENU, "MAXMENUBUF":MAXMENUBUF, "MAXNESTING":MAXNESTING,
 5509                      "REFRESHINT":REFRESHINT, "SCALEPRECISION":SCALEPRECISION, "STARTX":STARTX, "STARTY":STARTY, "WIDTH":WIDTH}
 5510 
 5511 UI.OptionsString  = {"BCOLOR":BCOLOR,   "FCOLOR":FCOLOR,   "FNAME":FNAME,   "FWT":FWT,    # Main Font/Colors
 5512                      "MBCOLOR":MBCOLOR, "MFCOLOR":MFCOLOR, "MFNAME":MFNAME, "MFWT":MFWT,  # Menu Font/Colors
 5513                      "HBCOLOR":HBCOLOR, "HFCOLOR":HFCOLOR, "HFNAME":HFNAME, "HFWT":HFWT,  # Help Font/Colors
 5514                      "MBARCOL":MBARCOL, "QUOTECHAR":QUOTECHAR, "SORTBYFIELD":SORTBYFIELD, # Other
 5515                      "STARTDIR":STARTDIR, "CMDSHELL":CMDSHELL, "DEFAULTSEP":DEFAULTSEP, "DOTFILE":DOTFILE} 
 5516 
 5517 # Prepare storage for key bindings
 5518 UI.KeyBindings = {}
 5519 
 5520 
 5521 # Storage For Associations
 5522 
 5523 UI.Associations = {ASSOCEXCL:[]}
 5524 
 5525 # Initialize list of all directories visited
 5526 UI.AllDirs    = []
 5527 
 5528 # Initialize directory stack
 5529 UI.LastDir    = []
 5530 
 5531 # Initialize storage for last manually entered directory path
 5532 UI.LastPathEntered = ""
 5533 
 5534 # Initialize storage for last manually entered Filter and Selection wildcards
 5535 UI.LastFiltWildcard = ""
 5536 UI.LastSelWildcard = ""
 5537 
 5538 # Initialize storage for current location
 5539 UI.CurrentDir = ""
 5540 
 5541 # Initialize storage for filtering wildcard
 5542 UI.FilterWildcard = ("None", None, None, False)
 5543 
 5544 # Initialize various menu data structures
 5545 ClearHistory(None)
 5546 
 5547 # Initialize Storage For Program Memories
 5548 
 5549 UI.ProgMem = [[], [], [], [], [], [], [], [], [], [], [], []]
 5550 
 5551 # Need mutex to serialize on widget updates
 5552 UI.DirListMutex = mutex.mutex()
 5553 
 5554 # Intialize the "new dir via mouse" flag
 5555 UI.MouseNewDir = False
 5556 
 5557 # Initialize the refresh timers
 5558 UI.LastRefresh    = 0
 5559 
 5560 # Initialize storage for list of configuration files processed
 5561 UI.ConfigVisited = []
 5562 
 5563 # Start in detailed mode
 5564 UI.SetDetailedView(True)
 5565 
 5566 
 5567 #####
 5568 # Command line processing - Options are set with the
 5569 # following priority scheme (lowest to highest):
 5570 #
 5571 #    1) Defaults coded into the program
 5572 #    2) Options set in the configuration file
 5573 #    3) Options set in the environment variable
 5574 #    4) Options set on the command line
 5575 #
 5576 #####
 5577 
 5578 # Concatenate any environment variable with the
 5579 # command line so the command line takes precedence.
 5580 
 5581 OPTIONS = sys.argv[1:]
 5582 envopt = os.getenv(PROGNAME.upper())
 5583 if envopt:
 5584     OPTIONS = envopt.split() + OPTIONS
 5585 
 5586 try:
 5587     opts, args = getopt.getopt(OPTIONS, '-c:d:hqrtv')
 5588 except getopt.GetoptError:
 5589     Usage()
 5590     sys.exit(1)
 5591 
 5592 # If the user wants help or version information, do this first
 5593 # so we don't bother with other options processing
 5594 
 5595 for opt, val in opts:
 5596     if opt == "-h":
 5597         Usage()
 5598         sys.exit(0)
 5599     if opt == "-v":
 5600         print RCSID
 5601         sys.exit(0)
 5602 
 5603 # Read configuration file before any other arguments.  This allows the
 5604 # environment variable and then the command line to override any
 5605 # settings in the configuration file.
 5606 
 5607 for opt, val in opts:
 5608     if opt == "-c":
 5609         CONF = os.path.abspath(val)
 5610 
 5611 # Parse the configuration file, but suppress options
 5612 # processing - on startup this is done just before
 5613 # we enter the main message loop to make sure we
 5614 # pickup any options changes from the environment
 5615 # variable or command line.
 5616 
 5617 ProcessConfiguration(None, DoOptionsProcessing=False)
 5618 
 5619 # Process the rest of the options, if any
 5620 
 5621 for opt, val in opts:
 5622     if opt == "-d":
 5623         DEBUGLEVEL = val
 5624         ValidateDebugLevel()
 5625 
 5626         # If we're going to be dumping debug info, print header
 5627         if DEBUGLEVEL:
 5628             print dHEADER % time.asctime()
 5629 
 5630     if opt == "-q":
 5631         WARN = False
 5632     if opt == "-r":
 5633         AUTOREFRESH = False
 5634     if opt == "-t":
 5635         UI.OptionsString["QUOTECHAR"] = QUOTECHAR = ""
 5636     if opt == "-x":
 5637         WIDTH = val
 5638     if opt == "-y":
 5639         HEIGHT = val
 5640 
 5641 # Figure out where to start - Environment/Command Line overrides config
 5642 # file STARTDIR option.  Program can only have 0 or 1 arguments.  Make
 5643 # sure any startdir argument is legit
 5644 
 5645 if len(args) > 1:
 5646     ErrMsg(eTOOMANY)
 5647     sys.exit(1)
 5648 
 5649 if len(args) == 1:
 5650     STARTDIR = args[0]
 5651 
 5652     # Windows is sloppy about accepting both '//' and '\\'
 5653     # so we have to condition the command line input for consistency.
 5654 
 5655     if OSPLATFORM == 'win32' and STARTDIR == '//':
 5656         STARTDIR = SHOWDRIVES
 5657 
 5658     # Make sure any user request to start in a Drive List View
 5659     # is possible.  If not, just start in the root directory.
 5660     
 5661     if STARTDIR == SHOWDRIVES and (OSPLATFORM != 'win32' or not GetWin32Drives()):
 5662         STARTDIR = PSEP
 5663         
 5664     if not os.path.isdir(STARTDIR):
 5665         ErrMsg(eBADROOT % STARTDIR)
 5666         sys.exit(1)
 5667 
 5668     # Save this value as the default for STARTDIR
 5669     UI.OptionsString["STARTDIR"] = STARTDIR
 5670 
 5671 # Get starting directory into canonical form
 5672 STARTDIR = os.path.abspath(STARTDIR)
 5673 
 5674 # Initialize the UI directory listing
 5675 LoadDirList(STARTDIR, updtitle = False)
 5676 
 5677 # Process options to catch any changes detected in
 5678 # environment variable or command line
 5679 
 5680 ProcessOptions()
 5681 
 5682 # And start the periodic polling of the widget
 5683 UI.poll()
 5684 
 5685 # Run the program interface
 5686 UIroot.mainloop()
 5687