(* Copyright (C) 1992, Digital Equipment Corporation *) (* All rights reserved. *) (* See the file COPYRIGHT for a full description. *) (* *) (* Last modified on Tue Jun 16 13:08:53 PDT 1992 by muller *) (* modified on Thu May 7 17:17:41 PDT 1992 by meehan *) (* modified on Mon Apr 6 09:46:35 1992 by mhb *) (* modified on Fri Mar 20 22:41:26 1992 by steveg *) (* modified on Mon Feb 11 14:05:15 PST 1991 by brooks *) INTERFACE FileBrowserVBT; (* A FileBrowserVBT displays the files in a directory, in a browser, and allows the user to point to a file to be acted on. A TextPort "helper" may be associated with a FileBrowserVBT; it displays the pathname of the directory and allows the user to type new pathnames. An AnchorSplit "menu" may be associated with a FileBrowserVBT; it displays the names of each level in the directory-tree, with the root at the bottom. There are two user actions, "selecting" and "activating". 1. The user may *select* items, by single-clicking on an item to select just that one, shift-clicking to select more than one, or single-clicking and dragging to select a range. A change in selection is reported to the client by invoking the "selectItems" method, whose default is a no-op. The client can read the current selection-set with "TextBrowserVBT.GetMultiSelection" or "TextBrowserVBT.GetMultiTextList". 2. The user may *activate* an item, either by double-clicking on it, or by typing its name in the helper and pressing Return. Activation of a *file* is reported to the client by invoking the "activateFile" method, whose default is a no-op. Activation of a *directory* is reported by invoking the "activateDir" method, whose default behavior is to call Set to display the activated directory. The client can distinguish between a double-click and a Return by looking at the AnyEvent.Code passed to the activation method. A double-click will be reported as an AnyEvent.MouseCode, and the Return will be an AnyEvent.KeyCode (subtypes of AnyeEvent.Code). Directories are indicated in the display by showing a '/' after the name. The slash is present in the name reported to the callback procedure, and thus can be used as an indicator that the selected object is a directory. In the interest of speed, directories are displayed "lazily". First FileBrowserVBT reads the current directory, filters on the list of acceptable suffixes, and sorts the results in alphabetical order. Then, in a seaparate thread, it does a "stat" on all the files in the directory (a much slower operation) and if any are discovered to be directories, they are inserted into the browser (preserving alphabetical order). Someday there will probably be an option to suppress this behavior and display directories in their appropriate alphabetical place right at the beginning. This is guaranteed to delay the initial display, though. Once a directory is read, it is not read again until the user explicitly performs some action that would require the directory to be re-read, such as double-clicking on the directory "." or traversing the structure. Consequently, if the directory is modified, the display in the FileBrowserVBT will not be accurate. The way to fix this infelicity is to fork a thread that wakes up every so often and checks if the directory has been changed. If the thread notices that the directory has changed, it will update the browser with the current directory contents. A FileBrowserVBT.T is a subtype of a multi-selecting TextBrowserVBT (TextBrowser.Multi). It is OK to use many TextBrowser procedures, such as SetFont, ScrollToShow, or SelectText. However, although routines that change the contents are allowable, they may pervert the intent of a file browser and cause confusion for its client. *) IMPORT AnchorSplit, AnyEvent, Font, Shadow, TextBrowserVBT, TextList, TextPort, VBT; <* PRAGMA LL *> TYPE Public = TextBrowserVBT.Multi BRANDED "FileBrowser 2.0" OBJECT METHODS init (multiColumn: BOOLEAN := FALSE; font : Font.T := Font.BuiltIn; shadow : Shadow.T := NIL; style : Shadow.Style := Shadow.Style.Flat): T; (* Initialize v as a FileBrowserVBT. Its initial state is the working directory, as returned by UnixUtils.GetWD. If the wd is not readable, "/" is used. The multiColumn, font, and shadow parameters are the same as in TextBrowserVBT. *) selectItems (event: AnyEvent.Code);<* LL = VBT.mu *> activateFile (filename: TEXT; event: AnyEvent.Code);<* LL = VBT.mu *> (* Called when a file is selected by double-clicking in the browser (with filename = first selected item) or CR in the helper (with filename = tail of the contents of the helper). To find the full path name of filename, use | GetDir(self) & "/" & filename. Don't forget that multiple files might be selected in the browser, if activateFile is being called because of a double-click; GetFiles returns a list of selected items. *) activateDir (dirname: TEXT; event: AnyEvent.Code);<* LL = VBT.mu *> (* This is called when a new directory is activated. The normal action is simply to set v to view that directory, relative to GetDir(self). If an error occurs during the activation, self.error will be called. *) error (err: E);<* LL = VBT.mu *> (* This method is called when an error occurs during user action in v, and Error cannot be raised directly. Examples: the user has typed a nonexistent directory in the path; the current directory has become inaccessible; the user has no permission to read the directory. By default the response to such errors is that nothing happens. By providing a method, the client can provide better information to the user. *) END; T <: Public; PublicHelper = TextPort.T OBJECT METHODS init (hMargin: REAL := 1.5; vMargin: REAL := 1.5; font : Font.T := Font.BuiltIn; shadow : Shadow.T := NIL (* Shadow.None *)): Helper; END; Helper <: PublicHelper; (* The helper detects all text-modifying keystrokes to let us know when it has the value, and it intercepts Return in all modified or unmodified forms, to treat them all as activations. If an error occurs during the activation, we invoke "error" method of the filebrowser to which the helper is attached. *) PublicDirMenu = AnchorSplit.T OBJECT METHODS init (font := Font.BuiltIn; shadow: Shadow.T := NIL; (* Shadow.None *) n : CARDINAL := 0 ): DirMenu END; DirMenu <: PublicDirMenu; (* The directory menu contains all the ancestors in the pathname. *) PROCEDURE SetHelper (v: T; helper: Helper) RAISES {Error}; (* Sets the helper for v, and fills it with GetDir(v) *) PROCEDURE SetDirMenu (v: T; dm: DirMenu); PROCEDURE SetReadOnly (v: T; readOnly: BOOLEAN); (* When TRUE, the filename passed to activateFile is guaranteed to exist. Otherwise, the default, the user can type a non-existing file name into the helper. *) PROCEDURE SetSuffixes (v: T; suffixes: TEXT); (* Suffixes provides a filter on the files to be displayed. By default all files in the directory are displayed, but if suffixes is not "", only files with the specified suffixes (and all directories) will be displayed. The format of is a list of suffixes (not including the period) separated by non-alphanumeric characters (e.g., spaces). The special suffix "$" indicates "files with no suffix". Does not force immediate redisplay. *) TYPE Preference = {caseSensitive}; PROCEDURE SetPreference (v: T; p: Preference; value: BOOLEAN); (* Set user-dependent preference parameters. Now there is only one, the list is likely to grow. Eventually such things should be set from some sort of user profile. Meanings: - caseSensitive: controls the sorting order of files displayed. If true (the default), then files are sorted in Unix order. If false, capital and small letters are both treated as the small version thereof. Does not force immediate redisplay. *) EXCEPTION Error(E); TYPE E = OBJECT v: T; text, path: TEXT := "" END; PROCEDURE Set (v: T; pathname: TEXT; time: VBT.TimeStamp := 0) RAISES {Error}; <* LL = VBT.mu *> (* Set the display state of v. 'pathname' may be absolute or relative; if it's relative, it is relative to the current displayed directory. (A pathame is absolute if the results of ExpandTilde starts with /.) If the pathname refers to a non-existent or inacessible directory, an error will be raised. If it refers to a non-existent file and v is read-only, an error will be raised. If 'time' is not zero and there is a helper, then the helper will take the keyboard focus and will display its new contents in replace-mode, ready for the user to type something in its place. *) PROCEDURE Unselect (v: T); (* Put v into the no-selection state, without changing the current directory. *) PROCEDURE Refresh (v: T) RAISES {Error}; (* Update the display without changing directory. If the directory has had any change since the last time it was displayed, then it will be displayed afresh. Error is raised only if the directory has become inaccessible for some reason; in this case, the browser goes to the empty state, so that if the client catches Error and takes no other action, the browser will be empty but not broken. *) PROCEDURE GetFile (v : T; shortName: BOOLEAN := FALSE; normalize: BOOLEAN := TRUE ): TEXT RAISES {Error}; (* =TextList.First(GetFile(v, shortName, normalize)) *) PROCEDURE GetFiles (v : T; shortName: BOOLEAN := FALSE; normalize: BOOLEAN := TRUE ): TextList.T RAISES {Error}; (* Get the currently selected files of v. Returns NIL if none is selected. Returns full pathnames unless 'shortName' is true; then returns only the name relative to the current displayed directory. Currently selected objects that are directories will have a '/' on the end. Normalizing is done during Set and when the user types Return in the helper. It consists of expanding a tilde if present, and checking to see that all directories in the pathname really exist and are accessible. It should be done during GetFiles if and only if that GetFiles is a synchronous result of some user action, e.g. pressing a button or invoking a menu command. Normalizing can lead to the discovery of errors, raising Error; no exceptions are raised if 'normalize' is false. *) PROCEDURE GetDir (v: T): TEXT; (* Get the current displayed directory of v. Always reports a full pathname, excluding a '/' on the end. Returns "" if v is in the "empty" state. *) PROCEDURE IsDir (filename: TEXT): BOOLEAN; (* A convenience procedure to test for a '/' on the end of a filename, indicating that it is a directory. It does NOT actually query the filesystem, it only looks at the form of the name. *) END FileBrowserVBT.