#!/usr/bin/env python # twander - Wander around the file system # Copyright (c) 2002-2004 TundraWare Inc. All Rights Reserved. # For Updates See: http://www.tundraware.com/Software/twander # Program Information PROGNAME = "twander" RCSID = "$Id: twander.py,v 3.139 2004/03/10 23:58:17 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information DATE = "2002-2004" CPRT = chr(169) OWNER = "TundraWare Inc." RIGHTS = "All Rights Reserved." COPYRIGHT = "Copyright %s %s %s %s" % (CPRT, DATE, OWNER, RIGHTS) #----------------------------------------------------------# # Imports # #----------------------------------------------------------# import getopt import mutex import os import re from socket import getfqdn from stat import * import sys import thread import time from Tkinter import * from tkMessageBox import askyesno, showerror, showinfo, showwarning from tkSimpleDialog import askstring ##### # Imports conditional on OS ##### # Set OS type - this allows us to trigger OS-specific code # where needed. OSNAME = os.name # If we're on Win32, try to load win32all stuff if possible WIN32ALL = FALSE if OSNAME == 'nt': try: from win32api import GetLogicalDriveStrings as GetDrives from win32api import GetUserName, GetFileAttributes, GetComputerName, GetDiskFreeSpace, GetVolumeInformation from win32file import GetDriveType from win32wnet import WNetGetUniversalName import win32con from win32security import * WIN32HOST = GetComputerName() WIN32ALL = TRUE except: WIN32ALL = FALSE # Get unix password and group features if OSNAME == 'posix': import grp import pwd #----------------------------------------------------------# # Variables User Might Change # #----------------------------------------------------------# ##### # Default Key Assignments ##### # General Program Commands CLRHIST = '<Control-y>' # Clear Command History FONTDECR = '<Control-bracketleft>' # Decrease Font Size FONTINCR = '<Control-bracketright>' # Increase Font Size MOUSECTX = '<ButtonRelease-3>' # Pop-up Command Menu MOUSEDIR = '<Shift-ButtonRelease-3>' # Pop-up Directory Menu KEYPRESS = '<KeyPress>' # Any keypress (for commands) QUITPROG = '<Control-q>' # Quit the program READCONF = '<Control-r>' # Re-read the configuration file REFRESH = '<Control-l>' # Refresh screen TOGAUTO = '<Control-o>' # Toggle autorefreshing TOGDETAIL = '<Control-t>' # Toggle detail view TOGWIN32ALL = '<Control-w>' # Toggle win32all features, if available # Directory Navigation CHANGEDIR = '<Control-x>' # Enter a new path DIRHOME = '<Control-h>' # Goto $HOME DIRBACK = '<Control-b>' # Goto previous directory DIRROOT = '<Control-j>' # Goto root directory DIRSTART = '<Control-s>' # Goto starting directory DIRUP = '<Control-u>' # Go up one directory level DRIVELIST = '<Control-k>' # On Win32, display Drive List View if possible MOUSEBACK = '<Control-Double-ButtonRelease-1>' # Go back one directory with mouse MOUSEUP = '<Control-Double-ButtonRelease-3>' # Go up one directory with mouse # Selection Keys SELALL = '<Control-comma>' # Select all items SELINV = '<Control-i>' # Invert the current selection SELNONE = '<Control-period>' # Unselect all items SELNEXT = '<Control-n>' # Select next item SELPREV = '<Control-p>' # Select previous item SELEND = '<Control-e>' # Select bottom item SELTOP = '<Control-a>' # Select top item SELWILD = '<Control-backslash>' # Select using wildcards # Scrolling Commands PGDN = '<Control-v>' # Move page down PGUP = '<Control-c>' # Move page up PGRT = '<Control-g>' # Move page right PGLFT = '<Control-f>' # Move page left # Execute Commands RUNCMD = '<Control-z>' # Run arbitrary user command SELKEY = '<Return>' # Select item w/keyboard MOUSESEL = '<Double-ButtonRelease-1>' # Select item w/mouse # Directory Shortcuts KDIRSC1 = '<F1>' KDIRSC2 = '<F2>' KDIRSC3 = '<F3>' KDIRSC4 = '<F4>' KDIRSC5 = '<F5>' KDIRSC6 = '<F6>' KDIRSC7 = '<F7>' KDIRSC8 = '<F8>' KDIRSC9 = '<F9>' KDIRSC10 = '<F10>' KDIRSC11 = '<F11>' KDIRSC12 = '<F12>' # Program Memories MEMCLR1 = '<Control-F1>' MEMCLR2 = '<Control-F2>' MEMCLR3 = '<Control-F3>' MEMCLR4 = '<Control-F4>' MEMCLR5 = '<Control-F5>' MEMCLR6 = '<Control-F6>' MEMCLR7 = '<Control-F7>' MEMCLR8 = '<Control-F8>' MEMCLR9 = '<Control-F9>' MEMCLR10 = '<Control-F10>' MEMCLR11 = '<Control-F11>' MEMCLR12 = '<Control-F12>' MEMCLRALL = '<Control-m>' # Clear all memories MEMSET1 = '<Alt-F1>' # Set program memories MEMSET2 = '<Alt-F2>' MEMSET3 = '<Alt-F3>' MEMSET4 = '<Alt-F4>' MEMSET5 = '<Alt-F5>' MEMSET6 = '<Alt-F6>' MEMSET7 = '<Alt-F7>' MEMSET8 = '<Alt-F8>' MEMSET9 = '<Alt-F9>' MEMSET10 = '<Alt-F10>' MEMSET11 = '<Alt-F11>' MEMSET12 = '<Alt-F12>' # Sorting Keys SORTBYNONE = '<Shift-F10>' # Select sorting parameters SORTBYPERMS = '<Shift-F1>' SORTBYLINKS = '<Shift-F2>' SORTBYOWNER = '<Shift-F3>' SORTBYGROUP = '<Shift-F4>' SORTBYLENGTH = '<Shift-F5>' SORTBYTIME = '<Shift-F6>' SORTBYNAME = '<Shift-F7>' SORTREV = '<Shift-F11>' SORTSEP = '<Shift-F12>' ##### # GUI Defaults ##### ##### # Initial Size And Postition In Pixels ##### HEIGHT = 600 WIDTH = 800 STARTX = 0 STARTY = 0 ##### # Default Colors ##### # Main Display Colors BCOLOR = "black" FCOLOR = "green" # Menu Colors MBARCOL = "beige" MBCOLOR = "beige" MFCOLOR = "black" # Help Screen Colors HBCOLOR = "lightgreen" HFCOLOR = "black" ##### # Default Display Fonts ##### # Main Display Font FNAME = "Courier" FSZ = 12 FWT = "bold" # Menu Font MFNAME = "Courier" MFSZ = 12 MFWT = "bold" # Help Screen Font HFNAME = "Courier" HFSZ = 10 HFWT = "italic" #------------------- Nothing Below Here Should Need Changing ------------------# #----------------------------------------------------------# # Constants & Literals # #----------------------------------------------------------# ##### # Booleans ##### # Don't need to define TRUE & FALSE - they are defined in the Tkinter module ##### # Symbolic Constants Needed Below ##### ##### # Defaults ##### AUTOREFRESH = TRUE # Automatically refresh the directory display? CMDSHELL = "" # No CMDSHELL processing DEBUGLEVEL = 0 # No debug output MAXMENU = 32 # Maximum length of displayed menu MAXMENUBUF = 250 # Maximum size of internal menu buffer MAXNESTING = 32 # Maximum depth of nested variable definitions NODETAILS = FALSE # TRUE means details can never be displayed NONAVIGATE = FALSE # TRUE means that all directory navigation is prevented REFRESHINT = 5000 # Interval (ms) for automatic refresh QUOTECHAR = '\"' # Character to use when quoting Built-In Variables SORTBYFIELD = "Name" # Field to use as sort key SORTREVERSE = FALSE # Reverse specified sort order? SORTSEPARATE = TRUE # Separate Directories and Files in sorted displays? USETHREADS = FALSE # Use threads on Unix? USEWIN32ALL = TRUE # Use win32all features if available? WARN = TRUE # Warnings on? WIN32ALLON = TRUE # Flag for toggling win32all features while running ##### # Constants ##### # General Constants CMDESCAPE = '"' # Character to force literal dialog processing CMDSHELLESC = CMDESCAPE # Disable CMDSHELL processing for a manual command entry KB = 1024 # 1 KB constant MB = KB * KB # 1 MB constant GB = MB * KB # 1 GB constant NUMFUNCKEY = 12 # Number of function keys NUMPROGMEM = 12 # Number of program memories POLLINT = 20 # Interval (ms) the poll routine should run PSEP = os.sep # Character separating path components SHOWDRIVES = '\\\\' # Logical directory name for Win32 Drive Lists STRICTMATCH = CMDESCAPE # Tells wildcard system to enforce strict matching # Sort Field Names In Normal View fNONE = "No Sort" fPERMISSIONS = "Permissions" fLINKS = "Links" fOWNER = "Owner" fGROUP = "Group" fLENGTH = "Length" fDATE = "Time" fNAME = "Name" # Sort Field Names In Drive List View dlvNONE = "No Sort" dlvLABEL = "Label/Share" dlvTYPE = "Drive Type" dlvFREE = "Free Space" dlvTOTAL = "Total Space" dlvLETTER = "Drive Letter" # 'Fake' sorting field used to set SORTREVERSE and SORTSEPARATE fREVERSE = "Reverse" fSEPARATE = "Separate" # Build a dictionary whose keys are the names of all the legal sort fields # and whose entries are tuples in the form: # # (field #, # Name Of Sort Option In Normal View, # Name Of Sort Option In Drive List View) # # Placing a None value in either of the last two tuple entries causes the # keystroke combination associated with that sort option to be ignored AND # prevents that option from appearing in the Sort Menu. # # Note that fNONE indicates that no sorting is to be done, so there # is no real associated sortkey field. Name2Key = {} index = -1 for x, y in [(fNONE, dlvNONE), (fPERMISSIONS, dlvLABEL), (fLINKS, dlvTYPE), (fOWNER, dlvFREE), (fGROUP, dlvTOTAL), (fLENGTH, dlvLETTER), (fDATE, None), (fNAME, None), (fREVERSE, fREVERSE), (fSEPARATE, None)]: Name2Key[x.lower()] = (index, x, y) index += 1 # Highest key index needed by Drive List View MAXDLVKEY = 4 ##### # System-Related Defaults ##### # Default startup directory STARTDIR = os.path.abspath("." + os.sep) # Home directory HOME = os.getenv("HOME") or STARTDIR # Get hostname # Try the local environment first when looking for the # hostname. Only use the socket call as a last # resort because a misconfigured or malfunctioning # network will cause this call to be *very* slow # and 'twander' will take forever to start-up. HOSTNAME = os.getenv("HOSTNAME") or getfqdn() # Get the user name if OSNAME == 'nt': if WIN32ALL and USEWIN32ALL and WIN32ALLON: USERNAME = GetUserName() else: USERNAME = os.getenv("LOGNAME") elif OSNAME == 'posix': USERNAME = os.getenv("USER") else: USERNAME = "" # Concatenate them if we got a user name if USERNAME: FULLNAME = "%s@%s" % (USERNAME, HOSTNAME) else: FULLNAME = HOSTNAME ##### # GUI-Related Stuff ##### # Constants CMDMENU_WIDTH = 16 ROOTBORDER = 1 MENUBORDER = 2 MENUPADX = 2 MENUOFFSET = ROOTBORDER + MENUBORDER + MENUPADX # Key & Button Event Masks ShiftMask = (1<<0) LockMask = (1<<1) ControlMask = (1<<2) Mod1Mask = (1<<3) Mod2Mask = (1<<4) Mod3Mask = (1<<5) Mod4Mask = (1<<6) Mod5Mask = (1<<7) Button1Mask = (1<<8) Button2Mask = (1<<9) Button3Mask = (1<<10) Button4Mask = (1<<11) Button5Mask = (1<<12) # There are some event bits we don't care about - # We'll use the following constant to mask them out # later in the keyboard and mouse handlers. DontCareMask = LockMask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask # Some things are OS-dependent if OSNAME == 'nt': AltMask = (1<<17) DontCareMask |= Mod1Mask # Some versions of Win32 set this when Alt is pressed else: AltMask = Mod1Mask # Name The Key/Mouse Assignments Which We Do Not Allow To Be Rebound In The Config File NOREBIND = ["MOUSECTX", "MOUSEDIR", "MOUSEBACK", "MOUSEUP", "MOUSESEL"] ##### # Stat-Related Stuff ##### # Misc. Stat-Related Strings FILEGROUP = "group" FILEOWNER = "owner" NODRIVE = "<Drive Empty>" NOLABEL = "<No Label>" SYMPTR = " -> " UNAVAILABLE = "Unavailable" WIN32GROUP = "win32" + FILEGROUP WIN32OWNER = "win32" + FILEOWNER WIN32FREE = " free" WIN32TOTAL = " total " # Leave trailing space - drive letter follows if WIN32ALL: # Used with win32all-based permissions logic. # Format for each entry is: (mask to test, symbol if true). # Position in tuple determines position in display. win32all_mode = ((win32con.FILE_ATTRIBUTE_DIRECTORY, 'd'), (win32con.FILE_ATTRIBUTE_ARCHIVE, 'A'), (win32con.FILE_ATTRIBUTE_COMPRESSED, 'C'), (win32con.FILE_ATTRIBUTE_HIDDEN, 'H'), (win32con.FILE_ATTRIBUTE_NORMAL, 'N'), (win32con.FILE_ATTRIBUTE_READONLY, 'R'), (win32con.FILE_ATTRIBUTE_SYSTEM, 'S')) win32all_type = ((win32con.DRIVE_CDROM, 'CD/DVD'), (win32con.DRIVE_FIXED, 'Fixed'), (win32con.DRIVE_RAMDISK, 'Ramdisk'), (win32con.DRIVE_REMOTE, 'Remote'), (win32con.DRIVE_REMOVABLE, 'Removable')) # Size of each of the drive detail fields, including room for trailing space. SZ_DRIVE_SHARE = 24 # Can be label or share string - leave plenty of room SZ_DRIVE_TYPE = 20 SZ_DRIVE_FREE = 13 # The trailing text string has as leading blank for separation SZ_DRIVE_TTL = 13 # The trailing text string has leading and trailing blanks for separation SZ_DRIVE_TOTAL = SZ_DRIVE_SHARE + SZ_DRIVE_TYPE + SZ_DRIVE_FREE + len(WIN32FREE) + \ SZ_DRIVE_TTL + len(WIN32TOTAL) # Constants used with the more usual Unix-style details # Used both by Unix and Win32 when win32all is not present or disabled. # Permissions ST_PERMIT = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"] # Special file type lookup ST_SPECIALS = {"01":"p", "02":"c", "04":"d", "06":"b", "10":"-", "12":"l", "14":"s"} # Size of each status display field including trailing space # These are sized for the worst-case: Win32 with win32all features ST_SZMODE = 12 ST_SZNLINK = 5 ST_SZUNAME = 18 ST_SZGNAME = 18 ST_SZLEN = 12 ST_SZMTIME = 18 ST_SZTOTAL = ST_SZMODE + ST_SZNLINK + ST_SZUNAME + ST_SZGNAME + \ ST_SZLEN + ST_SZMTIME # Special Bit Masks STICKY_MASK = 1 SETGID_MASK = 2 SETUID_MASK = 4 ##### # Configuration File-Related Constants & Globals ##### ASSIGN = "=" # Assignment for variable definitions CONF = "" # Config file user selected with -c option COMMENT = r"#" # Comment introducer string ENVVBL = r'$' # Symbol denoting an environment variable # Names Of Conditionals, Directives, And Pre-Defined Symbols CONDENDIF = '.endif' CONDEQUAL = '==' CONDIF = '.if' CONDNOTEQUAL = '!=' DIRECTINC = '.include' SYMOS = '.OS' SYMPLATFORM = '.PLATFORM' # Globals Supporting Configutration File Conditional Processing ConditionalStack = [] # Stack for tracking conditional state # Variable Name Pattern Matching Stuff DIRSC = "DIRSC" # Directory Shortcut naming WILDCARD = "WILDCARD" # Wildcard naming reDIRSC = r'^' + DIRSC + r'([1-9]|1[0-2])$' # Regex describing Directory Shortcut names reVAR = r"\[.+?\]" # Regex describing variable notation # Create actual regex matching engines REDIRSC = re.compile(reDIRSC) REVAR = re.compile(reVAR) CONDVAR = re.compile(r'^' + reVAR + r'$') # Built-In Variables DIR = r'[DIR]' DSELECTION = r'[DSELECTION]' DSELECTIONS = r'[DSELECTIONS]' HASH = r'[HASH]' MEM1 = r'[MEM1]' MEM2 = r'[MEM2]' MEM3 = r'[MEM3]' MEM4 = r'[MEM4]' MEM5 = r'[MEM5]' MEM6 = r'[MEM6]' MEM7 = r'[MEM7]' MEM8 = r'[MEM8]' MEM9 = r'[MEM9]' MEM10 = r'[MEM10]' MEM11 = r'[MEM11]' MEM12 = r'[MEM12]' PROMPT = r'[PROMPT:' SELECTION = r'[SELECTION]' SELECTIONS = r'[SELECTIONS]' YESNO = r'[YESNO:' #----------------------------------------------------------# # Prompts, & Application Strings # #----------------------------------------------------------# ##### # Menu, Error, Information, & Warning Messages ##### # Title-Bar Strings TTLAUTO = "Auto:" TTLFILES = "Total Files:" TTLSIZE = "Total Size:" TTLSORTFLD = "Sort By:" TTLSORTREV = "Rev:" TTLSORTSEP = "Sep:" # Convert Logical Values Into Yes/No String YesOrNo = {TRUE:"YES", FALSE:"N0"} # Menu Button Titles COMMANDMENU = 'Commands' # Title for Command Menu button DIRMENU = 'Directories' # Title for Directory Menu button HISTMENU = 'History' # Title for History Menu button SORTMENU = 'Sorting' # Title for Sort Menu button WILDMENU = 'Wildcards' # Title for Wildcard Menu button HELPMENU = 'Help' # Title for Help Menu button # Sort Menu-Related # And their names - used in Sorting Menu sSORTBY = "Sort By" sREVERSE = "Reverse Sort" sSEPARATE = "Separate Dirs/Files" # Help Menu-Related WEBSITE = "Homepage:\n\nhttp://www.tundraware.com/Software/twander" ABOUT = "%s %s\n\nCopyright %s %s\n%s\n\n%s\n\n%s" % (PROGNAME, VERSION, CPRT, DATE, OWNER, RIGHTS, WEBSITE) hABOUT = 'About' hCOMMANDS = 'Command Definitions' hDIRSC = 'Directory Shortcuts' hINTVBLS = 'Internal Program Variables' hKEYS = 'Keyboard Assignments' hNONE = 'No %s Found.' hOPTVBLS = 'User-Settable Options' hUSERVBLS = 'User-Defined Variables' # Errors eBADROOT = " %s Is Not A Directory" eDIRRD = "Cannot Open Directory : %s --- Check Permissions." eERROR = "ERROR" eINITDIRBAD = "Cannot Open Starting Directory : %s - Check Permissions - ABORTING!." eOPEN = "Cannot Open File: %s" eTOOMANY = "You Can Only Specify One Starting Directory." # Information iNOSTAT = "Details Unavailable For This File ->" # Prompts pCHPATH = "Change Path" pENCMD = "Enter Command To Run:" pENPATH = "Enter New Path Desired:" pENWILD = "Enter Selection Wildcard:" pMANUALCMD = "Manual Command Entry" pRUNCMD = "Run Command" pWILDSEL = "Selection By Wildcard" # Warnings wBADCFGLINE = "Ignoring Line %s.\nBogus Configuration Entry:\n\n%s" wBADDEBUG = "Ignoring Bogus Debug Level! - %s Is Not In Integer Or Hex Format." wBADENDIF = "Ignoring Line %s!\nBogus End-Of-Block Statement:\n\n%s" wBADENVVBL = "Ignoring Line %s.\nEnvironment Variable %s Not Set:\n\n%s" wBADEXE = "Could Not Execute Command:\n\n%s" wBADIF = "Ignoring Line %s.\nImproperly Formed Condition: \n\n%s" wBADRHS = "Ignoring Line %s.\nOption Assignment Has Bad Righthand Side:\n\n%s" wBADSCNUM = "Ignoring Line %s.\nShortcut Number Must Be From 1-12:\n\n%s" wBADSORTFLD = "Don't Know How To Sort By: %s\n\nWill Sort By %s Instead." wCONFOPEN = "Cannot Open Configuration File:\n%s" wEXTRAENDIF = "Ignoring Line %s!\nNo Conditional Block To End:\n\n%s" wLINKBACK = "%s Points Back To Own Directory" wMISSENDIF = "Configuration File Is Missing %s " + CONDENDIF +" Statement(s)" wNOCMDS = "Running With No Commands Defined!" wNOREBIND = "Ignoring Line %s.\nCannot Rebind This Keyboard Or Mouse Button Combination:\n\n%s" wREDEFVAR = "Ignoring Line %s.\nCannot Redefine Built-In Variable %s:\n\n%s" wUNDEFVBL = "Ignoring Line %s.\nUndefined Variable %s Referenced:\n\n%s" wVBLTOODEEP = "Ignoring Line %s.\nVariable Definition Nested Too Deeply:\n\n%s" wWARN = "WARNING" wWILDCOMP = "Cannot Compile Wilcard Expression: %s" ##### # Debug-Related Stuff ##### # Debug Levels # Nibble #1 DEBUGVARS = (1<<0) # Dump internal variables DEBUGSYMS = (1<<1) # Dump symbol table DEBUGCTBL = (1<<2) # Dump command table DEBUGKEYS = (1<<3) # Dump key bindings # Nibble #2 DEBUGCMDS = (1<<4) # Dump command execution string DEBUGDIRS = (1<<5) # Dump directory stack contents as it changes DEBUGHIST = (1<<6) # Dump contents of command history stack after command execution DEBUGMEM = (1<<7) # Dump contents of program memories as they change # Nibble #3 DEBUGWILD = (1<<8) # Dump contents of wildcard stack as it changes DEBUGRSRV2 = (1<<9) # Reserved for future use DEBUGRSRV3 = (1<<10) DEBUGQUIT = (1<<11) # Dump debug info and quit program # Debug Strings dCMD = "COMMAND" dCMDTBL = hCOMMANDS dDIRSTK = "DIRECTORY STACK" dFALSE = "False" dFUNCKEYS = hDIRSC dHEADER = "twander Debug Dump Run On: %s\n" dHIST = "COMMAND HISTORY STACK" dINTVAR = hINTVBLS dKEYBINDS = hKEYS dMEM = "CONTENTS OF MEMORY %s" dMEMALL = "CONTENTS OF ALL PROGRAM MEMORIES" dNULL = "None" dOPTVAR = hOPTVBLS dSYMTBL = hUSERVBLS dTRUE = "True" dWILDSTK = "WILDCARD STACK" # Debug Formatting dCMDWIDTH = 20 dINTVARWIDTH = 12 dKEYWIDTH = 16 dOPTIONWIDTH = 16 dSCWIDTH = 6 dTWOUPWIDTH = 50 dUSRVBLWIDTH = 20 # List of internal program variables to dump during debug sessions DebugVars = ["RCSID", "OSNAME", "HOSTNAME", "USERNAME", "OPTIONS", "CONF", "HOME", "PSEP", "POLLINT"] ##### # Usage Information ##### uTable = [PROGNAME + " " + VERSION + " - %s\n" % COPYRIGHT, "usage: " + PROGNAME + " [-cdhqrstvwxy] [startdir] where,\n", " startdir name of directory in which to begin (default: current dir)", " -c file name of configuration file (default: $HOME/." + PROGNAME + " or PROGDIR/." + PROGNAME + ")", " -d level set debugging level (default: 0, debugging off)", " Bit Assignments:", " 0 - Dump Internal Options & User-Settable Options (0x001)", " 1 - Dump User-Defined Variables (0x002)", " 2 - Dump Command Definitions (0x004)", " 3 - Dump Key Bindings (0x008)", " 4 - Display, Do Not Execute, Commands When Invoked (0x010)", " 5 - Dump Directory Stack As It Changes (0x020)", " 6 - Dump Command History Stack After Command Executes (0x040)", " 7 - Dump Contents Of Program Memories As They Change (0x080)", " 8 - Dump Contents Of Wildcard Stack As It Changes (0x100)", " 9 - Reserved", " 10 - Reserved", " 11 - Dump Requested Debug Information And Exit Immediately (0x800)", " -h print this help information", " -q quiet mode - no warnings (default: warnings on)", " -r turn off automatic content refreshing (default: refresh on)", " -t no quoting when substituting Built-In Variables (default: quoting on)", " -v print detailed version information", ] #---------------------------Code Begins Here----------------------------------# #----------------------------------------------------------# # General Support Functions # #----------------------------------------------------------# ##### # Print An Error Message ##### def ErrMsg(emsg): showerror(PROGNAME + " " + VERSION + " " + eERROR, emsg) # End of 'ErrMsg()' ##### # Build List Of Win32 Drives ##### def GetWin32Drives(): # Get Win32 drive string, split on nulls, and get # rid of any resulting null entries. if WIN32ALL and USEWIN32ALL and WIN32ALLON: return filter(lambda x : x, GetDrives().split('\x00')) else: return "" # End of 'GetWin32Drives()' ##### # Convert A File Size Into Equivalent String With Scaling # Files under 1 MB show actual length # Files < 1 MB < 1 GB shown in KB # Files 1 GB or greater, shown in MB ##### def FileLength(flen): if flen >= GB: flen = str(flen/MB) + "m" elif flen >= MB: flen = str(flen/KB) + "k" else: flen = str(flen) return flen # End of 'FileLength()' ##### # Pad A String With Trailing Spaces To Specified Width # Return either that padded string or, if the passed # string is too large, truncate it and return a string # of exactly the specified with whose last character is # a space. ##### def PadString(string, width, Rjust=FALSE): s = string[:(width-1)] if Rjust: s = s.rjust(width) else: s = s.ljust(width) return s # End of 'PadString()' ##### # Process The Configuraton File # This is called once at program start time # and again any time someone hits the READCONF key # while the program is running. ##### def ProcessConfiguration(event, DoOptionsProcessing=TRUE): global CONF, UI, ConditionalStack # Cleanout any old configuration data UI.CmdTable = {} UI.Commands = [] UI.DirSCKeys = ["", "", "", "", "", "", "", "", "", "", "", ""] UI.SymTable = {} # Initialize internal parsing data structures ConditionalStack = [TRUE,] # This is a sentinel and guarantees there will # will always be something in this stack # Load Symbol Table with predefined symbols UI.SymTable[SYMOS] = os.name UI.SymTable[SYMPLATFORM] = sys.platform # Unbind all existing key bindings for x in UI.KeyBindings.keys(): UI.DirList.unbind(UI.KeyBindings[x]) # Initialize keyboard binding variables to their defaults # These may be overriden in the configuration file # parsing process. UI.KeyBindings = {"CLRHIST":CLRHIST, "FONTDECR":FONTDECR, "FONTINCR":FONTINCR, "MOUSECTX":MOUSECTX, "MOUSEDIR":MOUSEDIR, "KEYPRESS":KEYPRESS, "QUITPROG":QUITPROG, "READCONF":READCONF, "REFRESH":REFRESH, "TOGAUTO":TOGAUTO, "TOGDETAIL":TOGDETAIL, "TOGWIN32ALL":TOGWIN32ALL, "CHANGEDIR":CHANGEDIR, "DIRHOME":DIRHOME, "DIRBACK":DIRBACK, "DIRROOT":DIRROOT, "DIRSTART":DIRSTART, "DIRUP":DIRUP, "DRIVELIST":DRIVELIST, "MOUSEBACK":MOUSEBACK, "MOUSEUP":MOUSEUP, "SELALL":SELALL, "SELINV":SELINV, "SELNONE":SELNONE, "SELNEXT":SELNEXT, "SELPREV":SELPREV, "SELEND":SELEND, "SELTOP":SELTOP, "SELWILD":SELWILD, "PGDN":PGDN, "PGUP":PGUP, "PGRT":PGRT, "PGLFT":PGLFT, "RUNCMD":RUNCMD, "SELKEY":SELKEY, "MOUSESEL":MOUSESEL, "KDIRSC1":KDIRSC1, "KDIRSC2":KDIRSC2, "KDIRSC3":KDIRSC3, "KDIRSC4":KDIRSC4, "KDIRSC5":KDIRSC5, "KDIRSC6":KDIRSC6, "KDIRSC7":KDIRSC7, "KDIRSC8":KDIRSC8, "KDIRSC9":KDIRSC9, "KDIRSC10":KDIRSC10, "KDIRSC11":KDIRSC11, "KDIRSC12":KDIRSC12, "MEMCLR1":MEMCLR1, "MEMCLR2":MEMCLR2, "MEMCLR3":MEMCLR3, "MEMCLR4":MEMCLR4, "MEMCLR5":MEMCLR5, "MEMCLR6":MEMCLR6, "MEMCLR7":MEMCLR7, "MEMCLR8":MEMCLR8, "MEMCLR9":MEMCLR9, "MEMCLR10":MEMCLR10, "MEMCLR11":MEMCLR11, "MEMCLR12":MEMCLR12, "MEMCLRALL":MEMCLRALL, "MEMSET1":MEMSET1, "MEMSET2":MEMSET2, "MEMSET3":MEMSET3, "MEMSET4":MEMSET4, "MEMSET5":MEMSET5, "MEMSET6":MEMSET6, "MEMSET7":MEMSET7, "MEMSET8":MEMSET8, "MEMSET9":MEMSET9, "MEMSET10":MEMSET10, "MEMSET11":MEMSET11, "MEMSET12":MEMSET12, "SORTBYNONE":SORTBYNONE, "SORTBYPERMS":SORTBYPERMS, "SORTBYLINKS":SORTBYLINKS, "SORTBYOWNER":SORTBYOWNER, "SORTBYGROUP":SORTBYGROUP, "SORTBYLENGTH":SORTBYLENGTH, "SORTBYTIME":SORTBYTIME, "SORTBYNAME":SORTBYNAME, "SORTREV":SORTREV, "SORTSEP":SORTSEP, } # Set all the program options to their default values # This means that a configuration file reload can # override the options set previously in the environment # variable or on the command line. for x in (UI.OptionsBoolean, UI.OptionsNumeric, UI.OptionsString): for o in x.keys(): globals()[o] = x[o] # If user specified a config file, try that # Otherwise use HOME == either $HOME or ./ if not CONF: CONF = os.path.join(HOME, "." + PROGNAME) # Actually read and parse the configuration file. ReadConfFile(CONF) MissingEndIfs = len(ConditionalStack) - 1 if MissingEndIfs: WrnMsg(wMISSENDIF % str(MissingEndIfs)) # Make sure any options we've changed are implemented if DoOptionsProcessing: ProcessOptions() # Initialize the command menu UI.CmdBtn.menu.delete(0,END) # And disable it UI.CmdBtn.config(state=DISABLED) # Now load the menu with the final set of commands for cmdkey in UI.Commands: cmdname = UI.CmdTable[cmdkey][0] UI.CmdBtn.menu.add_command(label=PadString(cmdname, CMDMENU_WIDTH) + "(" + cmdkey + ")", command=lambda cmd=cmdkey: CommandMenuSelection(cmd)) # Enable the menu if it has entries. # If no commands are defined, warn the user. if UI.CmdBtn.menu.index(END): UI.CmdBtn['menu'] = UI.CmdBtn.menu UI.CmdBtn.configure(state=NORMAL) else: WrnMsg(wNOCMDS) return 'break' # End of 'ProcessConfiguration()' ##### # Read & Parse A Configuration File # Called By ProcessConfiguration() And Each Time # A '.include' Is Encountered Within A Configuration File ##### def ReadConfFile(file): # Keep track of the line number on a per-file basis linenum = 0 try: cf = open(file) # Successful open of config file - Begin processing it # Process and massage the configuration file for line in cf.read().splitlines(): linenum += 1 # Parse this line if line: ParseLine(line, file, linenum) # Close the config file cf.close() except: WrnMsg(wCONFOPEN % file) # End of 'ReadConfFile()' ##### # Parse A Line From A Configuration File ##### def ParseLine(line, file, num): global UI, ConditionalStack ### # Cleanup the line ### # Get rid of trailing newline, if any if line[-1] == '\n': line = line[:-1] # Strip comments out idx = line.find(COMMENT) if idx > -1: line = line[:idx] # Strip off leading/trailing spaces cleanline = line.strip() # Split what's left into separate fields again fields = cleanline.split() # Make a copy of the fields which is guaranteed to have at # least two fields for use in the variable declaration tests. dummy = fields[:] dummy.append("") # Before going on, make sure we're even supposed to process the line. # If we are currently in the midst of a *false* conditional # block, we must throw this line away. The only thing # we'll accept in that case is more conditional statements. if (not ConditionalStack[-1]) and (dummy[0] not in (CONDIF, CONDENDIF)): return ### # Blank Lines - Ignore ### if len(fields) == 0: pass ### # Conditionals ### # Legal conditional statements are in one of several forms: # # .if [SYMBOL] # .if [SYMBOL] == string OR .if [SYMBOL] != string # .if [SYMBOL]== string OR .if [SYMBOL]!= string # .if [SYMBOL] ==string OR .if [SYMBOL] !=string # .if [SYMBOL]==string OR .if [SYMBOL]!=string # .endif # Additionally, [SYMBOL] can also be an environment # variable - [$SYMBOL] ##### # Process Conditional Beginning-Of-Block Statement ##### elif fields[0] == CONDIF: # Hack off the conditional statement so we can # process what's left condline = cleanline[len(CONDIF):].strip() # Iterate through all legitimate possible # beginning-of-block forms. The iteration # tuple is in the form: (condition, # of required arguments) args = [] condition, var, cmpstr = "", "", "" for condtype, numargs in [(CONDEQUAL, 2), (CONDNOTEQUAL, 2), (None, 1)]: # Process forms that have arguments following the variable reference if condtype: if condline.count(condtype): args = condline.split(condtype) condition = condtype break # Process the existential conditional form else: args = condline.split() condition = condtype break # Check for correct syntax # We have to have the right number of arguments, AND # the condition variable has to be in proper variable reference form: [VAR] AND # the rightmost argument cannot be an empty string if (len(args) != numargs) or (not CONDVAR.match(args[0].strip())) or (not args[-1]): WrnMsg(wBADIF % (num, line), fn=file) return # Syntax OK, process the conditional test else: # Assume the conditional test fails conditional = FALSE # Strip the reference syntax to get just the variable name var = args[0].strip()[1:-1] # Handle the equality tests if condition: # De-reference the variable's contents, accomodating # both Environment and User-Defined variable types if var[0] == ENVVBL: var = os.getenv(var[1:]) else: var = UI.SymTable.get(var) # Get the comparison string cmpstr = args[1].strip() # Now process each type of condition explicitly if condition == CONDEQUAL: if var == cmpstr: conditional = TRUE elif condition == CONDNOTEQUAL: if var != cmpstr: conditional = TRUE # Handle the existential conditional else: if var[0] == ENVVBL: if os.environ.has_key(var[1:]): conditional = TRUE elif UI.SymTable.has_key(var): conditional = TRUE # Even if the current conditional is True, we do not # process its contents if the *containing* scope is False. # i.e., A given conditional's truth is determined by its # own state AND the state of the containing scope. ConditionalStack.append(ConditionalStack[-1] and conditional) ##### # Process Conditional End-Of-Block Statement ##### elif fields[0] == CONDENDIF: # The end-of-block statement must be on a line by itself if len(fields) != 1: WrnMsg(wBADENDIF % (num, line), fn=file) # The conditional stack must always have 1 value left in # it *after* all conditional processing. If it does not, # it means there are more .endifs than .ifs. elif len(ConditionalStack) == 1: WrnMsg(wEXTRAENDIF % (num, line), fn=file) else: ConditionalStack.pop() ##### # Process Include Directive ##### elif fields[0] == DIRECTINC: ReadConfFile(cleanline.split(DIRECTINC)[1].strip()) ### # Variable Definitions And Special Assignments ### # A valid variable definition can # be 1, 2, or 3 fields: # # x=y - 1 field # x= y - 2 fields # x =y # x = y - 3 fields # # But this is illegal # # =....... # # However, the assignment character # must always been in the 1st or second # field. If it is a 3rd field, it is not # a variable definition, but a command definition. # # If the LHS is one of the Program Function Names # used in key binding, the statement is understood # to be a key rebinding, not a user variable definition. # # If the LHS is one of the Directory Shortcut variables, # the RHS is added to the Directory Menu and assigned # to the associated Function Key (1-12). # # Finally, the LHS cannot be one of the program # Built-In Variables - it is an error, for example, # to have something like: # # DIR = string # # because "DIR" is a Built-In Variable name. # elif ((dummy[0].count(ASSIGN) + dummy[1].count(ASSIGN)) > 0) and (fields[0][0] != ASSIGN): assign = cleanline.find(ASSIGN) name = cleanline[:assign].strip() val=cleanline[assign+1:].strip() # Error out on any attempt to "define" a Built-In Variable if UI.BuiltIns.has_key('[' + name + ']') or UI.BuiltIns.has_key('[' + name): WrnMsg(wREDEFVAR % (num, name, line), fn=file) return # Handle Directory Shortcut entries. if REDIRSC.match(name): # Get shortcut number sc = int(name.split(DIRSC)[1]) # Process if in range if 0 < sc < NUMFUNCKEY + 1: # Associate the directory with the correct shortcut key UI.DirSCKeys[sc-1] = val # Add to Directory Menu making sure it is PSEP-terminated if val and (val[-1] != PSEP): val += PSEP if val: UpdateDirMenu(val) # User specified an invalid shortcut number else: WrnMsg(wBADSCNUM % (num, line), fn=file) return # Process any wildcard definitions elif name == WILDCARD: if val and (val not in UI.WildHist): UpdateMenu(UI.WildBtn, UI.WildHist, MAXMENU, MAXMENUBUF, KeySelWild, newentry=val, fakeevent=TRUE) # Process any option variables - blank RHS is OK and means to leave # option set to its default value. elif name in UI.OptionsBoolean.keys(): if val: val = val.upper() if val == 'TRUE' or val == 'FALSE': globals()[name] = eval(val) # !!! Cheater's way to get to global variables. else: WrnMsg(wBADRHS % (num, line), fn=file) return elif name in UI.OptionsNumeric.keys(): if val: try: val = StringToNum(val) if val >= 0: globals()[name] = val else: WrnMsg(wBADRHS % (num, line), fn=file) return except: WrnMsg(wBADRHS % (num, line), fn=file) return elif name in UI.OptionsString.keys(): if val: globals()[name] = val # Process other variable types. # Distinguish between internal program variables and # user-defined variables and act accordingly. We inhibit # the rebinding of certain, special assignments, however. elif name in UI.KeyBindings.keys(): if name in NOREBIND: WrnMsg(wNOREBIND % (num, line), fn=file) return else: UI.KeyBindings[name] = val else: UI.SymTable[name] = val ### # Command Definitions ### elif len(fields[0]) == 1: # Must have at least 3 fields for a valid command definition if len(fields) < 3: WrnMsg(wBADCFGLINE % (num, line), fn=file) return else: cmdkey = fields[0] cmdname = fields[1] cmd = " ".join(fields[2:]) # Process any User-Defined variables cmd = ProcessVariables(cmd, num, cleanline) # A null return means there was a problem - abort if not cmd: return # Add the command entry to the command table. # If the key is a duplicate, the older definition is # overwritten. UI.CmdTable[cmdkey] = [cmdname, cmd] # Keep track of the order in which the commands # were defined - we want them to show up that # way in the Command Menu so user can put # most-used commands near the top. # Do this suppressing duplicates. if cmdkey in UI.Commands: UI.Commands.remove(cmdkey) UI.Commands.append(cmdkey) else: WrnMsg(wBADCFGLINE % (num, line), fn=file) # End of 'ParseLine()' ##### # Print Debug Information On stdout ##### def PrintDebug(title, content): print '<%s>\n' % title.upper() if content: for i in content: print i else: print dNULL print # End of 'PrintDebug()' ##### # Setup The GUI Visual Parameters, Menus, & Help Information ##### def SetupGUI(): # Start in detailed mode unless details are inhibited UI.SetDetailedView(~NODETAILS) # Rebind all the handlers UI.BindAllHandlers() # Any user-set options have now been read, set the GUI for i in (UI.CmdBtn, UI.DirBtn, UI.HistBtn, UI.SortBtn, UI.WildBtn, UI.HelpBtn): i.config(foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) i.menu.config(foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) # Set Menu Bar background to match buttons UI.mBar.config(background=MBARCOL) UI.DirList.config(font=(FNAME, FSZ, FWT), foreground=FCOLOR, background=BCOLOR) # Make sure menus conform to max lengths (which may have changed). UpdateMenu(UI.DirBtn, UI.AllDirs, MAXMENU, MAXMENUBUF, LoadDirList, sort=TRUE) UpdateMenu(UI.HistBtn, UI.CmdHist, MAXMENU, MAXMENUBUF, KeyRunCommand, fakeevent=TRUE) UpdateMenu(UI.WildBtn, UI.WildHist, MAXMENU, MAXMENUBUF, KeySelWild, fakeevent=TRUE) # Initialize the Sorting Menu LoadSortMenu() # Initialize the Help Menu LoadHelpMenu() # Size and position the display UIroot.geometry("%sx%s+%s+%s" % (WIDTH, HEIGHT, STARTX, STARTY)) # End of 'SetupGUI()' ##### # Load The Sorting Menu with the latest information ##### def LoadSortMenu(): # Sort Menu content is different if in Drive List View # Show options appropriate to the current view if UI.CurrentDir == SHOWDRIVES: idx = 2 else: idx = 1 # Clear out any old entries UI.SortBtn.config(state=DISABLED) UI.SortBtn.menu.delete(0,END) # Add the menu selections for entry in [fNONE, fPERMISSIONS, fLINKS, fOWNER, fGROUP, fLENGTH, fDATE, fNAME, fREVERSE, fSEPARATE]: t = Name2Key[entry.lower()] if t[idx]: UI.SortBtn.menu.add_command(label=t[idx], command=lambda parm=t[1] : KeySetSortParm(parm)) # Store the current directory - used in subsequent calls to this funtion to # determine whether or not we're moving between Normal <--> Drive List Views UI.SortBtn.CurrentDir = UI.CurrentDir # Enable the menu selections UI.SortBtn['menu'] = UI.SortBtn.menu UI.SortBtn.config(state=NORMAL) # End of 'LoadSortMenu()' ##### # Load Help Menu with latest information ##### def LoadHelpMenu(): # Clear out existing content UI.HelpBtn.config(state=DISABLED) UI.HelpBtn.menu.delete(0,END) # Update the cascading submenus # We iterate across tuples of (Menu Name, Menu Variable, List Of Items) for mname, mvbl, mlist in ((hUSERVBLS, UI.UserVbls, GetUserVbls()), (hCOMMANDS, UI.CmdDefs, GetCommandTable()), (hINTVBLS, UI.IntVbls, GetIntVars()), (hOPTVBLS, UI.OptVbls, GetOptions()), (hKEYS, UI.Keys, GetKeyBindings()), (hDIRSC, UI.DirSCs, GetDirShortcuts())): mvbl.delete(0,END) # Indicated if there is nothing to display for this class of help if not mlist: mvbl.add_command(label=hNONE % mname, command=None, foreground=HFCOLOR, background=HBCOLOR, font=(HFNAME, HFSZ, HFWT)) # Load the help class with relevant information else: for l in mlist: mvbl.add_command(label=l, command=None, foreground=HFCOLOR, background=HBCOLOR, font=(HFNAME, HFSZ, HFWT)) UI.HelpBtn.menu.add_cascade(label=mname, menu=mvbl) # Setup the About item UI.HelpBtn.menu.add_command(label=hABOUT, command=lambda title=hABOUT, text=ABOUT : showinfo(title, text)) # Enable the menu content UI.HelpBtn['menu'] = UI.HelpBtn.menu UI.HelpBtn.config(state=NORMAL) # End of 'LoadHelpMenu()' ##### # Convert A String In Integer Or Hex Format To An Equivalent Numeric # We assume that the string is either in correct format or that # the calling routine will catch any error. ##### def StringToNum(string): if string.lower().startswith('0x'): value = int(string, 16) else: value = int(string, 10) return value # End of 'StringToNum() ##### # Strip Trailing Path Separator ##### def StripPSEP(s): if s and s[-1] == PSEP: return s[:-1] else: return s # End of 'StripPSEP()' ##### # Print Usage Information ##### def Usage(): for x in uTable: print x # End of 'Usage()' ##### # Check Debug Level To See If It Is A Properly Formed Integer Or Hex Value # If So, Convert To Numeric, If Not, Warn User, And Set To 0 ##### def ValidateDebugLevel(): global DEBUGLEVEL d = DEBUGLEVEL # Save, in case of error try: DEBUGLEVEL = StringToNum(DEBUGLEVEL) except: DEBUGLEVEL = 0 WrnMsg(wBADDEBUG % d) # End of 'ValidateDebugLevel()' ##### # Print A Warning Message ##### def WrnMsg(wmsg, fn=""): if WARN: showwarning("%s %s %s %s" % (PROGNAME, VERSION, wWARN, fn), wmsg) # End of 'WrnMsg()' #----------------------------------------------------------# # GUI Definition # #----------------------------------------------------------# ##### # Enacapsulate the UI in a class ##### class twanderUI: def __init__(self, root): # Setup Menubar frame self.mBar = Frame(root, relief=RAISED, borderwidth=MENUBORDER) self.mBar.pack(fill=X) # Setup the Command Menu self.CmdBtn = Menubutton(self.mBar, text=COMMANDMENU, underline=0, state=DISABLED) self.CmdBtn.menu = Menu(self.CmdBtn) self.CmdBtn.pack(side=LEFT, padx=MENUPADX) # Setup the Directory Menu self.DirBtn = Menubutton(self.mBar, text=DIRMENU, underline=0, state=DISABLED) self.DirBtn.menu = Menu(self.DirBtn) self.DirBtn.pack(side=LEFT, padx=MENUPADX) # Setup the History Menu self.HistBtn = Menubutton(self.mBar, text=HISTMENU, underline=0, state=DISABLED) self.HistBtn.menu = Menu(self.HistBtn) self.HistBtn.pack(side=LEFT, padx=MENUPADX) # Setup the Sort Menu self.SortBtn = Menubutton(self.mBar, text=SORTMENU, underline=0, state=DISABLED) self.SortBtn.menu = Menu(self.SortBtn) self.SortBtn.pack(side=LEFT, padx=MENUPADX) # Setup the Wildcard Menu self.WildBtn = Menubutton(self.mBar, text=WILDMENU, underline=0, state=DISABLED) self.WildBtn.menu = Menu(self.WildBtn) self.WildBtn.pack(side=LEFT, padx=MENUPADX) # Setup the Help Menu self.HelpBtn = Menubutton(self.mBar, text=HELPMENU, underline=2, state=DISABLED) self.HelpBtn.menu = Menu(self.HelpBtn) self.HelpBtn.pack(side=LEFT, padx=MENUPADX) # Setup the cascading submenus self.UserVbls = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) self.CmdDefs = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) self.IntVbls = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) self.OptVbls = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) self.Keys = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) self.DirSCs = Menu(self.HelpBtn.menu, foreground=MFCOLOR, background=MBCOLOR, font=(MFNAME, MFSZ, MFWT)) # Setup the Directory Listing and Scrollbars self.hSB = Scrollbar(root, orient=HORIZONTAL) self.vSB = Scrollbar(root, orient=VERTICAL) self.DirList = Listbox(root, selectmode=EXTENDED, exportselection=0, xscrollcommand=self.hSB.set, yscrollcommand=self.vSB.set) # Make them visible by packing self.hSB.config(command=self.DirList.xview) self.hSB.pack(side=BOTTOM, fill=X) self.vSB.config(command=self.DirList.yview) self.vSB.pack(side=RIGHT, fill=Y) self.DirList.pack(side=LEFT, fill=BOTH, expand=1) # End of method 'twanderUI.__init__()' ### # Bind the relevant event handlers ### def BindAllHandlers(self): ### # General Program Commands ### # Bind handler to invoke Clear Command History self.DirList.bind(self.KeyBindings["CLRHIST"], ClearHistory) # Bind handler to invoke Decrement Font Size self.DirList.bind(self.KeyBindings["FONTDECR"], FontDecr) # Bind handler to invoke Increment Font Size self.DirList.bind(self.KeyBindings["FONTINCR"], FontIncr) # Bind handler to invoke Command Menu self.DirList.bind(self.KeyBindings["MOUSECTX"], MouseClick) # Bind handler to invoke Directory Menu self.DirList.bind(self.KeyBindings["MOUSEDIR"], MouseClick) # Bind handler for individual keystrokes self.DirList.bind(self.KeyBindings["KEYPRESS"], KeystrokeHandler) # Bind handler for "Quit Program" self.DirList.bind(self.KeyBindings["QUITPROG"], KeyQuitProg) # Bind handler for "Read Config File" self.DirList.bind(self.KeyBindings["READCONF"], ProcessConfiguration) # Bind handler for "Refresh Screen" self.DirList.bind(self.KeyBindings["REFRESH"], RefreshDirList) # Bind handler for "Toggle Autorefresh" self.DirList.bind(self.KeyBindings["TOGAUTO"], KeyToggleAuto) # Bind handler for "Toggle Detail" self.DirList.bind(self.KeyBindings["TOGDETAIL"], KeyToggleDetail) # Bind handler for "Toggle win32all Features" self.DirList.bind(self.KeyBindings["TOGWIN32ALL"], KeyToggleWin32All) ### # Directory Navigation ### # Bind handler for "Change Directory" self.DirList.bind(self.KeyBindings["CHANGEDIR"], KeyChangeDir) # Bind handler for "Home Dir" self.DirList.bind(self.KeyBindings["DIRHOME"], KeyHomeDir) # Bind handler for "Previous Dir" self.DirList.bind(self.KeyBindings["DIRBACK"], KeyBackDir) # Bind handler for "Root Dir" self.DirList.bind(self.KeyBindings["DIRROOT"], KeyRootDir) # Bind handler for "Starting Dir" self.DirList.bind(self.KeyBindings["DIRSTART"], KeyStartDir) # Bind handler for "Up Dir" self.DirList.bind(self.KeyBindings["DIRUP"], KeyUpDir) # Bind handler for "Display Drive View" self.DirList.bind(self.KeyBindings["DRIVELIST"], KeyDriveList) # Bind handler for "Mouse Back Dir" self.DirList.bind(self.KeyBindings["MOUSEBACK"], MouseDblClick) # Bind handler for "Mouse Up Dir" self.DirList.bind(self.KeyBindings["MOUSEUP"], MouseDblClick) ### # Selection Keys ### # Bind handler for "Select All" self.DirList.bind(self.KeyBindings["SELALL"], KeySelAll) # Bind handler for "Invert Current Selection" self.DirList.bind(self.KeyBindings["SELINV"], KeySelInv) # Bind handler for "Select No Items" self.DirList.bind(self.KeyBindings["SELNONE"], KeySelNone) # Bind handler for "Next Item" self.DirList.bind(self.KeyBindings["SELNEXT"], KeySelNext) # Bind handler for "Previous Item" self.DirList.bind(self.KeyBindings["SELPREV"], KeySelPrev) # Bind handler for "Last Item" self.DirList.bind(self.KeyBindings["SELEND"], KeySelEnd) # Bind handler for "First Item" self.DirList.bind(self.KeyBindings["SELTOP"], KeySelTop) # Bind handler for "Select With Wildcard" self.DirList.bind(self.KeyBindings["SELWILD"], KeySelWild) ### # Scrolling Keys ### # Bind Handler for "Move Page Down self.DirList.bind(self.KeyBindings["PGDN"], KeyPageDown) # Bind Handler for "Move Page Up" self.DirList.bind(self.KeyBindings["PGUP"], KeyPageUp) # Bind Handler for "Move Page Right" self.DirList.bind(self.KeyBindings["PGRT"], KeyPageRight) # Bind Handler for "Move Page Up" self.DirList.bind(self.KeyBindings["PGLFT"], KeyPageLeft) ### # Execute Commands ### # Bind handler for "Run Command" self.DirList.bind(self.KeyBindings["RUNCMD"], lambda event : KeyRunCommand(event, DoCmdShell=True)) # Bind handler for "Item Select" self.DirList.bind(self.KeyBindings["SELKEY"], DirListHandler) # Bind handler for "Mouse Select" self.DirList.bind(self.KeyBindings["MOUSESEL"], MouseDblClick) ### # Directory Shortcut Keys - All Bound To A Common Handler ### self.DirList.bind(self.KeyBindings["KDIRSC1"], lambda event :DirSCKeyPress(event, 1)) self.DirList.bind(self.KeyBindings["KDIRSC2"], lambda event :DirSCKeyPress(event, 2)) self.DirList.bind(self.KeyBindings["KDIRSC3"], lambda event :DirSCKeyPress(event, 3)) self.DirList.bind(self.KeyBindings["KDIRSC4"], lambda event :DirSCKeyPress(event, 4)) self.DirList.bind(self.KeyBindings["KDIRSC5"], lambda event :DirSCKeyPress(event, 5)) self.DirList.bind(self.KeyBindings["KDIRSC6"], lambda event :DirSCKeyPress(event, 6)) self.DirList.bind(self.KeyBindings["KDIRSC7"], lambda event :DirSCKeyPress(event, 7)) self.DirList.bind(self.KeyBindings["KDIRSC8"], lambda event :DirSCKeyPress(event, 8)) self.DirList.bind(self.KeyBindings["KDIRSC9"], lambda event :DirSCKeyPress(event, 9)) self.DirList.bind(self.KeyBindings["KDIRSC10"], lambda event :DirSCKeyPress(event, 10)) self.DirList.bind(self.KeyBindings["KDIRSC11"], lambda event :DirSCKeyPress(event, 11)) self.DirList.bind(self.KeyBindings["KDIRSC12"], lambda event :DirSCKeyPress(event, 12)) ### # Memory Keys - All Features Bound To A Common Handler ### self.DirList.bind(self.KeyBindings["MEMCLR1"], lambda event : KeyMemHandler(mem=1, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR2"], lambda event : KeyMemHandler(mem=2, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR3"], lambda event : KeyMemHandler(mem=3, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR4"], lambda event : KeyMemHandler(mem=4, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR5"], lambda event : KeyMemHandler(mem=5, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR6"], lambda event : KeyMemHandler(mem=6, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR7"], lambda event : KeyMemHandler(mem=7, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR8"], lambda event : KeyMemHandler(mem=8, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR9"], lambda event : KeyMemHandler(mem=9, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR10"], lambda event : KeyMemHandler(mem=10, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR11"], lambda event : KeyMemHandler(mem=11, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLR12"], lambda event : KeyMemHandler(mem=12, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMCLRALL"], lambda event : KeyMemHandler(mem=13, clear=TRUE)) self.DirList.bind(self.KeyBindings["MEMSET1"], lambda event : KeyMemHandler(mem=1)) self.DirList.bind(self.KeyBindings["MEMSET2"], lambda event : KeyMemHandler(mem=2)) self.DirList.bind(self.KeyBindings["MEMSET3"], lambda event : KeyMemHandler(mem=3)) self.DirList.bind(self.KeyBindings["MEMSET4"], lambda event : KeyMemHandler(mem=4)) self.DirList.bind(self.KeyBindings["MEMSET5"], lambda event : KeyMemHandler(mem=5)) self.DirList.bind(self.KeyBindings["MEMSET6"], lambda event : KeyMemHandler(mem=6)) self.DirList.bind(self.KeyBindings["MEMSET7"], lambda event : KeyMemHandler(mem=7)) self.DirList.bind(self.KeyBindings["MEMSET8"], lambda event : KeyMemHandler(mem=8)) self.DirList.bind(self.KeyBindings["MEMSET9"], lambda event : KeyMemHandler(mem=9)) self.DirList.bind(self.KeyBindings["MEMSET10"], lambda event : KeyMemHandler(mem=10)) self.DirList.bind(self.KeyBindings["MEMSET11"], lambda event : KeyMemHandler(mem=11)) self.DirList.bind(self.KeyBindings["MEMSET12"], lambda event : KeyMemHandler(mem=12)) ### # Sort Selection Keys - All Bound To A Common Handler ### self.DirList.bind(self.KeyBindings["SORTBYNONE"], lambda event : KeySetSortParm(parm=fNONE)) self.DirList.bind(self.KeyBindings["SORTBYPERMS"], lambda event : KeySetSortParm(parm=fPERMISSIONS)) self.DirList.bind(self.KeyBindings["SORTBYLINKS"], lambda event : KeySetSortParm(parm=fLINKS)) self.DirList.bind(self.KeyBindings["SORTBYOWNER"], lambda event : KeySetSortParm(parm=fOWNER)) self.DirList.bind(self.KeyBindings["SORTBYGROUP"], lambda event : KeySetSortParm(parm=fGROUP)) self.DirList.bind(self.KeyBindings["SORTBYLENGTH"], lambda event : KeySetSortParm(parm=fLENGTH)) self.DirList.bind(self.KeyBindings["SORTBYTIME"], lambda event : KeySetSortParm(parm=fDATE)) self.DirList.bind(self.KeyBindings["SORTBYNAME"], lambda event : KeySetSortParm(parm=fNAME)) self.DirList.bind(self.KeyBindings["SORTREV"], lambda event : KeySetSortParm(parm=fREVERSE)) self.DirList.bind(self.KeyBindings["SORTSEP"], lambda event : KeySetSortParm(parm=fSEPARATE)) # Give the listbox focus so it gets keystrokes self.DirList.focus() # End Of method 'twanderUI.BindAllHandlers() ##### # Return tuple of all selected items ##### def AllSelection(self): sellist = [] nameindex = self.NameFirst for entry in self.DirList.curselection(): sellist.append(self.DirList.get(entry)[nameindex:].split(SYMPTR)[0]) return sellist # End of method 'twanderUI.AllSelection()' ##### # Return name of currently selected item ##### def LastInSelection(self): nameindex = self.NameFirst index = self.DirList.curselection() if index: return self.DirList.get(index[-1])[nameindex:].split(SYMPTR)[0] else: return "" # End of method 'twanderUI.LastInSelection()' ##### # Support periodic polling to make sure widget stays # in sync with reality of current directory. ##### def poll(self): # If new dir entered via mouse, force correct activation if self.MouseNewDir: self.DirList.activate(0) self.MouseNewDir = FALSE # Do autorefreshing as required if AUTOREFRESH: self.ElapsedTime += POLLINT # Is it time for a referesh? if self.ElapsedTime >= REFRESHINT: # Don't autorefresh on drive list views if UI.CurrentDir != SHOWDRIVES: # Don't autorefresh if there is a lock outstanding # Try back again in 1/4 the usual time # This avoids a deadly embrace which can happen # if you spin on this lock, waiting for it to release if not UI.DirListMutex.test(): RefreshDirList() self.ElapsedTime = 0 else: self.ElapsedTime = (REFRESHINT * 3)/4 # Setup next polling event self.DirList.after(POLLINT, self.poll) # End of method 'twanderUI.poll()' ##### # Set Detailed View -> FALSE == No Details, TRUE == Details ##### def SetDetailedView(self, details): # See if we're forcing details to always be off if NODETAILS: self.DetailsOn = FALSE else: self.DetailsOn = details # End of method 'twanderUI.SetDetailedView()' ##### # Set a particular selection, w/bounds checking # Note that 'selection' is passed as a string # but 'active' is passed as a number. ##### def SetSelection(self, selection, active): # Clear all current selection(s) self.DirList.selection_clear(0, END) # Get current maximum index maxindex = self.DirList.size() - 1 # And bounds check/adjust if active > maxindex: active = maxindex # Set desired selected items, if any if selection: for entry in selection: self.DirList.select_set(entry) self.DirList.see(selection[-1]) # Now set the active entry self.DirList.activate(active) # End of method 'twanderUI.SetSelection()' ##### # Update title bar with most current information ##### def UpdateTitle(self, mainwin): if UI.CurrentDir == SHOWDRIVES: # SORTBYFIELD can have values not meaningful # in Drive List View - these are always mapped to fNAME if Name2Key[SORTBYFIELD.lower()][0] > MAXDLVKEY: srtfld = dlvLETTER.upper() else: srtfld = Name2Key[SORTBYFIELD.lower()][2].upper() sepsort = "" else: srtfld = SORTBYFIELD.upper() srtsep = YesOrNo[SORTSEPARATE] sepsort = "%s %s" % (TTLSORTSEP, srtsep) # Indicate Reverse sort by appending a '-' to the sort field name if SORTREVERSE: srtfld += "-" sortedby = "%s %s " % (TTLSORTFLD, srtfld) autostate = YesOrNo[AUTOREFRESH] mainwin.title("%s %s %s: %s %s %s %s %s %s%s %s %s" % (PROGNAME, VERSION, FULLNAME, UI.CurrentDir, TTLFILES, str(self.DirList.size()), TTLSIZE, FileLength(self.TotalSize), sortedby, sepsort, TTLAUTO, autostate)) # End of method 'twanderUI.UpdateTitle()' # End of class definition, 'twanderUI' #----------------------------------------------------------# # Handler Functions # #----------------------------------------------------------# #---------------- Mouse Click Dispatchers -----------------# # We intercept all mouse clicks (of interest) here so it # is easy to uniquely handle the Control, Shift, Alt, # variations of button presses. We use Tkinter itself # keep track of single- vs. double-clicks and hand-off # the event to the corresponding Mouse Click Dispatcher. ##### # Event Handler: Single Mouse Clicks ##### def MouseClick(event): event.state &= ~DontCareMask # Kill the bits we don't care about if event.state == Button3Mask: # Button-3 / No Modifier x, y = UI.DirList.winfo_pointerxy() # Position near mouse PopupMenu(UI.CmdBtn.menu, x, y) # Display Command Menu elif event.state == (Button3Mask | ShiftMask | ControlMask): # Shift-Control-Button-3 x, y = UI.DirList.winfo_pointerxy() # Position near mouse PopupMenu(UI.HistBtn.menu, x, y) # Display History Menu elif event.state == (Button3Mask | AltMask | ControlMask): # Alt-Control-Button-3 x, y = UI.DirList.winfo_pointerxy() # Position near mouse PopupMenu(UI.WildBtn.menu, x, y) # Display Wildcard Menu elif event.state == (Button3Mask | ShiftMask): # Shift-Button-3 x, y = UI.DirList.winfo_pointerxy() # Position near mouse PopupMenu(UI.DirBtn.menu, x, y) # Display Directory Menu # End Of 'MouseClick()' ##### # Event Handler: Mouse Double-Clicks ##### def MouseDblClick(event): event.state &= ~DontCareMask # Kill the bits we don't care about if event.state == Button1Mask: # Double-Button-2 / No Modifier DirListHandler(event) # Execute selected item elif event.state == (Button1Mask | ControlMask): # Control-DblButton-1 KeyBackDir(event) # Move back one directory elif event.state == (Button3Mask | ControlMask): # Control-DblButton-3 KeyUpDir(event) # Move up one directory return "break" # End Of 'MouseDblClick() #--------------- General Program Commands -----------------# ##### # Event Handler: Clear Various Program Histories ##### def ClearHistory(event): global UI UI.AllDirs = [] UI.CmdHist = [] UI.WildHist = [] UI.LastCmd = "" UI.LastDir = [] UI.LastPathEntered = "" UI.LastSelWildcard = "" for x in [UI.DirBtn, UI.HistBtn, UI.WildBtn]: x.menu.delete(0,END) x['menu'] = x.menu x.config(state=DISABLED) return 'break' # End of 'ClearHistory()' ##### # Decrement Font Size ##### def FontDecr(event): global FSZ, MFSZ, HFSZ if FSZ > 1: FSZ -= 1 if MFSZ > 1: MFSZ -= 1 if HFSZ > 1: HFSZ -= 1 SetupGUI() return 'break' # End of 'FontDecr()' ##### # Increment Font Size ##### def FontIncr(event): global FSZ, MFSZ, HFSZ FSZ += 1 MFSZ += 1 HFSZ += 1 SetupGUI() return 'break' # End of 'FontIncr()' ##### # Event Handler: Individual Keystrokes ##### def KeystrokeHandler(event): event.state &= ~DontCareMask # Kill the bits we don't care about # Check for, and handle accelerator keys if event.state == AltMask: # Set menu button associated with accelerator # Command Menu if event.char == 'c': button = UI.CmdBtn # Directory Menu elif event.char == 'd': button = UI.DirBtn # History Menu elif event.char == 'h': button = UI.HistBtn # Sort Menu elif event.char == 's': button = UI.SortBtn # Wildcard Menu elif event.char == 'w': button = UI.WildBtn # Help Menu elif event.char == 'l': button = UI.HelpBtn # Unrecognized - Ignore else: return parts = button.winfo_geometry().split('+') # Geometry returned as "WidthxHeight+X+Y" dims = parts[0].split('x') x, y = int(parts[1]), int(parts[2]) w, h = int(dims[0]), int(dims[1]) x += UIroot.winfo_rootx() # This is relative to root window position y += UIroot.winfo_rooty() # So adjust accordingly # Display the requested menu PopupMenu(button.menu, x+MENUOFFSET, y+h) # Inhibit event from getting picked up by local accelerator key handlers return "break" ##### # Otherwise, process single character command invocations. ##### # We *only* want to handle simple single-character # keystrokes. This means that there must be a character # present and that the only state modifier permitted # is the Shift key if not event.char or (event.state and event.state != 1): return # If the key pressed is a command key (i.e., it is in the table of # defined commands), get its associated string and execute the command. cmd = UI.CmdTable.get(event.char, ["", "", ""])[1] name = UI.CmdTable.get(event.char, ["", "", ""])[0] # cmd == null means no matching command key - do nothing # Otherwise, replace config tokens with actual file/dir names if cmd: ExecuteCommand(cmd, name) # end of 'KeystrokeHandler()' ##### # Event Handler: Program Quit ##### def KeyQuitProg(event): sys.exit() # End of 'KeyQuitProg()' ##### # Event Handler: Toggle Autorefresh ##### def KeyToggleAuto(event): global AUTOREFRESH # Toggle the state AUTOREFRESH = not AUTOREFRESH # Update the title bar to reflect this UI.UpdateTitle(UIroot) # Update the help menu to reflect change LoadHelpMenu() return 'break' # End of 'KeyToggleAuto()' ##### # Event Handler: Toggle Detail View ##### def KeyToggleDetail(event): UI.SetDetailedView(not UI.DetailsOn) RefreshDirList(event) return 'break' # End of 'KeyToggleDetail()' ##### # Event Handler: Toggle win32all Features, If Available ##### def KeyToggleWin32All(event): global WIN32ALLON if USEWIN32ALL and (UI.CurrentDir != SHOWDRIVES): WIN32ALLON = not WIN32ALLON RefreshDirList(event) return 'break' # End of 'KeyToggleWin32All()' #------------------- Directory Navigation -----------------# ##### # Event Handler: Change Directory/Path #### def KeyChangeDir(event): newpath = askstring(pCHPATH, pENPATH, initialvalue=UI.LastPathEntered) if newpath: if MAXMENU > 0: UI.LastPathEntered = newpath LoadDirList(newpath) KeySelTop(event) UI.DirList.focus() return 'break' # End of 'KeyChangeDir()' ##### # Event Handler: Goto $HOME ##### def KeyHomeDir(event): if HOME: LoadDirList(HOME) return 'break' # End of 'KeyHomeDir()' ##### # Event Handler: Move To Previous Directory ##### def KeyBackDir(event): # Move to last directory visited, if any - inhibit this from # being placed on the directory traversal stack if UI.LastDir: LoadDirList(UI.LastDir.pop(), save=FALSE) # No previous directory else: pass return 'break' # End of 'KeyBackDir()' ##### # Event Handler: Go To Root Directory ##### def KeyRootDir(event): LoadDirList(PSEP) return 'break' # End of 'KeyRootDir()' ##### # Event Handler: Go Back To Initial Directory ##### def KeyStartDir(event): LoadDirList(STARTDIR) return 'break' # End of 'KeyStartDir()' ##### # Event Handler: Move Up One Directory ##### def KeyUpDir(event): # Move up one directory level unless we're already at the root if UI.CurrentDir != os.path.abspath(PSEP): LoadDirList(UI.CurrentDir + "..") # Unless we're running on Win32 and we are able to do # a Drive List View elif OSNAME == 'nt' and GetWin32Drives(): LoadDirList(SHOWDRIVES) return 'break' # End of 'KeyUpDir()' ##### # Event Handler: Display Drive List View On Win32, If Possible ##### def KeyDriveList(event): # This is only possible on Win32 and if there is a list of # drives available - i.e, If Win32All is installed if OSNAME == 'nt' and GetWin32Drives(): LoadDirList(SHOWDRIVES) return 'break' # End of 'KeyDriveList() #---------------------- Selection Keys ---------------------# ##### # Event Handler: Select All Items ##### def KeySelAll(event): # In the case of a Drive List View, we want to literally # select everything. In all other cases, we do not # want this feature to select the first item which is ".." if UI.CurrentDir == SHOWDRIVES: UI.DirList.selection_set(0, END) else: # Unselect first item in case it was UI.DirList.selection_clear(0) # We never want to select the first item which is ".." UI.DirList.selection_set(1, END) return 'break' # End of 'KeySelAll()' ##### # Event Handler: Invert Current Selection ##### def KeySelInv(event): # List of current selections cs= UI.DirList.curselection() # Select everything UI.DirList.selection_set(0, END) # And unselect what was selected for v in cs: UI.DirList.selection_clear(v) # If we're not in a Drive List View, we never # select the first entry (".." this way) if UI.CurrentDir != SHOWDRIVES: UI.DirList.selection_clear(0) return 'break' # End of 'KeySelInv()' ##### # Event Handler: Select Next Item ##### def KeySelNext(event): next = UI.DirList.index(ACTIVE) # Don't increment if at end of list if (next < UI.DirList.size() - 1): next += 1 UI.SetSelection((str(next),), next) return 'break' # End of 'KeySelNext()' ##### # Event Handler: Select No Items ##### def KeySelNone(event): UI.DirList.selection_clear(0, END) return 'break' # End of 'KeySelNone()' ##### # Event Handler: Select Previous Item ##### def KeySelPrev(event): prev = UI.DirList.index(ACTIVE) # Only decrement if > 0 if prev: prev -= 1 UI.SetSelection((str(prev),), prev) return 'break' # End of 'KeySelPrev()' ##### # Event Handler: Select Last Item ##### def KeySelEnd(event): # Get index of last item in listbox sz = UI.DirList.size() - 1 # And setup to last item accordingly UI.SetSelection((str(sz),), sz) return 'break' # End of 'KeySelEnd()' ##### # Event Handler: Select First Item ##### def KeySelTop(event): UI.SetSelection(('0',),0) return 'break' # End of 'KeySelTop()' ##### # Event Handler: Select Using Wildcard ##### def KeySelWild(event, initial=""): # Ask the user for the wildcard pattern, using initial string, if any if initial: uwc = askstring(pWILDSEL, pENWILD, initialvalue=initial) else: uwc = askstring(pWILDSEL, pENWILD, initialvalue=UI.LastSelWildcard) # Return focus to the main interface UI.DirList.focus() # Blank value means to abort if not uwc: return # Clear current selections KeySelNone(event) # Unless the user indicates otherwise, cook the regex so # a match can occur anywhere on the line if uwc[0] == STRICTMATCH: wc = uwc[1:] else: wc = r".*" + uwc # Compile it - abort if compilation fails try: wild = re.compile(wc) except: WrnMsg(wWILDCOMP % wc) return 'break' # Iterate over the current directory listing # saving the indexes of items which match. # We start at 1 not 0 because the first entry # ("..") is never considered when doing wildcard # matching. matches = [] for x in range(1,UI.DirList.size()): if wild.match(UI.DirList.get(x)): matches.append(x) # If anything matched, select it if matches: UI.SetSelection(matches, matches[0]) # Save the wildcard only if dynamic menus are enabled (MAXMENU > 0) # AND one of two conditions exist: # # 1) No initial string was provided (The user entered a command manually). # 2) An initial string was provided, but the user edited it. if (MAXMENU > 0) and ((not initial) or (uwc != initial)): UI.LastSelWildcard = uwc # Add this wildcard to the menu if its not there alread if uwc not in UI.WildHist: UpdateMenu(UI.WildBtn, UI.WildHist, MAXMENU, MAXMENUBUF, KeySelWild, newentry=uwc, fakeevent=TRUE) # Dump wildcard stack if debug requested it if DEBUGLEVEL & DEBUGWILD: PrintDebug(dWILDSTK, UI.WildHist) return 'break' # End of 'KeySelWild()' #---------------------- Scrolling Keys ---------------------# ##### # Event Handler: Move Down A Page ##### def KeyPageDown(event): UI.DirList.yview_scroll(1, "pages") UI.DirList.activate("@0,0") return 'break' # End of 'KeyPageDown()' ##### # Event Handler: Move Up A Page ##### def KeyPageUp(event): UI.DirList.yview_scroll(-1, "pages") UI.DirList.activate("@0,0") return 'break' # End of 'KeyPageUp()' ##### # Event Handler: Move Page Right ##### def KeyPageRight(event): UI.DirList.xview_scroll(1, "pages") return 'break' # End of 'KeyPageRight()' ##### # Event Handler: Move Page Left ##### def KeyPageLeft(event): UI.DirList.xview_scroll(-1, "pages") return 'break' # End of 'KeyPageLeft()' #---------------------- Execute Commands -------------------# ##### # Event Handler: Run Manually Entered Command #### def KeyRunCommand(event, initial="", DoCmdShell=False): global UI # NOTE: DoCmdShell determines whether or not we do CMDSHELL # processing (if enabled). It is *off* by default because # we do not want commands invoked via the command history # mechanism to use this feature - doing so would cause # cascading of the CMDSHELL string on each subsequent # invocation. So, the only time we want to do CMDSHELL # processing is when the user presses the RUNCMD key. # The binding for this key thus sets DoCmdShell to True. # Prompt with passed initial edit string if initial: cmd = askstring(pRUNCMD, pENCMD, initialvalue=initial) # Prompt with last manually entered command - doesn't matter if it's null else: cmd = askstring(pRUNCMD, pENCMD, initialvalue=UI.LastCmd ) # Execute command (if any) - Blank entry means do nothing/return if cmd: # Do CMDSHELL Processing if enabled and requested mycmd = cmd if CMDSHELL and DoCmdShell: # See if feature is enabled and requested if not mycmd.startswith(CMDSHELLESC): # See if user is trying to escape the feature mycmd = "%s '%s'" % (CMDSHELL, cmd) else: # User is escaping the feature mycmd = mycmd[1:] ExecuteCommand(mycmd, pMANUALCMD, ResolveVars=TRUE, SaveUnresolved=TRUE) # Save the command only if Command History is enabled (MAXMENU > 0) # AND one of two conditions exist: # # 1) No initial string was provided (The user entered a command manually). # 2) An initial string was provided, but the user edited it. if (MAXMENU > 0) and ((not initial) or (cmd != initial)): UI.LastCmd = cmd UI.DirList.focus() return 'break' # End of 'KeyRunCommand()' ##### # Event Handler: Process Current Selection ##### def DirListHandler(event): global UI # Get current selection. If none, just return, otherwise process selected = UI.LastInSelection() if not selected: return # If we're on Win32 and we just selected ".." from the root of # a drive, request a display of the Drive List. LoadDirList() # will check to see if there is anything in the Drive List and # do nothing if it is empty (which happens if the user has not # installed the Win32All package). if OSNAME=='nt' and \ os.path.abspath(UI.CurrentDir) == os.path.abspath(UI.CurrentDir + selected): LoadDirList(SHOWDRIVES, save=TRUE) UI.MouseNewDir = TRUE # If selection is a directory, move there and list contents. elif os.path.isdir(os.path.join(UI.CurrentDir, selected)): # On Unix, don't follow links pointing back to themselves if OSNAME == 'posix' and os.path.samefile(UI.CurrentDir, UI.CurrentDir + selected): # Protect with try/except because Tk loses track of things # if you keep hitting this selection very rapidly - i.e. Select # the entry and lean on the Enter key. The try/except # prevents the error message (which is benign) from ever # appearing on stdout. try: WrnMsg(wLINKBACK % (UI.CurrentDir + selected[:-1])) except: pass return # Build full path name selected = os.path.join(os.path.abspath(UI.CurrentDir), selected) # Convert ending ".." into canonical path if selected.endswith(".."): selected = PSEP.join(selected.split(PSEP)[:-2]) # Need to end the directory string with a path # separator character so that subsequent navigation # will work when we hit the root directory of the file # system. In the case of Unix, this means that # if we ended up at the root directory, we'll just # get "/". In the case of Win32, we will get # "DRIVE:/". if selected[-1] != PSEP: selected += PSEP # Load UI with new directory LoadDirList(selected, save=TRUE) # Indicate that we entered a new directory this way. # This is a workaround for Tk madness. When this # routine is invoked via the mouse, Tk sets the # activation *when this routine returns*. That means # that the activation set at the end of LoadDirList # gets overidden. We set this flag here so that # we can subsequently do the right thing in our # background polling loop. Although this routine # can also be invoked via a keyboard selection, # we run things this way regardless since no harm # will be done in the latter case. UI.MouseNewDir = TRUE # No, a *file* was selected with a double-click # We know what to do on Win32 and Unix. We ignore # the selection elsewhere. elif OSNAME == 'nt': ExecuteCommand(os.path.join(os.path.abspath(UI.CurrentDir), selected), '', ResolveBuiltIns=FALSE, UseStartDir=TRUE) elif OSNAME == 'posix': ExecuteCommand(os.path.join(os.path.abspath(UI.CurrentDir), selected), '') return 'break' # End of 'DirListHandler()' ##### # Event Handler: Handler Function Keys ##### def DirSCKeyPress(event, index): # Process the keypress dir = UI.DirSCKeys[index-1] if dir: LoadDirList(dir) # Inhibit further processing of key - some Function Keys # have default behavior in Tk which we want to suppress. return "break" # End of 'DirSCKeyPress()' #-------------------- Memory Features --------------------# ##### # Event Handler: Menu-Related Features Handled By Single Handler ##### def KeyMemHandler(mem, clear=FALSE): global UI # Clearing Memory if clear: # Clear all if mem == NUMPROGMEM + 1: UI.ProgMem = [[], [], [], [], [], [], [], [], [], [], [], []] # Clear specific location if 0 < mem < NUMPROGMEM + 1: UI.ProgMem[mem-1] = [] # Saving to memory else: for x in UI.AllSelection(): UI.ProgMem[mem-1].append(StripPSEP(os.path.abspath(x))) if DEBUGLEVEL & DEBUGMEM: if 0 < mem < NUMPROGMEM + 1: PrintDebug(dMEM % mem, UI.ProgMem[mem-1]) else: PrintDebug(dMEMALL, UI.ProgMem) # Inhibit further processing of keystroke so Tkinter # defaults like Alt-F10 don't take hold. return "break" # End of 'KeyMemHandler() #------------------ Sorting Features --------------------# ##### # Event Handler: Set Sort Parameters ##### def KeySetSortParm(parm): global SORTBYFIELD, SORTREVERSE, SORTSEPARATE # Which entry in the Name2Key refresh = FALSE if parm == fREVERSE: SORTREVERSE = not SORTREVERSE refresh = TRUE # Separate Dirs/Files Means Nothing In Drive List View - Suppress # this there to avoid an unnecessary refresh elif (parm == fSEPARATE): if (UI.CurrentDir != SHOWDRIVES): SORTSEPARATE = not SORTSEPARATE refresh = TRUE # Sort By Selected Parameter # In Drive List View only respond to those keys that have a # corresponding Sort Menu Entry else: # First determine whether we even want to act on this keystroke. # A given keystroke is ignored if the Name of the sort option associated # with the current view is None (the Python value None, not the string 'None'). # Get index of desired entry in Name2Key tuple if UI.CurrentDir == SHOWDRIVES: idx = 2 else: idx = 1 # Get the tuple associated with new key n2k = Name2Key[parm.lower()] # Get the name associated with the new key appropriate for the current view sortname = n2k[idx] # If the new key is the same as the old key, ignore - ignore, we're already # sorted this way. This avoids unnecessary refreshes. # # If the new key is (Python) None, it means we do not want to process this # keystoke, so ignore it. if (parm == SORTBYFIELD) or not sortname: return # Go ahead and do the new sort else: SORTBYFIELD = parm refresh = TRUE if refresh: LoadHelpMenu() RefreshDirList() return 'break' #End of 'KeySetSortParm()' #-------------- Handler Utility Functions -----------------# ##### # Event Handler: Popup Menus ##### def PopupMenu(menu, x, y): # Popup requested menu at specified coordinates # but only if the menu has at least one entry. if menu.index(END): menu.tk_popup(x, y) # End of 'PopupMenu()' ##### # Execute A Command ##### def ExecuteCommand(cmd, name, UseStartDir=FALSE, ResolveVars=FALSE, ResolveBuiltIns=TRUE, SaveUnresolved=FALSE): global UI # Do nothing on blank commands if not cmd.strip(): return # Work with a copy of the passed command newcmd = cmd # Process references to any Built-In variables if ResolveBuiltIns: newcmd = ProcessBuiltIns(cmd, name) # Replace references to any Environment or User-Defined variables # but only when asked to. - i.e., The command was manually # entered by the user and may contain unresolved variables. # (Commands the user defined in the configuration file # already have their variables dereferenced when that file is # read and parsed.) if newcmd and ResolveVars: newcmd = ProcessVariables(newcmd, 0 , name) # Just dump command if we're debugging if DEBUGLEVEL & DEBUGCMDS: PrintDebug(dCMD, [newcmd,]) # A null return value means there was a problem - abort # Otherwise,actually execute the command. elif newcmd: # Run the command on Win32 using filename associations if UseStartDir: try: os.startfile(newcmd) except: WrnMsg(wBADEXE % newcmd) # Normal command execution for both Unix and Win32 else: try: if (OSNAME == 'posix') and not USETHREADS: os.system(newcmd + " &") else: thread.start_new_thread(os.system, (newcmd,)) except: WrnMsg(wBADEXE % newcmd) # Update the Command History observing MAXMENU # In most cases, we want to save the command with all the # variables (Built-In, Environment, User-Defined) resolved (dereferenced). # However, sometimes (e.g. manual command entry via KeyRunCommand()) we # want to save the *unresolved* version. if SaveUnresolved: savecmd = cmd else: savecmd = newcmd UpdateMenu(UI.HistBtn, UI.CmdHist, MAXMENU, MAXMENUBUF, KeyRunCommand, newentry=savecmd, fakeevent=TRUE) # Dump Command History stack if requested if DEBUGLEVEL & DEBUGHIST: PrintDebug(dHIST, UI.CmdHist) # End of 'ExecuteCommand() ##### # Load UI With Selected Directory ##### def LoadDirList(newdir, save=TRUE): # Make sure we're permitted to navigate - we have to allow initial entry into STARTDIR if NONAVIGATE and (newdir != STARTDIR): return # Transform double forward-slashes into a single # forward-slash. This keeps the Directory Stack # and Visited lists sane under Unix and prevents # Win32 from attempting to enter a Drive List View # when the user enters this string but Win32All has # not been loaded. if newdir == '//': newdir = '/' # Get path into canonical form unless we're trying # to display a Win32 Drive List if newdir != SHOWDRIVES: newdir = os.path.abspath(newdir) # Make sure newdir properly terminated if newdir[-1] != PSEP: newdir += PSEP # User has requested a Drive List View. Make sure we're # running on Win32 and see the feature is available. If # not available (which means Win32All is not installed), # just ignore and return, doing nothing. elif OSNAME != 'nt' or not GetWin32Drives(): return # Check right now to see if we can read # the directory. If not, at least we # haven't screwed up the widget's current # contents or program state. try: contents = BuildDirList(newdir) except: # If CurrentDir set, we're still there: error w/ recovery if UI.CurrentDir: ErrMsg(eDIRRD % newdir) return # If not, we failed on the initial directory: error & abort else: ErrMsg(eINITDIRBAD % newdir) sys.exit(1) # Push last directory visited onto the visited stack # We do NOT save this to the stack if: # # 1) We've been told not to. - Passed when we're called (save=FALSE). # 2) If we're trying to move into the current directory again. # This can happen either when the user does a manual directory # change or if they press ".." while in root. We don't # actually want to save the directory until we *leave* it, # otherwise we'll end up with a stack top and current # directory which are the same, and we'll have to go # back *twice* to move to the previous directory. # Are we trying to move back into same directory? if os.path.abspath(UI.CurrentDir) == os.path.abspath(newdir): save = FALSE # Now save if we're supposed to. if save and UI.CurrentDir: UI.LastDir.append(UI.CurrentDir) # Dump directory stack if debug requested it if DEBUGLEVEL & DEBUGDIRS: PrintDebug(dDIRSTK, UI.LastDir) # And select new directory to visit UI.CurrentDir = newdir # Wait until we have exclusive access to the widget while not UI.DirListMutex.testandset(): pass # Clear out the old contents UI.DirList.delete(0,END) # Load new directory contents into UI for x in contents: UI.DirList.insert(END, x) # Also move the program context to the new directory # for everything except a Drive List View. In that case # the program context remains in the directory from # which the Drive List View was selected if newdir != SHOWDRIVES: os.chdir(newdir) # Keep list of all unique directories visited in the Directory Menu UpdateDirMenu(newdir) # Nothing should be pre-selected in the new directory listing KeySelNone(None) # Update Help Menu to reflect whether we're in Normal or Drive List View LoadSortMenu() # Update Titlebar to reflect any changes UI.UpdateTitle(UIroot) #Release the lock UI.DirListMutex.unlock() # End of 'LoadDirList(): ##### # Return Ordered List Of Directories & Files For Current Root # Posts A Warning Message If SORTBYFIELD Is Out Of Range ##### def BuildDirList(currentdir): global UI, SORTBYFIELD # Check to see if SORTBYFIELD makes sense if SORTBYFIELD.lower() not in Name2Key.keys(): default = UI.OptionsString["SORTBYFIELD"] WrnMsg(wBADSORTFLD % (SORTBYFIELD, default)) SORTBYFIELD = default LoadHelpMenu() UI.TotalSize = 0 fileinfo = [] dKeys, fKeys = {}, {} # Indicate where in each display string the actual file name # can be found. This is used both in the code in this routine # and other places in the program where just the file name is # needed - for example, when we want to execute it or replace it # in one of the built-ins. This value depends on whether or # not we are viewing details or not. if UI.DetailsOn: if currentdir == SHOWDRIVES: UI.NameFirst = SZ_DRIVE_TOTAL else: UI.NameFirst = ST_SZTOTAL else: UI.NameFirst = 0 # Two possible cases have to be handled: # A normal directory read and a Drive List View # under Win32. # Normal directory reads if currentdir != SHOWDRIVES: # Get and sort directory contents filelist = os.listdir(currentdir) keyindex = Name2Key[SORTBYFIELD.lower()][0] dList, fList = [], [] for x in range(len(filelist)): # Get File/Dir name file = filelist[x] # Get detail display string as well as individual fields detail, fields = FileDetails(file, currentdir) # Add trailing path separator to directory entries if detail[0] == ST_SPECIALS["04"]: file += PSEP detail += PSEP # Decide whether we have to sort or not, act accordingly if SORTBYFIELD.lower() == fNONE.lower(): # Nope, no sorting, just load-up the return data structures # with either the file name or the details string depending # on whether or not we're displaying details at the moment. if UI.DetailsOn: dList.append(detail) else: dList.append(file) # Yup, we're going to sort later - build index/return data structures else: # Get the key by which to sort - we decrement it by one because # the user expresses it as a 1-relative value. # Collapse case on Win32 when sorting by name sortkey = fields[keyindex] if OSNAME == 'nt' and (SORTBYFIELD.lower() == fNAME.lower()): sortkey = sortkey.lower() # Keep track of the file name and its details, separating directories from files # Build corresponding key list for sorting fileinfo.append((file, detail)) if detail[0] == ST_SPECIALS["04"]: dKeys.setdefault(sortkey, []).append(x) else: fKeys.setdefault(sortkey, []).append(x) # If sorting has been requested, do so now if SORTBYFIELD.lower() != fNONE.lower(): # Sort keys according to user's desire for Dir/File separation if SORTSEPARATE: dk = dKeys.keys() dk.sort() fk = fKeys.keys() fk.sort() else: # Combine the two key dicts into one composite # dictionary (dKeys) for key in fKeys.keys(): for val in fKeys[key]: dKeys.setdefault(key, []).append(val) dk = dKeys.keys() dk.sort() fk = [] # Reverse the sorts, if requested if SORTREVERSE: dk.reverse() fk.reverse() # Build corresponding dir/file lists, observing user's # request for detail information for x in dk: for index in dKeys[x]: if UI.DetailsOn: dList.append(fileinfo[index][1]) else: dList.append(fileinfo[index][0]) for x in fk: for index in fKeys[x]: if UI.DetailsOn: fList.append(fileinfo[index][1]) else: fList.append(fileinfo[index][0]) # Sorting logic ends here # Now return results in their final form if UI.DetailsOn: dotdot = [FileDetails(".." + PSEP, currentdir)[0],] else: dotdot = [".." + PSEP,] # Return the results # Entry to move up one directory is always first, # no matter what the sort. This is necessary because # OSs like Win32 like to use '$' in file names which # sorts before "." if SORTREVERSE: return dotdot + fList + dList else: return dotdot + dList + fList # The user requested Drive List View. else: dlv = {} dList = [] # There are more keys in normal view than in Drive List View # If the user has selected one of these, map them to sort # by name in Drive List View. dlvkey = Name2Key[SORTBYFIELD.lower()][0] if dlvkey > MAXDLVKEY: dlvkey = MAXDLVKEY drivelist = GetWin32Drives() # Create a detailed entry for each drive on the system # Store it as a string in the details list which will # then be returned to the caller for drive in drivelist: fields = [] # Drive Label - Drive Might Not Be Available try: label = GetVolumeInformation(drive)[0] except: label = NODRIVE if not label: label = NOLABEL # Type Of Drive - We need this now to get hostname drivetype = GetDriveType(drive) typestring = '' for type, stype in win32all_type: if drivetype == type: typestring = stype # Machine Hosting The Drive if drivetype == win32con.DRIVE_REMOTE: name = WNetGetUniversalName(drive, 1) else: name = label entry = PadString(name, SZ_DRIVE_SHARE) fields.append(name) # Add the drive type entry += PadString(typestring, SZ_DRIVE_TYPE) fields.append(typestring) # Get Drive Space Information - Drive Might Not Be Available try: clustersz, sectorsz, freeclusters, totalclusters = GetDiskFreeSpace(drive) # Free Space fspace = clustersz * sectorsz * freeclusters freespace = FileLength(fspace) # Total Space tspace = clustersz * sectorsz * totalclusters totalspace = FileLength(tspace) except: fspace, tspace = (0, 0) freespace, totalspace = ('0', '0') freespace = PadString(freespace, SZ_DRIVE_FREE, Rjust=TRUE) totalspace = PadString(totalspace, SZ_DRIVE_TTL, Rjust=TRUE) entry += "%s%s%s%s" % (freespace, WIN32FREE, totalspace, WIN32TOTAL) fields.append(fspace) fields.append(tspace) # Finally, tack on the drive letter entry += drive fields.append(drive) # If we're not going to sort later, just build the list # of drives now if SORTBYFIELD.lower() == fNONE.lower(): if UI.DetailsOn: dList.append(entry) else: dList.append(drive) # Nope, prepare to sort later else: idx = fields[dlvkey] dlv.setdefault(idx,[]).append((drive, entry)) # Now that we've built the list, sort by indicated parameter # if user has so indicated if SORTBYFIELD.lower() != fNONE.lower(): indexes = dlv.keys() indexes.sort() if SORTREVERSE: indexes.reverse() # Now build output list in sorted order for x in indexes: for entry in dlv[x]: if UI.DetailsOn: dList.append(entry[1]) else: dList.append(entry[0]) # Return the list return dList # End of 'BuildDirList()' ##### # Get Details For A File Or Directory # Returns Both A Formatted Display String With Detail Information, # And A List Containing The Individual Detail Fields ##### def FileDetails(name, currentdir): details = "" fields = [] # Condition the name fn = os.path.join(currentdir, name) if fn[-1] == PSEP: fn =fn[:-1] # Get file details from OS try: stinfo = os.lstat(fn) # 'lstat' failed - provide entry with some indication of this except: pad = (UI.NameFirst - len(iNOSTAT) - 1) * " " details = pad + iNOSTAT + " " + name fields = ["-" * 10, 0, UNAVAILABLE, UNAVAILABLE, 0L, 0, name] # Done with this file return details, fields # Do Win32-specific mode if win32all is loaded if WIN32ALL and USEWIN32ALL and WIN32ALLON: modlen = len(win32all_mode) try: win32stat = GetFileAttributes(fn) mode = "" except: mode = UNAVAILABLE if not mode: # Test each attribute and set symbol in respective # position, if attribute is true. for i in range(modlen): mask, sym = win32all_mode[i] if win32stat & mask: mode += sym else: mode += '-' # We're either on Unix or Win32 w/o win32all available else: # Mode - 1st get into octal string mode = stinfo[ST_MODE] modestr = str("%06o" % mode) # Set the permission bits mode = "" for x in [-3, -2, -1]: mode += ST_PERMIT[int(modestr[x])] # Deal with the special permissions sp = int(modestr[-4]) # Sticky Bit if sp & STICKY_MASK: if mode[-1] == "x": mode = mode[:-1] + "t" else: mode = mode[:-1] + "T" # Setgid Bit if sp & SETGID_MASK: if mode[-4] == "x": mode = mode[:-4] + "s" + mode[-3:] else: mode = mode[:-4] + "S" + mode[-3:] # Setuid Bit if sp & SETUID_MASK: if mode[-7] == "x": mode = mode[:-7] + "s" + mode[-6:] else: mode = mode[:-7] + "S" + mode[-6:] # Pickup the special file types mode = ST_SPECIALS.get(modestr[0:2], "?") + mode # Pad the result for column alignment details += PadString(mode, ST_SZMODE) fields.append(mode) # Number of links to entry nlinks = stinfo[ST_NLINK] details += PadString(str(nlinks), ST_SZNLINK) fields.append(nlinks) # Get first ST_SZxNAME chars of owner and group names on unix if OSNAME == 'posix': # Convert UID to name, if possible try: owner = pwd.getpwuid(stinfo[ST_UID])[0][:ST_SZUNAME-1] # No valid name associated with UID, so use number instead except: owner = str(stinfo[ST_UID]) # Convert GID to name, if possible try: group = grp.getgrgid(stinfo[ST_GID])[0][:ST_SZGNAME-1] # No valid name associated with GID, so use number instead except: group = str(stinfo[ST_GID]) # Handle Win32 systems elif OSNAME == 'nt': # Defaults owner = WIN32OWNER group = WIN32GROUP if WIN32ALL and USEWIN32ALL and WIN32ALLON: try: # Get the internal Win32 security information for this file. ho = GetFileSecurity(fn, OWNER_SECURITY_INFORMATION) hg = GetFileSecurity(fn, GROUP_SECURITY_INFORMATION) sido = ho.GetSecurityDescriptorOwner() sidg = hg.GetSecurityDescriptorGroup() # We have to know who is hosting the filesytem for this file drive = fn[0:3] if GetDriveType(drive) == win32con.DRIVE_REMOTE: fnhost = WNetGetUniversalName(drive, 1).split('\\')[2] else: fnhost = WIN32HOST # Now we can translate the sids into names owner = LookupAccountSid(fnhost, sido)[0] group = LookupAccountSid(fnhost, sidg)[0] # We assume the values are unavailable on any error except: owner = UNAVAILABLE group = UNAVAILABLE # Default names for all other OSs else: owner = OSNAME + FILEOWNER group = OSNAME + FILEGROUP # Add them to the detail details += PadString(owner, ST_SZUNAME) details += PadString(group, ST_SZGNAME) # Add them to the fields fields.append(owner) fields.append(group) # Length rlen = stinfo[ST_SIZE] flen = FileLength(rlen) details += PadString(flen, ST_SZLEN) fields.append(rlen) UI.TotalSize += rlen # mtime rawtime = stinfo[ST_MTIME] # Get the whole time value ftime = time.ctime(rawtime).split()[1:] # Pad single-digit dates with leading space if len(ftime[1]) == 1: ftime[1] = " " + ftime[1] # Drop the seconds ftime[-2] = ":".join(ftime[-2].split(":")[:-1]) # Turn into a single string ftime = " ".join(ftime) details += PadString(ftime, ST_SZMTIME) fields.append(rawtime) # Add the File Name details += name fields.append(name) # Include symlink details as necessary if details[0] == 'l': # If the symlink points to a file # in the same directory, just show # the filename and not the whole path f = os.path.realpath(currentdir + name) r = os.path.split(f) if r[0] == currentdir[:-1]: f = r[1] details += SYMPTR + f return details, fields # End of 'FileDetails()' ##### # Process A Command Line Containing Built-In Variables ##### def ProcessBuiltIns(cmd, name): # First do any prompting required for promptvar, handler, replace in ((YESNO, askyesno, FALSE), (PROMPT, askstring, TRUE)): for x in range(cmd.count(promptvar)): b = cmd.find(promptvar) e = cmd.find("]", b) prompt = cmd[b + len(promptvar):e] val = handler(name, prompt) # Make sure our program gets focus back UI.DirList.focus() if val: if replace: cmd = cmd.replace(cmd[b:e+1], QUOTECHAR + val + QUOTECHAR) else: cmd = cmd.replace(cmd[b:e+1], '') # Null input means the command is being aborted else: return # Now do files & directories # Strip trailing path separators in each case to # give the command author the maximum flexibility possible # We have to treat built-ins specially if we're in a Drive List View # Most noteably, there is no meaning to [DIR] in this context if UI.CurrentDir == SHOWDRIVES: currentdir = "" else: currentdir = UI.CurrentDir cmd = cmd.replace(DIR, QUOTECHAR + StripPSEP(currentdir) + QUOTECHAR) selection = StripPSEP(UI.LastInSelection()) dselection = "" if selection: dselection = QUOTECHAR + currentdir + selection + QUOTECHAR selection = QUOTECHAR + selection + QUOTECHAR cmd = cmd.replace(SELECTION, selection) cmd = cmd.replace(DSELECTION, dselection) selections = "" dselections = "" for selected in UI.AllSelection(): # Fill the various built-ins selected = StripPSEP(selected) dselections += QUOTECHAR + currentdir + selected + QUOTECHAR + " " selections += QUOTECHAR + selected + QUOTECHAR + " " cmd = cmd.replace(SELECTIONS, selections) cmd = cmd.replace(DSELECTIONS, dselections) cmd = cmd.replace(HASH, COMMENT) # Process references to program memories for x in range(NUMPROGMEM): # Only do this if there is a reference to the memory in # question. This keeps the program from needlessly # churning on long lists of memory contents which are not needed. vblref = "[MEM%s]" % str(x+1) if cmd.count(vblref): s = "" for m in UI.ProgMem[x]: s += QUOTECHAR + m + QUOTECHAR + " " cmd = cmd.replace(vblref, s) return cmd # End of 'ProcessBuiltIns()' ##### # Process/Replace References To User-Defined & Environment Variables ##### def ProcessVariables(cmd, num, line): doeval = TRUE depth = 0 while doeval: # Bound the number of times we can nest a definition # to prevent self-references which give infinite nesting depth depth += 1 if (depth > MAXNESTING): doeval = FALSE # See if there are still unresolved variable references. # If so, let the user know if REVAR.findall(cmd): WrnMsg(wVBLTOODEEP % (num, cmd)) return "" # Get a list of variable references vbls = REVAR.findall(cmd) # Throw away references to Built-In Variables - these are # processed at runtime and should be left alone here. # Note that we iterate over a *copy* of the variables # list, because we may be changing that list contents # as we go. i.e., It is bogus to iterate over a list # which we are changing during the iteration. for x in vbls[:]: # Ignore references to Built-In Variables here - They are # processed at runtime. if UI.BuiltIns.has_key(x): vbls.remove(x) elif x.startswith(PROMPT): vbls.remove(x) elif x.startswith(YESNO): vbls.remove(x) if vbls: for x in vbls: vbl = x[1:-1] # Process ordinary variables if UI.SymTable.has_key(vbl): cmd = cmd.replace(x, UI.SymTable[vbl]) # Process environment variables. # If an environment variable is referenced, # but not defined, this is a fatal error elif vbl[0] == ENVVBL: envvbl = os.getenv(vbl[1:]) if envvbl: cmd = cmd.replace(x, envvbl) else: WrnMsg(wBADENVVBL % (num, x, line)) return "" # Process references to undefined variables else: WrnMsg(wUNDEFVBL % (num, x, line)) return "" # No substitutions left to do else: doeval = FALSE return cmd # End of 'ProcessVariables()' ##### # Refresh Contents Of Directory Listing To Stay In Sync With Reality ##### def RefreshDirList(*args): # Wait until we have exclusive access to the widget while not UI.DirListMutex.testandset(): pass # Get current selection and active sellist = UI.DirList.curselection() active = UI.DirList.index(ACTIVE) # Save current scroll positions xs = UI.hSB.get() ys = UI.vSB.get() # Clean out old listbox contents UI.DirList.delete(0,END) # Save the new directory information UI.DirList.insert(0, *BuildDirList(UI.CurrentDir)) # Restore selection(s) UI.SetSelection(sellist, active) # Restore scroll positions UI.DirList.xview(MOVETO, xs[0]) UI.DirList.yview(MOVETO, ys[0]) # Update titlebar to reflect any changes UI.UpdateTitle(UIroot) # Release the mutex UI.DirListMutex.unlock() return 'break' # End of 'RefreshDirList() #---------------- Menu Support Functions ------------------# ##### # Handle Command Menu Selections ##### def CommandMenuSelection(cmdkey): class event: pass event.state = 0 event.char = cmdkey KeystrokeHandler(event) # End Of 'CommandMenuSelection()' ##### # Process Options ##### def ProcessOptions(): ##### # Setup The GUI Visual Parameters, Menus, & Help Information ##### SetupGUI() ##### # Reflect Our Changes In The Interface ##### RefreshDirList() ##### # Dump requested debug information ##### # Keyboard Assignments if DEBUGLEVEL & DEBUGKEYS: # Keyboard Bindings PrintDebug(dKEYBINDS, GetKeyBindings()) # Function Keys (Directory Shortcuts) PrintDebug(dFUNCKEYS, GetDirShortcuts()) # User-Defined Variables if DEBUGLEVEL & DEBUGSYMS: PrintDebug(dSYMTBL, GetUserVbls()) # Command Definitions if DEBUGLEVEL & DEBUGCTBL: PrintDebug(dCMDTBL, GetCommandTable()) # Internal Program Variables AndOptions if DEBUGLEVEL & DEBUGVARS: # Internal variabled PrintDebug(dINTVAR, GetIntVars()) # User-Settable options PrintDebug(dOPTVAR, GetOptions()) # If we just wanted debug output, quit now if DEBUGLEVEL & DEBUGQUIT: sys.exit() # End of 'ProcessOptions()' ##### # Add An Entry To The List Of All Directories Visited # Do this only if it is not already on the list and # observe the MAXMENU variable to keep list length bounded. ##### def UpdateDirMenu(newdir): global UI # Win32 collapses case so that 'x' and 'X' refer to the same # directory. We want to preserve this in the user's display # but we have to collapse case for the purposes of doing our # checks below otherwise the same directory with different # capitalization (if the user manually enteres it that way) # can appear twice in the Directory Menu addentry = FALSE if OSNAME == 'nt': # First make a case-collapsed copy of the existing list dlc = [] [dlc.append(d.lower()) for d in UI.AllDirs] # Now see if our new entry is already there if newdir.lower() not in dlc: addentry = TRUE elif newdir not in UI.AllDirs: addentry = TRUE # Now add the entry if we decided it was necessary. observing MAXMENU value. if addentry: UpdateMenu(UI.DirBtn, UI.AllDirs, MAXMENU, MAXMENUBUF, LoadDirList, sort=TRUE, newentry=newdir) # End of 'UpdateDirMenu()' ##### # Generic Menu Update Routine ##### def UpdateMenu(menubtn, datastore, max, maxbuf, func, sort=FALSE, newentry="", fakeevent=FALSE): # First add the new data, if any, to the specified data storage stucture. if newentry: datastore.append(newentry) # Now trim it to requested maximum length. 'max' sets how # many entries we see in the menu, and 'maxbuf' sets how large # the actual storage buffer is allowed to get. datastore[:] = datastore[-maxbuf:] # We do not sort the data storage itself if sorting has been requested. # We sort the *copy*. That way we get the 'last _max_ items in # sorted order.' (If we sorted the master copy, we would lose # track of the order in which things were placed there.) data = datastore[:] if not max: data = [] elif len(data) > max: data = datastore[-max:] # Initialize the menu to empty menubtn.menu.delete(0,END) menubtn.config(state=DISABLED) if len(data): if sort: data.sort() for entry in data: if fakeevent: menubtn.menu.add_command(label=entry, command=lambda item=entry: func(None, item)) else: menubtn.menu.add_command(label=entry, command=lambda item=entry: func(item)) menubtn['menu'] = menubtn.menu # Menu now has content, enable it menubtn.config(state=NORMAL) # End of 'UpdateMenu()' #---------------- Debug Support Functions -----------------# ##### # Return List Of Command Table Entries ##### def GetCommandTable(): debuginfo = [] for key in UI.CmdTable.keys(): name = UI.CmdTable[key][0] cmd = UI.CmdTable[key][1] debuginfo.append(PadString(key + " " + name, dCMDWIDTH) + cmd) debuginfo.sort() return debuginfo # End of 'GetCommandTable()' ##### # Return List Of Current Directory Shortcuts ##### def GetDirShortcuts(): debuginfo = [] for x in range(len(UI.DirSCKeys)): key = "F" + str(x+1) path = UI.DirSCKeys[x] if path: debuginfo.append(PadString(key, dSCWIDTH) + path) return debuginfo # End of 'GetDirShortcuts()' ##### # Return List Of Internal Variables ##### def GetIntVars(): debuginfo = [] for v in DebugVars: debuginfo.append(PadString(v, dINTVARWIDTH) + (str(eval(v)) or dNULL)) debuginfo.sort() return debuginfo # End of 'GetIntVars()' ##### # Return List Of Current Key Bindings ##### def GetKeyBindings(): debuginfo = [] # Get sorted list of all function names kb = UI.KeyBindings.keys() kb.sort() # Make sure it is of even length, so we can format 2-at-a-time if len(kb) % 2: kb.append("") # Now setup two lists - 1st and last half respectively m = len(kb)/2 l1 = kb[:m] l2 = kb[m:] # Format the bindings for output, 2 per line x = 0 while(x < m): k1 = l1[x] v1 = UI.KeyBindings[k1] k2 = l2[x] if k2: v2 = UI.KeyBindings[k2] else: v2 = "" s1 = PadString(k1, dKEYWIDTH) + v1 s2 = PadString(k2, dKEYWIDTH) + v2 debuginfo.append(PadString(s1, dTWOUPWIDTH) + s2) x += 1 debuginfo.sort() return debuginfo # End of 'GetKeyBindings()' ##### # Return List Of User-Settable Options ##### def GetOptions(): debuginfo = [] for l,f in ((UI.OptionsBoolean, True), (UI.OptionsNumeric, False), (UI.OptionsString, False)): for v in l: value = eval(v) # Translate Booleans into True/False strings if f: if value: s = dTRUE else: s = dFALSE # Translate all others into string representations else: s = str(value) debuginfo.append(PadString(v, dOPTIONWIDTH) + (s or dNULL)) debuginfo.sort() return debuginfo # End of 'GetOptions()' ##### # Return List Of User-Defined Variables ##### def GetUserVbls(): debuginfo = [] for sym in UI.SymTable.keys(): debuginfo.append(PadString(sym, dUSRVBLWIDTH) + UI.SymTable[sym]) debuginfo.sort() return debuginfo # End of 'GetUserVbls()' #----------------------------------------------------------# # Program Entry Point # #----------------------------------------------------------# ##### # Create an instance of the UI ##### UIroot = Tk() UI = twanderUI(UIroot) # Make the Tk window the topmost in the Z stack. # 'Gotta do this or Win32 will not return input # focus to our program after a startup warning # display. UIroot.tkraise() ##### # Setup global UI variables ##### # Setup Built-In Variables UI.BuiltIns = {DIR:"", DSELECTION:"", DSELECTIONS:"", HASH:"", MEM1:"", MEM2:"", MEM3:"", MEM4:"", MEM5:"", MEM6:"", MEM7:"", MEM8:"", MEM9:"", MEM10:"", MEM11:"", MEM12:"", PROMPT:"", SELECTION:"", SELECTIONS:"", YESNO:""} # Options (and their default values) which can be set in the configuration file UI.OptionsBoolean = {"AUTOREFRESH":AUTOREFRESH, "NODETAILS":NODETAILS, "NONAVIGATE":NONAVIGATE, "SORTREVERSE":SORTREVERSE, "SORTSEPARATE":SORTSEPARATE, "USETHREADS":USETHREADS, "USEWIN32ALL":USEWIN32ALL, "WARN":WARN} UI.OptionsNumeric = {"DEBUGLEVEL":DEBUGLEVEL, "FSZ":FSZ, "MFSZ":MFSZ, "HFSZ":HFSZ, "HEIGHT":HEIGHT, "MAXMENU":MAXMENU, "MAXMENUBUF":MAXMENUBUF, "MAXNESTING":MAXNESTING, "REFRESHINT":REFRESHINT, "STARTX":STARTX, "STARTY":STARTY, "WIDTH":WIDTH} UI.OptionsString = {"BCOLOR":BCOLOR, "FCOLOR":FCOLOR, "FNAME":FNAME, "FWT":FWT, # Main Font/Colors "MBCOLOR":MBCOLOR, "MFCOLOR":MFCOLOR, "MFNAME":MFNAME, "MFWT":MFWT, # Menu Font/Colors "HBCOLOR":HBCOLOR, "HFCOLOR":HFCOLOR, "HFNAME":HFNAME, "HFWT":HFWT, # Help Font/Colors "MBARCOL":MBARCOL, "QUOTECHAR":QUOTECHAR, "SORTBYFIELD":SORTBYFIELD, # Other "STARTDIR":STARTDIR, "CMDSHELL":CMDSHELL} # Prepare storage for key bindings UI.KeyBindings = {} # Initialize list of all directories visited UI.AllDirs = [] # Initialize directory stack UI.LastDir = [] # Initialize storage for last manually entered directory path UI.LastPathEntered = "" # Initialize storage for last manually entered selection wildcard UI.LastSelWildcard = "" # And current location UI.CurrentDir = "" # Initialize various menu data structures ClearHistory(None) # Initialize Storage For Program Memories UI.ProgMem = [[], [], [], [], [], [], [], [], [], [], [], []] # Need mutex to serialize on widget updates UI.DirListMutex = mutex.mutex() # Intialize the "new dir via mouse" flag UI.MouseNewDir = FALSE # Initialize the polling counter UI.ElapsedTime = 0 # Start in detailed mode UI.SetDetailedView(TRUE) ##### # Command line processing - Options are set with the # following priority scheme (lowest to highest): # # 1) Defaults coded into the program # 2) Options set in the configuration file # 3) Options set in the environment variable # 4) Options set on the command line # ##### # Concatenate any environment variable with the # command line so the command line takes precedence. OPTIONS = sys.argv[1:] envopt = os.getenv(PROGNAME.upper()) if envopt: OPTIONS = envopt.split() + OPTIONS try: opts, args = getopt.getopt(OPTIONS, '-c:d:hqrtv') except getopt.GetoptError: Usage() sys.exit(1) # If the user wants help or version information, do this first # so we don't bother with other options processing for opt, val in opts: if opt == "-h": Usage() sys.exit(0) if opt == "-v": print RCSID sys.exit(0) # Read configuration file before any other arguments. This allows the # environment variable and then the command line to override any # settings in the configuration file. for opt, val in opts: if opt == "-c": CONF = os.path.abspath(val) # Parse the configuration file, but suppress options # processing - on startup this is done just before # we enter the main message loop to make sure we # pickup any options changes from the environment # variable or command line. ProcessConfiguration(None, DoOptionsProcessing=FALSE) # Process the rest of the options, if any for opt, val in opts: if opt == "-d": DEBUGLEVEL = val ValidateDebugLevel() # If we're going to be dumping debug info, print header if DEBUGLEVEL: print dHEADER % time.asctime() if opt == "-q": WARN = FALSE if opt == "-r": AUTOREFRESH = FALSE if opt == "-t": QUOTECHAR = "" if opt == "-x": WIDTH = val if opt == "-y": HEIGHT = val # Figure out where to start - Environment/Command Line overrides config # file STARTDIR option. Program can only have 0 or 1 arguments. Make # sure any startdir argument is legit if len(args) > 1: ErrMsg(eTOOMANY) sys.exit(1) if len(args) == 1: STARTDIR = args[0] # Windows is sloppy about accepting both '//' and '\\' # so we have to condition the command line input for consistency. if OSNAME == 'nt' and STARTDIR == '//': STARTDIR = SHOWDRIVES # Make sure any user request to start in a Drive List View # is possible. If not, just start in the root directory. if STARTDIR == SHOWDRIVES and (OSNAME != 'nt' or not GetWin32Drives()): STARTDIR = PSEP if not os.path.isdir(STARTDIR): ErrMsg(eBADROOT % STARTDIR) sys.exit(1) # Save this value as the default for STARTDIR UI.OptionsString["STARTDIR"] = STARTDIR # Get starting directory into canonical form STARTDIR = os.path.abspath(STARTDIR) # Initialize the UI directory listing LoadDirList(STARTDIR) # Process options to catch any changes detected in # environment variable or command line ProcessOptions() # And start the periodic polling of the widget UI.poll() # Run the program interface UIroot.mainloop()