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