Newer
Older
twander / twander.py
  1. #!/usr/bin/env python
  2. # twander - Wander around the file system
  3. # Copyright (c) 2002 TundraWare Inc. All Rights Reserved.
  4. # For Updates See: http://www.tundraware.com/Software/twander
  5.  
  6. PROGNAME = "twander"
  7. RCSID = "$Id: twander.py,v 1.93 2002/12/09 20:08:16 tundra Exp $"
  8. VERSION = RCSID.split()[2]
  9.  
  10.  
  11. #----------------------------------------------------------#
  12. # Imports #
  13. #----------------------------------------------------------#
  14.  
  15. import getopt
  16. import mutex
  17. import os
  18. import re
  19. from socket import getfqdn
  20. from stat import *
  21. import sys
  22. import thread
  23. import time
  24.  
  25. from Tkinter import *
  26. from tkMessageBox import showerror, showwarning
  27. from tkSimpleDialog import askstring
  28.  
  29. # Imports conditional on OS
  30.  
  31. # Set OS type - this allows us to trigger OS-specific code
  32. # where needed.
  33.  
  34. OSNAME = os.name
  35.  
  36.  
  37. if OSNAME == 'posix':
  38. import grp
  39. import pwd
  40.  
  41.  
  42. #----------------------------------------------------------#
  43. # Variables User Might Change #
  44. #----------------------------------------------------------#
  45.  
  46. #####
  47. # Defaults
  48. #####
  49.  
  50. #####
  51. # Key Assignments
  52. #####
  53.  
  54. # General Program Commands
  55.  
  56. KEYPRESS = '<KeyPress>' # Any keypress (for commands)
  57. QUITPROG = '<Control-q>' # Quit the program
  58. READCONF = '<Control-r>' # Re-read the configuration file
  59. REFRESH = '<Control-l>' # Refresh screen
  60. RUNCMD = '<Control-z>' # Run arbitrary user command
  61. TOGDETAIL = '<Control-t>' # Toggle detail view
  62.  
  63. # Directory Navigation
  64.  
  65. CHANGEDIR = '<Control-x>' # Enter a new path
  66. DIRHOME = '<Control-h>' # Goto $HOME
  67. DIRBACK = '<Control-b>' # Goto previous directory
  68. DIRSTART = '<Control-s>' # Goto starting directory
  69. DIRUP = '<Control-u>' # Go up one directory level
  70. MSEBACK = '<Control-Double-ButtonRelease-1>' # Go back one directory with mouse
  71. MSEUP = '<Control-Double-ButtonRelease-3>' # Go up one directory with mouse
  72.  
  73. # Selection Keys
  74.  
  75. SELALL = '<Control-comma>' # Select all items
  76. SELNEXT = '<Control-n>' # Select next item
  77. SELNONE = '<Control-period>' # Unselect all items
  78. SELPREV = '<Control-p>' # Select previous item
  79. SELEND = '<Control-e>' # Select bottom item
  80. SELTOP = '<Control-a>' # Select top item
  81. SELKEY = '<Control-space>' # Select item w/keyboard
  82. SELMOUSE = '<Double-ButtonRelease-1>' # Select item w/mouse
  83.  
  84. # Intra-Display Movement
  85.  
  86. PGDN = '<Control-v>' # Move page down
  87. PGUP = '<Control-c>' # Move page up
  88.  
  89.  
  90. #####
  91. # System-Related Defaults
  92. #####
  93.  
  94. # Default startup directory
  95. STARTDIR = "." + os.sep
  96.  
  97. # Home director
  98. HOME = os.getenv("HOME") or STARTDIR
  99.  
  100.  
  101. #####
  102. # Program Constants
  103. #####
  104.  
  105. HEIGHT = 25
  106. WIDTH = 90
  107.  
  108. #####
  109. # Colors
  110. #####
  111.  
  112. BCOLOR = "black"
  113. FCOLOR = "green"
  114.  
  115.  
  116. #####
  117. # Fonts
  118. #####
  119.  
  120. FNAME = "Courier"
  121. FSZ = 12
  122. FWT = "bold"
  123.  
  124.  
  125. #------------------- Nothing Below Here Should Need Changing ------------------#
  126.  
  127.  
  128. #----------------------------------------------------------#
  129. # Constants & Literals #
  130. #----------------------------------------------------------#
  131.  
  132.  
  133.  
  134. #####
  135. # Booleans
  136. #####
  137.  
  138. # Don't need to define TRUE & FALSE - they are defined in the Tkinter module
  139.  
  140.  
  141. #####
  142. # Defaults
  143. #####
  144.  
  145.  
  146. AUTOREFRESH = TRUE # Automatically refresh the directory display
  147. DEBUG = FALSE # Debugging on
  148. WARN = TRUE # Warnings on
  149.  
  150. #####
  151. # Constants
  152. #####
  153.  
  154. # General constants
  155.  
  156. KB = 1024 # 1 KB constant
  157. MB = KB * KB # 1 MB constant
  158. GB = MB * KB # 1 GB constant
  159. HOSTNAME = getfqdn() # Full name of this host
  160. POLLINT = 20 # Interval (ms) the poll routine should run
  161. REFRESHINT = 3000 # Interval (ms) for automatic refresh
  162.  
  163.  
  164. # Stat-related
  165.  
  166. # Permissions
  167.  
  168. ST_PERMIT = ["---", "--x", "-w-", "-wx",
  169. "r--", "r-x", "rw-", "rwx"]
  170.  
  171. # Special file type lookup
  172.  
  173. ST_SPECIALS = {"01":"p", "02":"c", "04":"d", "06":"b",
  174. "10":"-", "12":"l", "14":"s"}
  175.  
  176. # Size of each status display field including trailing space
  177.  
  178. ST_SZMODE = 11
  179. ST_SZNLINK = 5
  180. ST_SZUNAME = 12
  181. ST_SZGNAME = 12
  182. ST_SZLEN = 12
  183. ST_SZMTIME = 18
  184.  
  185. ST_SZTOTAL = ST_SZMODE + ST_SZNLINK + ST_SZUNAME + ST_SZGNAME + \
  186. ST_SZLEN + ST_SZMTIME
  187.  
  188.  
  189. # String used to separate symlink entry from its real path
  190.  
  191. SYMPTR = " -> "
  192.  
  193.  
  194. #####
  195. # General Literals
  196. #####
  197.  
  198. PSEP = os.sep # Character separating path components
  199.  
  200.  
  201. #####
  202. # Configuration File Related Literals
  203. #####
  204.  
  205.  
  206. ASSIGN = "=" # Assignment for variable definitions
  207. CONF = "" # Config file user selected with -c option
  208. COMMENT = r"#" # Comment character
  209. ENVVBL = r'$' # Symbol denoting an environment variable
  210. MAXNESTING = 32 # Maximum depth of nested variable definitions
  211. reVAR = r"\[.*?\]" # Regex describing variable notation
  212.  
  213.  
  214. # Builtins
  215.  
  216. DIR = r'[DIR]'
  217. DSELECTION = r'[DSELECTION]'
  218. DSELECTIONS = r'[DSELECTIONS]'
  219. PROMPT = r'[PROMPT]'
  220. SELECTION = r'[SELECTION]'
  221. SELECTIONS = r'[SELECTIONS]'
  222.  
  223.  
  224. #----------------------------------------------------------#
  225. # Prompts, & Application Strings #
  226. #----------------------------------------------------------#
  227.  
  228.  
  229. #####
  230. # Error, Information, & Warning Messages
  231. #####
  232.  
  233. # Errors
  234.  
  235. eBADCFGLINE = "Bogus Configuration Entry In Line %s: %s"
  236. eBADENVVBL = "Environment Variable %s In Line %s Not Set: %s"
  237. eBADROOT = " %s Is Not A Directory"
  238. eDIRRD = "Cannot Open Directory : %s --- Check Permissions."
  239. eDUPKEY = "Found Duplicate Command Key '%s' In Line %s: %s"
  240. eERROR = "ERROR"
  241. eINITDIRBAD = "Cannot Open Starting Directory : %s - Check Permissions - ABORTING!."
  242. eOPEN = "Cannot Open File: %s"
  243. eREDEFVAR = "Variable %s Redefined In Line %s: %s"
  244. eTOOMANY = "You Can Only Specify One Starting Directory."
  245. eUNDEFVBL = "Undefined Variable %s Referenced In Line %s: %s"
  246. eVBLTOODEEP = "Variable Definition Nested Too Deeply At Line %s: %s"
  247.  
  248. # Information
  249.  
  250. iNOSTAT = "Details Unavailable For This File ->"
  251.  
  252. # Prompts
  253.  
  254. pCHPATH = "Change Path"
  255. pENPATH = "Enter New Path Desired:"
  256. pRUNCMD = "Run Command"
  257. pENCMD = "Enter Command To Run:"
  258.  
  259.  
  260. # Warnings
  261.  
  262. wCMDKEY = "Configuration File Entry For: \'%s\' Has No Command Key Defined."
  263. wCONFOPEN = "Cannot Open Configuration File:\n%s\n\n%s"
  264. wNOCMDS = "Running With No Commands Defined!"
  265. wSYMBACK = " Symbolic Link %s Points Back To Own Directory"
  266. wWARN = "WARNING"
  267.  
  268.  
  269. #####
  270. # Usage Information
  271. #####
  272.  
  273. uTable = [PROGNAME + " " + VERSION + " - Copyright 2002, TundraWare Inc., All Rights Reserved\n",
  274. "usage: " + PROGNAME + " [-bcdfhnqrsvwxy] [startdir] where,\n",
  275. " startdir name of directory in which to begin (default: current dir)",
  276. " -b color background color (default: black)",
  277. " -c file name of configuration file (default: $HOME/." + PROGNAME +
  278. " or PROGDIR/." + PROGNAME + ")",
  279. " -d turn on debugging",
  280. " -f color foreground color (default: green)",
  281. " -h print this help information",
  282. " -n name name of font to use (default: courier)",
  283. " -q quiet mode - no warnings (default: warnings on)",
  284. " -r turn off automatic content refreshing (default: refresh on)",
  285. " -s size size of font to use (default: 12)",
  286. " -v print detailed version information",
  287. " -w wght weight/style of font to use (default: bold)",
  288. " -x width window width (default: 60)",
  289. " -y height window height (default: 25)",
  290. ]
  291.  
  292.  
  293. #---------------------------Code Begins Here----------------------------------#
  294.  
  295.  
  296. #----------------------------------------------------------#
  297. # General Support Functions #
  298. #----------------------------------------------------------#
  299.  
  300.  
  301. #####
  302. # Print An Error Message
  303. #####
  304.  
  305. def ErrMsg(emsg):
  306.  
  307. showerror(PROGNAME + " " + VERSION + " " + eERROR, emsg)
  308.  
  309. # End of 'ErrMsg()'
  310.  
  311.  
  312. #####
  313. # Convert A File Size Into Equivalent String With Scaling
  314. # Files under 1 MB show actual length
  315. # Files < 1 MB < 1 GB shown in KB
  316. # Files 1 GB or greater, shown in MB
  317. #####
  318.  
  319. def FileLength(flen):
  320.  
  321. if flen >= GB:
  322. flen = str(flen/MB) + "m"
  323. elif flen >= MB:
  324. flen = str(flen/KB) + "k"
  325. else:
  326. flen = str(flen)
  327.  
  328. return flen
  329.  
  330. # End of 'FileLength()'
  331.  
  332.  
  333. #####
  334. # Parse & Process The Configuraton File
  335. # This is called once at program start time
  336. # and again any time someone hits the READCONF key
  337. # while the program is running.
  338. #####
  339.  
  340. def ParseConfFile(event):
  341. global CONF, UI
  342.  
  343. # If user specified a config file, try that
  344. # Otherwise use HOME == either $HOME or ./
  345.  
  346. if not CONF:
  347. CONF = os.path.join(HOME, "." + PROGNAME)
  348. try:
  349. cf = open(CONF)
  350. except:
  351. WrnMsg(wCONFOPEN % (CONF, wNOCMDS))
  352. UI.CmdTable = {}
  353. UI.Symtable = {}
  354. return
  355.  
  356. # Successful open of config file - Begin processing it
  357. # Cleanout any old
  358. UI.CmdTable = {}
  359. UI.SymTable = {}
  360. linenum = 0
  361. # Process and massage the configuration file
  362. for line in cf.read().splitlines():
  363. linenum += 1
  364.  
  365. # Lex for comment token and discard until EOL.
  366.  
  367. idx = line.find(COMMENT)
  368. if idx > -1:
  369. line = line[:idx]
  370.  
  371. # Parse whatever is left on non-blank lines
  372. if line:
  373. ParseLine(line, linenum)
  374.  
  375. cf.close()
  376.  
  377. # Dump tables if we're debugging
  378. if DEBUG:
  379. print "SYMBOL TABLE:\n"
  380. for sym in UI.SymTable.keys():
  381. print sym + " " * (16-len(sym)) + UI.SymTable[sym]
  382.  
  383. print"\nCOMMAND TABLE:\n"
  384. for key in UI.CmdTable.keys():
  385. name = UI.CmdTable[key][0]
  386. cmd = UI.CmdTable[key][1]
  387. print key + " " + name + " " * (16-len(name)) + cmd
  388.  
  389. # End of 'ParseConfFile()'
  390.  
  391.  
  392. #####
  393. # Parse A Line From A Configuration File
  394. # Routine Assumes That Comments Previously Removed
  395. #####
  396.  
  397.  
  398. def ParseLine(line, num):
  399. global UI
  400. revar = re.compile(reVAR)
  401. # Get rid of trailing newline, if any
  402. if line[-1] == '\n':
  403. line = line[:-1]
  404.  
  405. fields = line.split()
  406.  
  407. #####
  408. # Blank Lines - Ignore
  409. #####
  410.  
  411. if len(fields) == 0:
  412. pass
  413.  
  414. #####
  415. # Variable Definitions
  416. #####
  417.  
  418. elif fields[0].find(ASSIGN) > 0:
  419. assign = fields[0].find(ASSIGN)
  420. name = line[:assign]
  421. val=line[assign+1:]
  422.  
  423. # Warn on variable redefinitions
  424. if UI.SymTable.has_key(name):
  425. ErrMsg(eREDEFVAR % (name, num, line))
  426. sys.exit(1)
  427. UI.SymTable[name] = val
  428.  
  429. #####
  430. # Command Definitions
  431. #####
  432.  
  433. elif len(fields[0]) == 1:
  434.  
  435. # Must have at least 3 fields for a valid command definition
  436. if len(fields) < 3:
  437. ErrMsg(eBADCFGLINE % (num, line))
  438. sys.exit(1)
  439. else:
  440. cmdkey = fields[0]
  441. cmdname = fields[1]
  442. cmd = " ".join(fields[2:])
  443.  
  444. # Evaluate the command line, replacing
  445. # variables as needed
  446.  
  447. doeval = TRUE
  448. depth = 0
  449.  
  450. while doeval:
  451. # Get a list of variable references
  452. vbls = revar.findall(cmd)
  453.  
  454. # Throw away references to builtins - these are
  455. # processed at runtime and should be left alone here.
  456.  
  457. # Note that we iterate over a *copy* of the variables
  458. # list, because we may be changing that list contents
  459. # as we go. i.e., It is bogus to iterate over a list
  460. # which we are changing during the iteration.
  461.  
  462. for x in vbls[:]:
  463. vbl = x[1:-1]
  464. if UI.BuiltIns.has_key(vbl):
  465. vbls.remove(x)
  466. if vbls:
  467. for x in vbls:
  468. vbl = x[1:-1]
  469.  
  470. # Process ordinary variables
  471. if UI.SymTable.has_key(vbl):
  472. cmd = cmd.replace(x, UI.SymTable[vbl])
  473.  
  474. # Process environment variables.
  475. # If an environment variable is referenced,
  476. # but not defined, this is a fatal error
  477. elif vbl[0] == ENVVBL:
  478. envvbl = os.getenv(vbl[1:])
  479. if envvbl:
  480. cmd = cmd.replace(x, envvbl)
  481. else:
  482. ErrMsg(eBADENVVBL % (x, num, line))
  483. sys.exit(1)
  484.  
  485. # Process references to undefined variables
  486. else:
  487. ErrMsg(eUNDEFVBL % (x, num, line))
  488. sys.exit(1)
  489.  
  490. # No substitutions left to do
  491. else:
  492. doeval = FALSE
  493. # Bound the number of times we can nest a definition
  494. # to prevent self-references which give infinite nesting depth
  495.  
  496. depth += 1
  497. if depth == MAXNESTING:
  498. doeval = FALSE
  499.  
  500. # See if there are still unresolved variable references.
  501. # If so, let the user know
  502. if revar.findall(cmd):
  503. ErrMsg(eVBLTOODEEP % (num, cmd))
  504. sys.exit(1)
  505.  
  506. # Add the command entry to the command table.
  507. # Prevent duplicate keys from being entered.
  508. if UI.CmdTable.has_key(cmdkey):
  509. ErrMsg(eDUPKEY % (cmdkey, num, line))
  510. sys.exit(1)
  511. else:
  512. UI.CmdTable[cmdkey] = [cmdname, cmd]
  513.  
  514. else:
  515. ErrMsg(eBADCFGLINE % (num, line))
  516. sys.exit(1)
  517.  
  518. # End of 'ParseLine()'
  519.  
  520.  
  521. #####
  522. # Print Usage Information
  523. #####
  524.  
  525. def Usage():
  526. ustring =""
  527. for x in uTable:
  528. print x
  529. # End of 'Usage()'
  530.  
  531.  
  532. #####
  533. # Print A Warning Message
  534. #####
  535.  
  536. def WrnMsg(wmsg):
  537. if WARN:
  538. showwarning(PROGNAME + " " + VERSION + " " + wWARN, wmsg)
  539.  
  540. # End of 'WrnMsg()'
  541.  
  542.  
  543. #----------------------------------------------------------#
  544. # GUI Definition #
  545. #----------------------------------------------------------#
  546.  
  547.  
  548.  
  549. #####
  550. # Enacapsulate the UI in a class
  551. #####
  552.  
  553.  
  554. class twanderUI:
  555.  
  556. def __init__(self, root):
  557.  
  558. # Setup the visual elements
  559.  
  560. self.hSB = Scrollbar(root, orient=HORIZONTAL)
  561. self.vSB = Scrollbar(root, orient=VERTICAL)
  562. self.DirList = Listbox(root,
  563. foreground = FCOLOR,
  564. background = BCOLOR,
  565. font=(FNAME, FSZ, FWT),
  566. selectmode=EXTENDED,
  567. exportselection=0,
  568. xscrollcommand=self.hSB.set,
  569. yscrollcommand=self.vSB.set,
  570. height = HEIGHT,
  571. width = WIDTH,
  572. )
  573.  
  574. # Make them visible by packing
  575. self.hSB.config(command=self.DirList.xview)
  576. self.hSB.pack(side=BOTTOM, fill=X)
  577. self.vSB.config(command=self.DirList.yview)
  578. self.vSB.pack(side=RIGHT, fill=Y)
  579. self.DirList.pack(side=LEFT, fill=BOTH, expand=1)
  580.  
  581. #####
  582. # Bind the relevant event handlers
  583. #####
  584. # General Program Commands
  585.  
  586. # Bind handler for individual keystrokes
  587. self.DirList.bind(KEYPRESS, KeystrokeHandler)
  588.  
  589. # Bind handler for "Quit Program"
  590. self.DirList.bind(QUITPROG, KeyQuitProg)
  591.  
  592. # Bind handler for "Read Config File"
  593. self.DirList.bind(READCONF, ParseConfFile)
  594.  
  595. # Bind handler for "Refresh Screen"
  596. self.DirList.bind(REFRESH, RefreshDirList)
  597.  
  598. # Bind handler for "Run Command"
  599. self.DirList.bind(RUNCMD, KeyRunCommand)
  600.  
  601. # Bind handler for "Toggle Detail"
  602. self.DirList.bind(TOGDETAIL, KeyToggleDetail)
  603.  
  604.  
  605. # Directory Navigation
  606.  
  607. # Bind handler for "Change Directory"
  608. self.DirList.bind(CHANGEDIR, ChangeDir)
  609.  
  610. # Bind handler for "Home Dir"
  611. self.DirList.bind(DIRHOME, KeyHomeDir)
  612.  
  613. # Bind handler for "Previous Dir"
  614. self.DirList.bind(DIRBACK, KeyBackDir)
  615.  
  616. # Bind handler for "Starting Dir"
  617. self.DirList.bind(DIRSTART, KeyStartDir)
  618.  
  619. # Bind handler for "Up Dir"
  620. self.DirList.bind(DIRUP, KeyUpDir)
  621.  
  622. # Bind handler for "Mouse Back Dir"
  623. self.DirList.bind(MSEBACK, KeyBackDir)
  624.  
  625. # Bind handler for "Mouse Up Dir"
  626. self.DirList.bind(MSEUP, KeyUpDir)
  627.  
  628.  
  629. # Selection Keys
  630.  
  631. # Bind handler for "Select All"
  632. self.DirList.bind(SELALL, KeySelAll)
  633.  
  634. # Bind handler for "Next Item"
  635. self.DirList.bind(SELNEXT, KeySelNext)
  636.  
  637. # Bind handler for "Select No Items"
  638. self.DirList.bind(SELNONE, KeySelNone)
  639.  
  640. # Bind handler for "Previous Item"
  641. self.DirList.bind(SELPREV, KeySelPrev)
  642.  
  643. # Bind handler for "First Item"
  644. self.DirList.bind(SELTOP, KeySelTop)
  645.  
  646. # Bind handler for "Last Item"
  647. self.DirList.bind(SELEND, KeySelEnd)
  648.  
  649. # Bind handler for "Item Select"
  650. self.DirList.bind(SELKEY, DirListHandler)
  651.  
  652. # Bind handler for "Mouse Select"
  653. self.DirList.bind(SELMOUSE, DirListHandler)
  654.  
  655.  
  656. # Intra-display movement
  657.  
  658. # Bind Handler for "Move Page Down
  659. self.DirList.bind(PGDN, KeyPageDown)
  660.  
  661. # Bind Handler for "Move Page Up"
  662. self.DirList.bind(PGUP, KeyPageUp)
  663.  
  664.  
  665. # Give the listbox focus so it gets keystrokes
  666. self.DirList.focus()
  667.  
  668. # End if method 'twanderUI.__init__()'
  669.  
  670.  
  671. #####
  672. # Return tuple of all selected items
  673. #####
  674.  
  675. def AllSelection(self):
  676. sellist = []
  677.  
  678. for entry in self.DirList.curselection():
  679. sellist.append(self.DirList.get(entry)[UI.NameFirst:].split(SYMPTR)[0])
  680.  
  681. return sellist
  682.  
  683. # End of method 'twanderUI.AllSelection()'
  684.  
  685. #####
  686. # Return name of currently selected item
  687. #####
  688.  
  689. def LastInSelection(self):
  690.  
  691. index = self.DirList.curselection()
  692. if index:
  693. return self.DirList.get(index[-1])[UI.NameFirst:].split(SYMPTR)[0]
  694. else:
  695. return ""
  696.  
  697. # End of method 'twanderUI.LastInSelection()'
  698.  
  699.  
  700. #####
  701. # Support periodic polling to make sure widget stays
  702. # in sync with reality of current directory.
  703. #####
  704.  
  705. def poll(self):
  706.  
  707. # If new dir entered via mouse, force correct activation
  708. if self.MouseNewDir:
  709. self.DirList.activate(0)
  710. self.MouseNewDir = FALSE
  711.  
  712. # See if its time to do a refresh
  713.  
  714. if AUTOREFRESH:
  715. self.ElapsedTime += POLLINT
  716. if self.ElapsedTime >= REFRESHINT:
  717. RefreshDirList()
  718. self.ElapsedTime = 0
  719.  
  720. # Setup next polling event
  721. self.DirList.after(POLLINT, self.poll)
  722.  
  723. # End of method 'twanderUI.poll()'
  724.  
  725.  
  726. #####
  727. # Set Detailed View -> FALSE == No Details, TRUE == Details
  728. #####
  729.  
  730. def SetDetailedView(self, details):
  731.  
  732. self.DetailsOn = details
  733. # Tell system where actual file name begins
  734. # For both choices below, we have to set the UI.NameFirst
  735. # value. This tells other handlers where in a given
  736. # selection the actual name of the file can be found.
  737. # This is necessary because we may be selecting a from
  738. # a detailed view and need to know where in that view
  739. # the file name lives. It is not good enough to just
  740. # split() the selected string and use the [-1] entry because
  741. # we may be dealing with a file which has spaces in its
  742. # name.
  743.  
  744.  
  745. if details:
  746. self.NameFirst = ST_SZTOTAL
  747. else:
  748. self.NameFirst = 0
  749.  
  750. # End of method 'twanderUI.SetDetailedView()'
  751.  
  752.  
  753. #####
  754. # Update title bar with most current information
  755. #####
  756.  
  757. def UpdateTitle(self, mainwin):
  758.  
  759. mainwin.title(PROGNAME + " " + VERSION +
  760. " " + HOSTNAME + ": "+
  761. UI.CurrentDir + " Total Files: " +
  762. str(UI.DirList.size()) +
  763. " Total Size: " + FileLength(UI.TotalSize))
  764.  
  765. # End of method 'twanderUI.UpdateTitle()'
  766.  
  767. # End of class definition, 'twanderUI'
  768.  
  769.  
  770. #----------------------------------------------------------#
  771. # Handler Functions #
  772. #----------------------------------------------------------#
  773.  
  774.  
  775. #--------------- General Program Commands -----------------#
  776.  
  777.  
  778. #####
  779. # Event Handler: Individual Keystrokes
  780. #####
  781.  
  782. def KeystrokeHandler(event):
  783.  
  784. # If the key pressed is a command key (i.e., it is in the table of
  785. # defined commands), get its associated string and execute the command.
  786.  
  787. cmd = UI.CmdTable.get(event.char, ["", "", ""])[1]
  788.  
  789.  
  790. # cmd == null means no matching command key - do nothing
  791. # Otherwise, replace config tokens with actual file/dir names
  792. if cmd:
  793. # Replace runtime-determined tokens
  794.  
  795. selection = UI.LastInSelection()
  796.  
  797. selections = ""
  798. for selected in UI.AllSelection():
  799. selections += selected + " "
  800.  
  801. dselections = ""
  802. for sel in selections.split():
  803. dselections += UI.CurrentDir + sel + " "
  804. cmd = cmd.replace(DIR, UI.CurrentDir)
  805. cmd = cmd.replace(DSELECTION, UI.CurrentDir + selection)
  806. cmd = cmd.replace(DSELECTIONS, dselections)
  807. cmd = cmd.replace(SELECTION, selection)
  808. cmd = cmd.replace(SELECTIONS, selections)
  809.  
  810. # Just dump command if we're debugging
  811.  
  812. if DEBUG:
  813. print cmd
  814. # Otherwise,actually execute the command
  815. else:
  816. if OSNAME == 'win32':
  817. thread.start_new_thread(os.system, (cmd,))
  818. else:
  819. os.system(cmd)
  820. # end of 'KeystrokeHandler()'
  821.  
  822. #####
  823. # Event Handler: Program Quit
  824. #####
  825.  
  826. def KeyQuitProg(event):
  827. sys.exit()
  828.  
  829. # End of 'KeyQuitProg()'
  830.  
  831.  
  832. #####
  833. # Event Handler: Run Command
  834. ####
  835.  
  836. def KeyRunCommand(event):
  837.  
  838. cmd = askstring(pRUNCMD, pENCMD)
  839. if cmd:
  840. thread.start_new_thread(os.system, (cmd,))
  841. UI.DirList.focus()
  842.  
  843. # End of 'ChangeDir()'
  844.  
  845.  
  846. #####
  847. # Event Handler: Toggle Detail View
  848. #####
  849.  
  850. def KeyToggleDetail(event):
  851.  
  852. UI.SetDetailedView(not UI.DetailsOn)
  853. RefreshDirList(event)
  854.  
  855. # End of 'KeyToggleDetail()'
  856.  
  857.  
  858. #------------------- Directory Navigation -----------------#
  859.  
  860.  
  861. #####
  862. # Event Handler: Change Directory/Path
  863. ####
  864.  
  865. def ChangeDir(event):
  866.  
  867. newpath = askstring(pCHPATH, pENPATH)
  868. if newpath:
  869. LoadDirList(newpath)
  870. KeySelTop(event)
  871. UI.DirList.focus()
  872.  
  873. # End of 'ChangeDir()'
  874.  
  875.  
  876. #####
  877. # Event Handler: Goto $HOME
  878. #####
  879.  
  880. def KeyHomeDir(event):
  881.  
  882. if HOME:
  883. LoadDirList(HOME)
  884. else:
  885. LoadDirList(STARTDIR)
  886.  
  887. # End of 'KeyHomeDir()'
  888.  
  889.  
  890. #####
  891. # Event Handler: Move To Previous Directory
  892. #####
  893.  
  894. def KeyBackDir(event):
  895.  
  896. # Move to last directory visited, if any - inhibit this from
  897. # being placed on the directory traversal stack
  898. if UI.LastDir:
  899. LoadDirList(UI.LastDir.pop(), save=FALSE)
  900.  
  901. # No previous directory
  902. else:
  903. pass
  904.  
  905. # End of 'KeyBackDir()'
  906.  
  907.  
  908. #####
  909. # Event Handler: Go Back to Initial Directory
  910. #####
  911.  
  912. def KeyStartDir(event):
  913. global STARTDIR
  914. LoadDirList(STARTDIR)
  915.  
  916. # End of 'KeyStartDir()'
  917.  
  918.  
  919. #####
  920. # Event Handler: Move up one directory
  921. #####
  922.  
  923. def KeyUpDir(event):
  924.  
  925. # Move up one directory level unless we're already at the root
  926. if UI.CurrentDir != os.path.abspath(PSEP):
  927. LoadDirList(UI.CurrentDir + "..")
  928.  
  929. # End of 'KeyUpDir()'
  930.  
  931.  
  932. #---------------------- Selection Keys ---------------------#
  933.  
  934.  
  935. #####
  936. # Event Handler: Select All Items
  937. #####
  938.  
  939. def KeySelAll(event):
  940. UI.DirList.selection_set(0, END)
  941.  
  942. # End of 'KeySelAll()'
  943.  
  944.  
  945. #####
  946. # Event Handler: Select Next Item
  947. #####
  948.  
  949. def KeySelNext(event):
  950. next = UI.DirList.index(ACTIVE) + 1
  951. SetSelection((str(next),), next)
  952.  
  953. # End of 'KeySelNext()'
  954.  
  955.  
  956. #####
  957. # Event Handler: Select No Items
  958. #####
  959.  
  960. def KeySelNone(event):
  961. UI.DirList.selection_clear(0, END)
  962.  
  963. # End of 'KeySelNone()'
  964.  
  965.  
  966. #####
  967. # Event Handler: Select Previous Item
  968. #####
  969.  
  970. def KeySelPrev(event):
  971. prev = UI.DirList.index(ACTIVE) - 1
  972. SetSelection((str(prev),), prev)
  973.  
  974. # End of 'KeySelPrev()'
  975.  
  976.  
  977. #####
  978. # Event Handler: Select Last Item
  979. #####
  980.  
  981. def KeySelEnd(event):
  982.  
  983. # Get current number of items in listbox
  984. sz = UI.DirList.size()
  985.  
  986. # And setup to last item accordingly
  987. SetSelection((str(sz),), sz)
  988.  
  989. # End of 'KeySelEnd()'
  990.  
  991.  
  992. #####
  993. # Event Handler: Select First Item
  994. #####
  995.  
  996. def KeySelTop(event):
  997. SetSelection(('0',),0)
  998.  
  999. # End of 'KeySelTop()'
  1000.  
  1001.  
  1002. #####
  1003. # Process Current Selection
  1004. #####
  1005.  
  1006. def DirListHandler(event):
  1007. global UI
  1008. SAVE = TRUE
  1009.  
  1010. # Get current selection. If none, just return, otherwise process
  1011. selected = UI.LastInSelection()
  1012. if not selected:
  1013. return
  1014. # If selection is a directory, move there and list contents.
  1015.  
  1016. if os.path.isdir(os.path.join(UI.CurrentDir, selected)):
  1017.  
  1018. # If we're on Unix, don't follow symlinks pointing back to themselves
  1019.  
  1020. if OSNAME == 'posix' and os.path.samefile(UI.CurrentDir, UI.CurrentDir + selected):
  1021. WrnMsg(wSYMBACK % (UI.CurrentDir + selected))
  1022. return
  1023.  
  1024. # We don't push this selection on the stack if
  1025. # we are at root directory and user presses '..'
  1026.  
  1027. if (selected == '..') and (UI.CurrentDir == os.path.abspath(PSEP)):
  1028. SAVE = FALSE
  1029.  
  1030. # Build full path name
  1031. selected = os.path.join(os.path.abspath(UI.CurrentDir), selected)
  1032.  
  1033. # Convert ending ".." into canonical path
  1034. if selected.endswith(".."):
  1035. selected = PSEP.join(selected.split(PSEP)[:-2])
  1036. # Need to end the directory string with a path
  1037. # separator character so that subsequent navigation
  1038. # will work when we hit the root directory of the file
  1039. # system. In the case of Unix, this means that
  1040. # if we ended up at the root directory, we'll just
  1041. # get "/". In the case of Win32, we will get
  1042. # "DRIVE:/".
  1043.  
  1044. selected += PSEP
  1045.  
  1046. # Load UI with new directory
  1047. LoadDirList(selected, save=SAVE)
  1048.  
  1049. # Indicate that we entered a new directory this way.
  1050. # This is a workaround for Tk madness. When this
  1051. # routine is invoked via the mouse, Tk sets the
  1052. # activation *when this routine returns*. That means
  1053. # that the activation set at the end of LoadDirList
  1054. # gets overidden. We set this flag here so that
  1055. # we can subsequently do the right thing in our
  1056. # background polling loop. Although this routine
  1057. # can also be invoked via a keyboard selection,
  1058. # we run things this way regardless since no harm
  1059. # will be done in the latter case.
  1060.  
  1061. UI.MouseNewDir = TRUE
  1062.  
  1063.  
  1064. # No, a *file* was selected with a double-click
  1065. # We know what to do on Win32 and Unix. We ignore
  1066. # the selection elsewhere.
  1067.  
  1068. elif OSNAME == 'nt':
  1069. os.startfile(os.path.join(os.path.abspath(UI.CurrentDir), selected))
  1070.  
  1071. elif OSNAME == 'posix':
  1072. thread.start_new_thread(os.system, (os.path.join(os.path.abspath(UI.CurrentDir), selected),))
  1073.  
  1074.  
  1075. # Have to update the window title because selection changed
  1076. UI.UpdateTitle(UIroot)
  1077.  
  1078. # End of 'DirListHandler()'
  1079.  
  1080.  
  1081. #####
  1082. # Load UI With Selected Directory
  1083. #####
  1084.  
  1085. def LoadDirList(newdir, save=TRUE):
  1086.  
  1087. # Get path into canonical form
  1088. newdir = os.path.abspath(newdir)
  1089.  
  1090. # Make sure newdir properly terminated
  1091. if newdir[-1] != PSEP:
  1092. newdir += PSEP
  1093.  
  1094. # Check right now to see if we can read
  1095. # the directory. If not, at least we
  1096. # haven't screwed up the widget's current
  1097. # contents or program state.
  1098.  
  1099. try:
  1100. contents = BuildDirList(newdir)
  1101. except:
  1102. # If CurrentDir set, we're still there: error w/ recovery
  1103. if UI.CurrentDir:
  1104. ErrMsg(eDIRRD % newdir)
  1105. return
  1106.  
  1107. # If not, we failed on the initial directory: error & abort
  1108. else:
  1109. ErrMsg(eINITDIRBAD % newdir)
  1110. sys.exit(1)
  1111.  
  1112. # Push last directory visited onto the visited stack
  1113.  
  1114. # Do not do this if we've been told not to OR if
  1115. # what we're about to save is the same as the top
  1116. # of the stack OR if the current directory is ""
  1117. # If there is anything on the stack, see if last element
  1118. # matches what we're about to put there.
  1119. if UI.LastDir and UI.LastDir[-1] == UI.CurrentDir:
  1120. save = FALSE
  1121.  
  1122. if save and UI.CurrentDir:
  1123. UI.LastDir.append(UI.CurrentDir)
  1124.  
  1125. # And select new directory to visit
  1126. UI.CurrentDir = newdir
  1127.  
  1128. # Wait until we have exclusive access to the widget
  1129.  
  1130. while not UI.DirListMutex.testandset():
  1131. pass
  1132.  
  1133. # Clear out the old contents
  1134. UI.DirList.delete(0,END)
  1135.  
  1136. # Load new directory contents into UI
  1137. for x in contents:
  1138. UI.DirList.insert(END, x)
  1139.  
  1140. # And update the title to reflect changes
  1141. UI.UpdateTitle(UIroot)
  1142.  
  1143. # And always force selection of first item there.
  1144. # This guarantees a selection in the new
  1145. # directory context, so subsequent commands
  1146. # won't try to operate on an item selected in a
  1147. # previous directory
  1148.  
  1149. KeySelTop(None)
  1150.  
  1151. #Release the lock
  1152. UI.DirListMutex.unlock()
  1153.  
  1154. # End of 'LoadDirList():
  1155.  
  1156.  
  1157. #####
  1158. # Return Ordered List Of Directories & Files For Current Root
  1159. #####
  1160.  
  1161. def BuildDirList(currentdir):
  1162. global UI
  1163. dList, fList = [], []
  1164. # Walk the directory separate subdirs and files
  1165. for file in os.listdir(currentdir):
  1166. if os.path.isdir(os.path.join(currentdir, file)):
  1167. dList.append(file + PSEP)
  1168. else:
  1169. fList.append(file)
  1170.  
  1171. # Put each in sorted order
  1172. dList.sort()
  1173. fList.sort()
  1174.  
  1175.  
  1176. # Entry to move up one directory is always first,
  1177. # no matter what the sort. This is necessary because
  1178. # OSs like Win32 like to use '$' in file names which
  1179. # sorts before "."
  1180.  
  1181. dList.insert(0, ".." + PSEP)
  1182.  
  1183. # If user has not requested detailed display, we're done
  1184. if not UI.DetailsOn:
  1185. return dList + fList
  1186.  
  1187. # Detailed display requested, do the work
  1188.  
  1189. all = dList + fList
  1190. detlist = []
  1191. UI.TotalSize = 0
  1192. for index in range(len(all)):
  1193.  
  1194. # Make room for the new detailed entry
  1195. detlist.append("")
  1196.  
  1197. # Get file details from OS
  1198. try:
  1199. fn = os.path.join(currentdir, all[index])
  1200. if fn[-1] == PSEP:
  1201. fn =fn[:-1]
  1202. stinfo = os.lstat(fn)
  1203.  
  1204. # 'lstat' failed - provide entry with some indication of this
  1205.  
  1206. except:
  1207. pad = (UI.NameFirst - len(iNOSTAT) - 1) * " "
  1208. detlist[index] = pad + iNOSTAT + " " + all[index]
  1209.  
  1210. # Done with this file, but keep going
  1211. continue
  1212. # Mode - 1st get into octal string
  1213.  
  1214. mode = stinfo[ST_MODE]
  1215. modestr = str("%06o" % mode)
  1216.  
  1217. # Set the permission bits
  1218.  
  1219. mode = ""
  1220. for x in [-3, -2, -1]:
  1221. mode += ST_PERMIT[int(modestr[x])]
  1222.  
  1223. # Deal with the special permissions
  1224.  
  1225. sp = int(modestr[-4])
  1226.  
  1227. # Sticky Bit
  1228.  
  1229. if 1 & sp:
  1230. if mode[-1] == "x":
  1231. mode = mode[:-1] + "t"
  1232. else:
  1233. mode = mode[:-1] + "T"
  1234.  
  1235. # Setgid Bit
  1236.  
  1237. if 2 & sp:
  1238. if mode[-4] == "x":
  1239. mode = mode[:-4] + "g" + mode[-3:]
  1240. else:
  1241. mode = mode[:-4] + "G" + mode[-3:]
  1242.  
  1243. # Setuid Bit
  1244.  
  1245. if 4 & sp:
  1246. if mode[-7] == "x":
  1247. mode = mode[:-7] + "g" + mode[-6:]
  1248. else:
  1249. mode = mode[:-7] + "G" + mode[-6:]
  1250.  
  1251. # Pickup the special file types
  1252. mode = ST_SPECIALS.get(modestr[0:2], "?") + mode
  1253.  
  1254. detlist[index] += mode + (ST_SZMODE - len(mode)) * " "
  1255.  
  1256. # Number of links to entry
  1257. detlist[index] += str(stinfo[ST_NLINK]) + \
  1258. ( ST_SZNLINK - len(str(stinfo[ST_NLINK]))) * " "
  1259.  
  1260. # Get first ST_SZxNAME chars of owner and group names on unix
  1261.  
  1262. if OSNAME == 'posix':
  1263. owner = pwd.getpwuid(stinfo[ST_UID])[0][:ST_SZUNAME-1]
  1264. group = grp.getgrgid(stinfo[ST_GID])[0][:ST_SZGNAME-1]
  1265.  
  1266. # Handle Win32 systems
  1267. elif OSNAME == 'nt':
  1268. owner = 'win32user'
  1269. group = 'win32group'
  1270.  
  1271. # Default names for all other OSs
  1272. else:
  1273. owner = OSNAME + 'user'
  1274. group = OSNAME + 'group'
  1275.  
  1276. # Add them to the detail
  1277.  
  1278. detlist[index] += owner + (ST_SZUNAME - len(owner)) * " "
  1279. detlist[index] += group + (ST_SZUNAME - len(group)) * " "
  1280.  
  1281. # Length
  1282.  
  1283. flen = FileLength(stinfo[ST_SIZE])
  1284. UI.TotalSize += stinfo[ST_SIZE]
  1285. detlist[index] += flen + (ST_SZLEN - len(flen)) * " "
  1286.  
  1287. # mtime
  1288.  
  1289. # Get the whole time value
  1290. ftime = time.ctime(stinfo[ST_MTIME]).split()[1:]
  1291.  
  1292. # Pad single-digit dates with leading space
  1293.  
  1294. if len(ftime[1]) == 1:
  1295. ftime[1] = " " + ftime[1]
  1296.  
  1297. # Drop the seconds
  1298. ftime[-2] = ":".join(ftime[-2].split(":")[:-1])
  1299.  
  1300. # Turn into a single string
  1301. ftime = " ".join(ftime)
  1302.  
  1303. detlist[index] += ftime + (ST_SZMTIME - len(ftime)) * " "
  1304.  
  1305. # File name
  1306. detlist[index] += all[index]
  1307.  
  1308. # Include symlink details as necessary
  1309. if detlist[index][0] == 'l':
  1310.  
  1311. # If the symlink points to a file
  1312. # in the same directory, just show
  1313. # the filename and not the whole path
  1314.  
  1315. f = os.path.realpath(currentdir + all[index])
  1316. r = os.path.split(f)
  1317. if r[0] == currentdir[:-1]:
  1318. f = r[1]
  1319.  
  1320. detlist[index] += SYMPTR + f
  1321.  
  1322. return detlist
  1323.  
  1324. # End of 'BuildDirList()'
  1325.  
  1326.  
  1327. #####
  1328. # Event Handler: Move Down A Page
  1329. #####
  1330.  
  1331. def KeyPageDown(event):
  1332. UI.DirList.yview_scroll(1, "pages")
  1333.  
  1334. # End of 'KeyPageDown()'
  1335.  
  1336.  
  1337. #####
  1338. # Event Handler: Move Up A Page
  1339. #####
  1340.  
  1341. def KeyPageUp(event):
  1342. UI.DirList.yview_scroll(-1, "pages")
  1343.  
  1344.  
  1345. # End of 'KeyPageUp()'
  1346.  
  1347.  
  1348. #-------------- Handler Utility Functions -----------------#
  1349.  
  1350.  
  1351. #####
  1352. # Refresh contents of directory listing to stay in sync with reality
  1353. #####
  1354.  
  1355. def RefreshDirList(*args):
  1356.  
  1357. # Wait until we have exclusive access to the widget
  1358.  
  1359. while not UI.DirListMutex.testandset():
  1360. pass
  1361.  
  1362. # Get current selection and active
  1363.  
  1364. sellist = UI.DirList.curselection()
  1365. active = UI.DirList.index(ACTIVE)
  1366.  
  1367. # Save current scroll positions
  1368.  
  1369. xs = UI.hSB.get()
  1370. ys = UI.vSB.get()
  1371.  
  1372. # Clean out old listbox contents
  1373. UI.DirList.delete(0,END)
  1374.  
  1375. # Save the new directory information
  1376. UI.DirList.insert(0, *BuildDirList(UI.CurrentDir))
  1377.  
  1378. # Restore selection(s)
  1379. SetSelection(sellist, active)
  1380.  
  1381. # Restore scroll positions
  1382.  
  1383. UI.DirList.xview(MOVETO, xs[0])
  1384. UI.DirList.yview(MOVETO, ys[0])
  1385.  
  1386. # Release the mutex
  1387. UI.DirListMutex.unlock()
  1388.  
  1389. # End of 'RefreshDirList()
  1390.  
  1391.  
  1392. #####
  1393. # Set a particular selection, w/bounds checking
  1394. # Note that 'selection' is passed as a string
  1395. # but 'active' is passed as a number.
  1396. #####
  1397.  
  1398. def SetSelection(selection, active):
  1399.  
  1400. # Clear all current selection(s)
  1401. UI.DirList.selection_clear(0, END)
  1402.  
  1403. # Get current maximum index
  1404. maxindex = UI.DirList.size() - 1
  1405.  
  1406. # And bounds check/adjust
  1407.  
  1408. if active > maxindex:
  1409. active = maxindex
  1410.  
  1411. # Set desired selected items, if any
  1412.  
  1413. if selection:
  1414. for entry in selection:
  1415. UI.DirList.select_set(entry)
  1416. UI.DirList.see(selection[-1])
  1417.  
  1418. # Now set the active entry
  1419. UI.DirList.activate(active)
  1420.  
  1421. # End of 'SetSelection()'
  1422.  
  1423.  
  1424. #----------------------------------------------------------#
  1425. # Program Entry Point #
  1426. #----------------------------------------------------------#
  1427.  
  1428. # Command line processing
  1429.  
  1430. try:
  1431. opts, args = getopt.getopt(sys.argv[1:], '-b:c:df:hn:qrs:vw:x:y:')
  1432. except getopt.GetoptError:
  1433. Usage()
  1434. sys.exit(1)
  1435.  
  1436. # Parse command line
  1437.  
  1438. for opt, val in opts:
  1439. if opt == "-b":
  1440. BCOLOR = val
  1441. if opt == "-c":
  1442. CONF = val
  1443. if opt == "-d":
  1444. DEBUG = TRUE
  1445. if opt == "-f":
  1446. FCOLOR = val
  1447. if opt == "-h":
  1448. Usage()
  1449. sys.exit(0)
  1450. if opt == "-n":
  1451. FNAME = val
  1452. if opt == "-q":
  1453. WARN = FALSE
  1454. if opt == "-r":
  1455. AUTOREFRESH = FALSE
  1456. if opt == "-s":
  1457. FSZ = val
  1458. if opt == "-v":
  1459. print RCSID
  1460. sys.exit(0)
  1461. if opt == "-w":
  1462. FWT = val
  1463. if opt == "-x":
  1464. WIDTH = val
  1465. if opt == "-y":
  1466. HEIGHT = val
  1467.  
  1468.  
  1469. # Create an instance of the UI
  1470. UIroot = Tk()
  1471. UI = twanderUI(UIroot)
  1472.  
  1473. # Make the Tk window the topmost in the Z stack.
  1474. # 'Gotta do this or Win32 will not return input
  1475. # focus to our program after a startup warning
  1476. # display.
  1477.  
  1478. UIroot.tkraise()
  1479.  
  1480. #####
  1481. # Setup global UI variables
  1482. #####
  1483.  
  1484. # Figure out where to start
  1485. # Program can only have 0 or 1 arguments
  1486. # Make sure any startdir argument is legit
  1487.  
  1488. if len(args) > 1:
  1489. ErrMsg(eTOOMANY)
  1490. sys.exit(1)
  1491.  
  1492. if len(args) == 1:
  1493. STARTDIR = args[0]
  1494. if not os.path.isdir(STARTDIR):
  1495. ErrMsg(eBADROOT % STARTDIR)
  1496. sys.exit(1)
  1497.  
  1498. # Get starting directory into canonical form
  1499. STARTDIR = os.path.abspath(STARTDIR)
  1500.  
  1501. # Setup builtin variables
  1502. UI.BuiltIns = {"DIR":"", "SELECTION":"", "SELECTIONS":"", "DSELECTION":"",
  1503. "DSELECTIONS":"", "PROMPT:":""}
  1504.  
  1505. # Parse the and store configuration file, if any
  1506. ParseConfFile(None)
  1507.  
  1508. # Initialize directory stack
  1509. UI.LastDir = []
  1510.  
  1511. # And current location
  1512. UI.CurrentDir = ""
  1513.  
  1514. # Need mutex to serialize on widget updates
  1515. UI.DirListMutex = mutex.mutex()
  1516.  
  1517. # Intialize the "new dir via mouse" flag
  1518. UI.MouseNewDir = FALSE
  1519.  
  1520. # Initialize the polling counter
  1521. UI.ElapsedTime = 0
  1522.  
  1523. # Start in detailed mode
  1524. UI.SetDetailedView(TRUE)
  1525.  
  1526. # Initialize the UI directory listing
  1527. LoadDirList(STARTDIR)
  1528. KeySelTop(None)
  1529.  
  1530. # And start the periodic polling of the widget
  1531. UI.poll()
  1532.  
  1533. # Run the program interface
  1534. UIroot.mainloop()
  1535.