Visual LISP System Utilities30 Apr, 2000 By: Bill Kramer
Figure 1. After you create this dialog box, your users can select files in the C:\WINDOWS sub-directory.
When AutoLISP was first introduced, it was a tool for automating tasks in AutoCAD. Initially it had the ability to provide procedural control for the AutoCAD command system; that is, it allowed a programmer to apply logic and AutoCAD commands in concert to accomplish something such as drawing a detail or finishing a drawing given a set of rules. As time passed, AutoLISP gained abilities that made it more of a programming language, including greater access into AutoCAD drawings and dialog-box interfaces. All these enhancements have led to Visual LISP, a very powerful programming language full of functions that can access not only the AutoCAD drawing system but also the operating system in which AutoCAD is running.
This month, let's explore some of the features in Visual LISP that relate to the operating system and develop a utility for our toolbox that has been frequently requested by readers. The example application is a dialog box that allows you to navigate around the directories and select multiple files from a given directory. In the past, the lack of access into the operating system and file directories from AutoLISP didn't allow for such a utility to exist without C/C++ programming. Using Visual LISP in AutoCAD 2000, you can do all of your application-level coding in LISP.Getting Started
To get started in this application you need to learn about a couple of functions provided in Visual LISP that have not been available to AutoLISP programmers in the past. The key function to making this utility work is (VL-DIRECTORY-FILES) that returns a list of file names. When used without any parameters, (VL-DIRECTORY-FILES) will return a list of all the file names in the current directory. Generally this is the working or drawing directory. The file names are returned with the name
You can refine the resulting list through the use of three arguments to the (VL-DIRECTORY-FILES) function. The first is the name of the directory to list and is in the same format as most file names in AutoLISP. That is, you can use a forward slash (/) character or double backslashes (\\) to denote the separation between sub-directories. Suppose you wanted to get a list of the files in the directory TEMP inside the WINDOWS directory on the C:\ drive. The call to the function can look similar to any of the following or some combination of the slashes in a single name.
The directory name does not have to be in uppercase. In fact, it can be either or a mixture of the two as needed for readability in your program. No matter which convention you follow, you'll get the same results-a complete list of the files in the Windows Temporary directory.
If your application does not need to see all the files in a given directory and is interested in only a particular type or name of file, the second argument to the (VL-DIRECTORY-FILES) function comes into play. This argument is a mask or pattern name for the files you are trying to get. For example, if you want only the drawing files (DWGs) in the directory C:\WORK, the function call might look similar to one of the following.
(VL-DIRECTORY-FILES "C:/WORK" "*.DWG")
(VL-DIRECTORY-FILES "C:\\WORK\\" "*.dwg")
Keep in mind that you can use forward or backward slashes and/or upper- or lowercase to specify the directory. In the case of the file mask, the wild-card characters can be employed. In this example you asked for all drawings (files that have an extension of DWG). If you were interested in only the drawings that started with the character P, you could use the mask value of P*.DWG. You can also use the question mark as a wild-card character for a match with any given character. The pattern ?123.DWG will result in a list of files with the first character being anything and the next three being the digits 123. This format for matching file names corresponds to the standard used in the MS-DOS environment. If nothing is provided or nil is used as the second argument to the (VL-DIRECTORY-FILES) function, then a mask of *.* is assumed.
A third argument to the (VL-DIRECTORY-FILES) function is an integer flag. If the value is -1 (minus one), the function will return directory names only. If the value is 1 (one), then file names are the only results. A value of 0 (zero) will return both directory and file names. The value zero is the default value if the argument is not used when calling the function. The following expressions will return a list of the directories found on your C:\ drive.
"C:/" "*.*" -1)
"C:/" nil -1)
While the next expression will return a list of file names in the root directory of the C:\ drive, it will not include any of the sub-directory names.
nil 1)Alphabetizing Your List
The (VL-DIRECTORY-FILES) function solves one of the difficulties involved in giving a list of file or directory names to a user. Another problem that's just as easy to solve is getting them in alphabetic order. To do that you can use the (VL-SORT) function; it will sort a list of items in any order desired; you just specify the sorting order as part of the call to the function. The first argument supplied to (VL-SORT) is the list to be sorted. It should be noted that (VL-SORT) expects to work with unique member lists in which each member of the list is supposed to be unique. The (VL-SORT) function simply reduces any duplicates to a single entry, and, in the case of a directory listing, this will not be a problem since each member of a directory must be unique (you cannot have two files with the same name and extension in a single directory). The uniqueness feature of the (VL-SORT) function is mentioned at this point in case you plan to use it elsewhere in your programming applications.
The second argument to (VL-SORT) defines how to sort the list. Obviously each different type of list will need to be sorted in a different way depending on the nature of the list and application. The (VL-SORT) function is a very powerful tool just as much as the Visual LISP library of functions are. But a word of caution-with power comes complexity. For example, you must supply a function or subr that accepts two arguments and returns a true (non-nil) or false (nil) answer. When coding, you can use a (LAMBDA) function with two parameters, your own function or one of many comparisons available in the LISP language such as greater than (>) or less than (<).
The basic idea is that the comparison test will return true if the first parameter precedes the second parameter in the sorted list. Put simply, each element of the list is compared with all the other elements in the list and placed after the last value in which the test fails but before the first value in which the test returns true. For example, suppose you have a simple list of numbers that you want to sort in ascending order (the minimum value of the list is found first followed by the next greater value and so forth to the largest value). Suppose you have a list such as (3 5 1 2) that is not in any particular order. If you want to sort it in ascending order, you use the predicate test for less than. In this case each element is placed such that the previous member in the list is less than the following number and the next member in the list is greater than that number. So, (VL-SORT '(3 5 1 2) '<) will return the list (1 2 3 5). If you want to sort the list in descending order, you can use the greater than comparison in the same way as shown in the following example: (VL-SORT '(3 5 1 2) '>) will return the list (5 3 2 1).
The greater and less than tests will accept two arguments and will return a true (non-nil) or false (nil) result depending on the numbers presented. This is exactly what the (VL-SORT) function is looking for in order to operate. Of course, most of the time the list may not be so simple and the comparison becomes more complex. Consider the following example in which the second element of a nested list is used.
(setq Alist '((1 3) (2 2)
(3 4) (5 2)))
The (VL-SORT Alist '(lambda (X Y) (> (cadr X) (cadr Y)))) program will return the list ((3 4) (1 3) (2 2) (5 2)). Study this example closely if you don't see how the sort comparison works. In this case the (LAMBDA) function is given two values to compare. Each value is a list with two members. The function (CADR) retrieves the second member of the lists, and then the comparison is made. If the second value of the first list is greater than the second value in the second list, the result is true ('T). Otherwise, the result is false (nil).
A lot of AutoLISP programmers are still not completely comfortable with using (LAMBDA), and, if you fall into that category, then you should consider employing a function definition instead. Unless you are at ease with (LAMBDA), a function may be hard to read quickly when just scanning the code.Sorting File Names
Str_compare, as shown in Listing 1, is a simple function that compares two strings after converting them to uppercase. The less-than operator compares the strings and will return 'T if the first string is alphabetically less than the second string. Let's use this function when sorting the directory and file names obtained with the (VL-DIRECTORY-FILES) function. In this case you want to produce a list of file (or directory) names that are in ascending alphabetic order.
Suppose you have obtained a list of names and stored them in a list called NAMES. The function call (VL-SORT NAMES 'STR_COMPARE') will result in the list NAMES being sorted in ascending alphabetic order. This quick example sets up the list and shows how this works.
(setq NAMES '("First" "Second" "Third" "Fourth" "Fifth"))
The function call (VL-SORT NAMES 'STR_COMPARE) will return the list of strings in alphabetic order as follows; ("Fifth" "First" "Fourth" "Second" "Third").
You are now armed with enough code to take on the problem of displaying a dialog box containing file and directory names. And since this dialog box is of your own design, you can enable multiple selections in the list box containing the file names. Figure 1 shows the dialog box in action selecting files in the C:\WINDOWS sub-directory.
The dialog box contains two list boxes: one for the directory names and another for the file list. A pop-up list at the bottom contains the file mask for the selected files. Three text labels are used to show the current directory, number of sub-directories and the number of files in the current directory.
The utility function is 'FileSelect', as shown in Listing 2. This function accepts two values: the name of the base directory to display (DIR) and a list (PAT) containing the masks that can be selected in the pop-up list box. The user can navigate to other directories, thus our function must return the complete file name including the directory from which the file(s) were selected. The function (FileSelect) will return a list of the files chosen (even if one file is selected, a list will be returned).
The utility function starts by loading a dialog box named CDNC5-00.DCL that must be located in the AutoCAD search directory. If you are using this utility in your own applications, copy the dialog box definition into the DCL file for your program and skip the step of loading the DCL each time the routine is run. When testing the function, the best place to put the DCL and LSP file is in the AutoCAD program or support directory so that they may be located easily by AutoCAD. After the dialog is loaded, a call to the (NEW_ DIALOG) subr will display the FILES dialog.
It is now time to populate and prepare the dialog box for user interaction. Call the (Refresh_ Display) function to place the directory contents into the appropriate tiles.
I've covered dialog-box programming in previous articles, so I won't spend any more time looking at the main function. Instead, let's look at the (Refresh_Display) function, as shown in Listing 3, since it is really the heart of this utility.
Looking at the code you can see that (Refresh_Display) starts by clearing the list box for the files. The reason for this is that the sorting and subsequent display of a long list of file names may take some time to complete. You don't want to confuse users with an older file list, so the first step is to simply clear the existing list box. The function also displays the message working in the text label at the top of the list boxes. Users should be aware that between these two actions the program is doing something and that they should simply wait for it to finish. If there are just a couple files to display, the message and cleared tile will only appear for a fraction of a second. You are doing this for larger directories that may take a few seconds to prepare.
The next step is to get the directory and file name listings by calling the (VL-DIRECTORY-FILES) function. Variable FL is set to the file names, and variable DR is loaded with the directory names themselves. Note the use of the 1 and -1 flags in the call-to-the-directory function. A flag value of 1 will result in only file names while -1 will return only directory names. With these lists in hand, you next sort them in alphabetic order for display (the (VL-SORT) function called with the string comparison function Str_Compare).
The Refresh_Display function also demonstrates some other handy system routines available in Visual LISP for file manipulations. If the variable SHOW_THE_DETAILS is set to a non-nil value, the file size, creation date and time will be displayed along with the file name. You can call the functions (VL-FILE-SYSTIME) and (VL-FILE-SIZE) to retrieve the values for these details. The (VL-FILE-SIZE) function simply returns the size of the file in bytes. Normally this value is an integer. However, it can exceed 32,767 (maximum integer size), so you use the (RTOS) subr to convert that value from a number to a string for display in the list box. If you check the DCL file for this dialog, you will see that tabs have been established to allow you to align the detailed values in columns within the list box.
The function (VL-FILE-SYSTIME) returns a list that contains the date and time when the file was last updated or created. The list returned has the values for the year (four digits)-month, day of the week, day of the month, hour, minute and second (in that order)-during which the file was last written or updated. Using a string concatenation (STRCAT) subr, you combine this information, with tabs, to construct the string you want to display in the file name tile. Keep in mind that this will only happen if the global variable SHOW_THE_DETAILS has a non-nil value; otherwise, just the file name is displayed.
After writing the list box values to the tiles via the (START_LIST), (ADD_LIST) and (END_LIST) subr sequence, the function updates the counts for the directories and files in the text labels DIRS and FILS. Now the display of the lists has been refreshed and the user can see the directory names as well as the file names.
The (Refresh_Display) function is not only called in the dialog-building process, it is also invoked in the callback functions that service the dialog box. Should the user select a directory, then all elements of the dialog box need to be redisplayed. When the directory is changed, the DIR variable is updated to reflect the new current directory; when the file mask is selected, the EXT variable is updated to contain that value.
The last callback function in this example utility is 'Picked'. This callback function runs when the user selects anything in the file-list box. In that case the index values are used to build a list named File_List that contains the names of the files selected along with the directory name.
This example shows how powerful the system's level functions in Visual LISP are for the applications developer. The ability to select multiple files is something that has been requested by many developers in AutoLISP, and now it is possible using these marvelous tools. Of course this is just the beginning; developers are being encouraged to use the functions just presented as a springboard into even more fantastic applications. But to do that, you must keep on programmin'! [Editor's Note: The complete code for this column is available. Download CDNC5-00.LSP and CDNC5-00.DCL.]