diff --git a/twander.py b/twander.py index aa2ddfb..60cbe18 100755 --- a/twander.py +++ b/twander.py @@ -6,7 +6,7 @@ # Program Information PROGNAME = "twander" -RCSID = "$Id: twander.py,v 3.107 2003/02/26 04:39:33 tundra Exp $" +RCSID = "$Id: twander.py,v 3.108 2003/02/26 23:24:16 tundra Exp $" VERSION = RCSID.split()[2] # Copyright Information @@ -160,7 +160,7 @@ MEMCLRALL = '' # Clear all memories -MEMSET1 = '' # Set program memories +MEMSET1 = '' # Set program memories MEMSET2 = '' MEMSET3 = '' MEMSET4 = '' @@ -173,6 +173,18 @@ MEMSET11 = '' MEMSET12 = '' +# Sorting Keys + +SORTBY0 = '' # Select sorting paramters +SORTBY1 = '' +SORTBY2 = '' +SORTBY3 = '' +SORTBY4 = '' +SORTBY5 = '' +SORTBY6 = '' +SORTBY7 = '' +SORTREV = '' +SORTSEP = '' ##### # GUI Defaults @@ -256,7 +268,7 @@ # Defaults ##### -AUTOREFRESH = TRUE # Automatically refresh the directory display +AUTOREFRESH = TRUE # Automatically refresh the directory display? CMDSHELL = "" # No CMDSHELL processing DEBUGLEVEL = 0 # No debug output MAXMENU = 32 # Maximum length of displayed menu @@ -266,11 +278,12 @@ NONAVIGATE = FALSE # TRUE means that all directory navigation is prevented REFRESHINT = 3000 # Interval (ms) for automatic refresh QUOTECHAR = '\"' # Character to use when quoting Built-In Variables -SORTREVERSE = FALSE # Reverse specified sort order? SORTBYFIELD = 7 # Field to use as sort key -USETHREADS = TRUE # Use threads on Unix -USEWIN32ALL = TRUE # Use win32all features if available -WARN = TRUE # Warnings on +SORTREVERSE = FALSE # Reverse specified sort order? +SORTSEPARATE = TRUE # Separate Directories and Files in sorted displays? +USETHREADS = TRUE # Use threads on Unix? +USEWIN32ALL = TRUE # Use win32all features if available? +WARN = TRUE # Warnings on? WIN32ALLON = TRUE # Flag for toggling win32all features while running @@ -283,6 +296,9 @@ KB = 1024 # 1 KB constant MB = KB * KB # 1 MB constant GB = MB * KB # 1 GB constant +MAXSORTFIELD = 7 # Highest legal field # for sorting +NAMEFIELD = 7 # Field containing name for normal displays +DLVNAMEFIELD = 5 # Field containting name in Drive List View NUMFUNCKEY = 12 # Number of function keys NUMPROGMEM = 12 # Number of program memories POLLINT = 20 # Interval (ms) the poll routine should run @@ -522,6 +538,15 @@ TTLFILES = "Total Files:" TTLSIZE = "Total Size:" +TTLSORTFLD = "Sort => Fld:" +TTLSORTREV = "Rev:" +TTLSORTSEP = "Sep:" + + +# Convert Logical Values Into Yes/No String + +YesOrNo = {True:"Yes", False:"No"} + # Menu Button Titles @@ -849,6 +874,16 @@ "MEMSET10":MEMSET10, "MEMSET11":MEMSET11, "MEMSET12":MEMSET12, + "SORTBY0":SORTBY0, + "SORTBY1":SORTBY1, + "SORTBY2":SORTBY2, + "SORTBY3":SORTBY3, + "SORTBY4":SORTBY4, + "SORTBY5":SORTBY5, + "SORTBY6":SORTBY6, + "SORTBY7":SORTBY7, + "SORTREV":SORTREV, + "SORTSEP":SORTSEP, } # Set all the program options to their default values @@ -1167,10 +1202,21 @@ UpdateMenu(UI.HistBtn, UI.CmdHist, MAXMENU, MAXMENUBUF, KeyRunCommand, fakeevent=TRUE) UpdateMenu(UI.WildBtn, UI.WildHist, MAXMENU, MAXMENUBUF, KeySelWild, fakeevent=TRUE) - ##### - # Initialize Help Menu with latest information - ##### + # Initialize the Help Menu + LoadHelpMenu() + # Size and position the display + UIroot.geometry("%sx%s+%s+%s" % (WIDTH, HEIGHT, STARTX, STARTY)) + +# End of 'SetupGUI()' + + +##### +# Load Help Menu with latest information +##### + +def LoadHelpMenu(): + # Clear out existing content UI.HelpBtn.config(state=DISABLED) @@ -1210,10 +1256,8 @@ UI.HelpBtn['menu'] = UI.HelpBtn.menu UI.HelpBtn.config(state=NORMAL) - # Size and position the display - UIroot.geometry("%sx%s+%s+%s" % (WIDTH, HEIGHT, STARTX, STARTY)) +# End of 'LoadHelpMenu()' -# End of 'SetupGUI()' ##### @@ -1545,6 +1589,22 @@ 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["SORTBY0"], lambda event : KeySetSortParm(parm=0)) + self.DirList.bind(self.KeyBindings["SORTBY1"], lambda event : KeySetSortParm(parm=1)) + self.DirList.bind(self.KeyBindings["SORTBY2"], lambda event : KeySetSortParm(parm=2)) + self.DirList.bind(self.KeyBindings["SORTBY3"], lambda event : KeySetSortParm(parm=3)) + self.DirList.bind(self.KeyBindings["SORTBY4"], lambda event : KeySetSortParm(parm=4)) + self.DirList.bind(self.KeyBindings["SORTBY5"], lambda event : KeySetSortParm(parm=5)) + self.DirList.bind(self.KeyBindings["SORTBY6"], lambda event : KeySetSortParm(parm=6)) + self.DirList.bind(self.KeyBindings["SORTBY7"], lambda event : KeySetSortParm(parm=7)) + self.DirList.bind(self.KeyBindings["SORTREV"], lambda event : KeySetSortParm(parm=-1)) + self.DirList.bind(self.KeyBindings["SORTSEP"], lambda event : KeySetSortParm(parm=-2)) # Give the listbox focus so it gets keystrokes @@ -1668,9 +1728,10 @@ def UpdateTitle(self, mainwin): - mainwin.title("%s %s %s: %s %s %s %s %s" % + mainwin.title("%s %s %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))) + TTLFILES, str(self.DirList.size()), TTLSIZE, FileLength(self.TotalSize), + TTLSORTFLD, SORTBYFIELD, TTLSORTREV, YesOrNo[SORTREVERSE], TTLSORTSEP, YesOrNo[SORTSEPARATE])) # End of method 'twanderUI.UpdateTitle()' @@ -2509,6 +2570,32 @@ # End of 'KeyMemHandler() +#------------------ Sorting Features --------------------# + +##### +# Event Handler: Set Sort Parameters +##### + +def KeySetSortParm(parm): + global SORTBYFIELD, SORTREVERSE, SORTSEPARATE + + if (0 <= parm <= MAXSORTFIELD): + SORTBYFIELD = parm + + elif parm == -1: + SORTREVERSE = not SORTREVERSE + + elif parm == -2: + SORTSEPARATE = not SORTSEPARATE + + LoadHelpMenu() + RefreshDirList() + + return 'break' + +#End of 'KeySetSortParm()' + + #-------------- Handler Utility Functions -----------------# ##### @@ -2734,7 +2821,8 @@ global UI UI.TotalSize = 0 - dList, fList, detlist = [], [], [] + dDetails, fDetails, detlist = [], [], [] + dFields, fFields = [], [] # Indicate where in each display string the actual file name # can be found. This is used both in the code in this routine @@ -2761,34 +2849,50 @@ # Walk the directory separate subdirs and files for file in os.listdir(currentdir): - d, f = FileDetails(file, currentdir) - detlist.append(d) + detail, fields = FileDetails(file, currentdir) - if d[0] == 'd': - dList.append(file + PSEP) + # Track directory and file entries separately + + if detail[0] == ST_SPECIALS["04"]: + dDetails.append(detail) + dFields.append(fields) else: - fList.append(file) + fDetails.append(detail) + fFields.append(fields) - # Sort as requested + # Sort as requested - SORTBYFIELD == 0 means no sorting - return in order provided by OS + # SORTSEPARATE == True means to sort directories and files separately + # Sorting is case-insensitive on Win32 systems + + dList, fList = [], [] + if 0 < SORTBYFIELD <= MAXSORTFIELD: - dList.sort() - fList.sort() - detlist.sort() + if OSNAME == 'nt': + CollapseCase = True + else: + CollapseCase = False + + if SORTSEPARATE: + dList = SortDir(dDetails, dFields, NAMEFIELD, SORTBYFIELD, SORTREVERSE, UI.DetailsOn, CollapseCase) + fList = SortDir(fDetails, fFields, NAMEFIELD, SORTBYFIELD, SORTREVERSE, UI.DetailsOn, CollapseCase) + else: + dList = SortDir(dDetails+fDetails, dFields+fFields, NAMEFIELD, SORTBYFIELD, SORTREVERSE, UI.DetailsOn, CollapseCase) + # 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 "." - dList.insert(0, ".." + PSEP) - detlist.insert(0, FileDetails(".." + PSEP, currentdir)[0]) - - # Return appropriate list - w/ or w/o details. - if UI.DetailsOn: - return detlist + dList.insert(0, FileDetails(".." + PSEP, currentdir)[0]) else: - return dList + fList + dList.insert(0, ".." + PSEP) + + + # Return the results + + return dList + fList # The user requested Drive List View. else: @@ -3097,6 +3201,60 @@ # End of 'FileDetails()' +##### +# Sort A List Of Directories Or Files, Using A Specified Detail Field As The Key +# Case-Sensitivity Can Be Turned On- Or Off- +# Sort Can Be Normal (Ascending) Or Reversed +# Appends A Path Separator Character To Directory Names +# Returns Sorted List +##### + +def SortDir(details, fields, namefield, keyfield, Reverse, DetailsOn, CollapseCase): + + # Build a dictonary of entries ordered by key field + # We substract 1 from the passed keyfield because the code is 0-relative, + # But the user provides the field as 1-relative. + + # Because the keys are not guaranteed to be unique, each key has + # an associated *list* of entries. Each such entry associated with + # the key is a tuple in the form: (file/dir details string, file/dir name) + + dict = {} + for x in range(len(fields)): + key = fields[x][keyfield-1] + dict.setdefault(key, []).append((details[x], fields[x][namefield-1])) + + + # Sort the keys, reversing if required + + keylist = dict.keys() + keylist.sort() + if Reverse: + keylist.reverse() + + # Build Result Set, Appending Path Separators To Directory Entries + + results = [] + for key in keylist: + + # Process each entry associated with a given key + + for det, nam in dict[key]: + + # Append the path separator for directories + if det[0] == ST_SPECIALS["04"]: + det += PSEP + nam += PSEP + + if DetailsOn: + results.append(det) + else: + results.append(nam) + + return results + +# End of 'SortDir()' + ##### # Process A Command Line Containing Built-In Variables @@ -3632,7 +3790,8 @@ # Options (and their default values) which can be set in the configuration file UI.OptionsBoolean = {"AUTOREFRESH":AUTOREFRESH, "NODETAILS":NODETAILS, "NONAVIGATE":NONAVIGATE, - "SORTREVERSE":SORTREVERSE, "USETHREADS":USETHREADS, "USEWIN32ALL":USEWIN32ALL, "WARN":WARN} + "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,