Using the fileXtra: Part 2
August 2, 2000
by Zac Belado
Last time on a very special Using Director
Last week we began our look at the fileXtra by examining the directory functions and two of the file functions. We'll finish up by looking at the file open and save dialogs, the rest of the file functions and the directory functions.
But before we start, a small note about last week's article. Kendall Anderson pointed out a small problem using mapped network drives under Windows. The fileXtra does not resolve network mapping references when specifying file names or paths.
So, specifying the drive using the mapped drive letter:
put fileExists("m:\pagefile.sys") -- 0
will work, but using the alternate Windows path to the same drive (as a network resource and not a mapped drive):
put fileExists("\\remote\Windows\pagefile.sys") -- -51
will not work.
I also wanted to mention that the directoryToList() function appears much slower under Windows than it does on the Mac. This is especially apparent if you try to list a directory that has a large number of files, like the Windows directory. On my PC it takes almost 90 seconds to display a list of files in the Windows directory. Consequently, it's very important to give the user some sort of feedback (like the hourglass cursor) when making a call to directoryToList() on the PC.
Let's get things rolling by looking at file open and save dialogs.
File Dialogs
The fileXtra provides two functions that allow you to display OS-specific open and save as dialogs. Both of these functions either return the full path to a file (if one was selected or created) or an empty string. Because the Mac OS and Windows dialogs have different features, the function calls for each OS are different.
The format for the open dialog is:
Windows
fileOpenDialog(initialDir, filterStr, dlogTitle, createPrompt, fileMustExist)
Mac
fileOpenDialog(initialDir, filterStr)
All of the parameters are required. Both functions need to be supplied with an initial directory, which will be displayed when the dialog opens. Both functions also require a "filter". The filter determines which files are displayed in the dialog; so you can, if you would like, allow the user to see files of only a certain type. As you would expect, this is handled differently on the Mac and the PC.
Under Windows, the filter is specified by a series of strings of the format <description>/<extension>. For example:
Word Files/*.doc
or
Director Movies/*.dir
These strings are separated by a forward slash. For example, if you wanted to add a Windows filter for Director movies, casts, and all files, you would use:
Director Movies/*.dir/Director Casts/*.cst/All Files/*.*
On the Mac, you need to know the type code for each type of file. As well, on the Mac you are limited to a total of four file types. There is no limitation on the PC. So, if you wanted to limit the open dialog to display only Director 7 movies and casts, you would have to find the type codes for these files and then make a string using them. There are several utilities on the Mac that allow you to view this code. In this case, the type for a Director 7 movie is MV07, and for a cast file it is MC07. The filter for the Mac version of the fileOpenDialog() function that displayed only these types of files would therefore be:
"MV07/MC07"
You can also use commas for the Mac function call, so:
"MV07,MC07"
is also acceptable. You can also just pass an empty string as a filter to get the dialog to show all files.
The Windows function also has three other parameters: dlogTitle, createPrompt and fileMustExist. You can use dlogTitle to add a title to the Windows dialog. It defaults to "Open" if you don't provide any text (i.e. you supply an empty string ""). The Windows dialog can also be prompted to create a file. If the createPrompt parameter is TRUE and the user enters a filename in the open dialog (as opposed to selecting a file), then the OS will ask the user if they want to create that file. The user can still enter a filename (and the function will return that filepath), even if the createPrompt parameter is set to FALSE. Setting it to TRUE simply adds another level of error checking to the process.
The final parameter, fileMustExist, ensures that the user either selects a file or enters a new filename. If not, the dialog will remain up until the user fulfils this requirement. This parameter, like createPrompt, is a boolean variable.
If you wanted to make a Mac open dialog that displayed only Director 7 movies and Word files, you could use:
filter = "MV07,W8BN" file = FileOpenDialog(the moviePath, filter)
On the PC, you could build a dialog that displayed only Director movies, casts, and any file using:
fileOpenDialog("c:\", "Director Movies/*.DIR/Director Casts/*.cst/All Files/*.*", "Please open a Director movie", FALSE, FALSE)
Because Windows cannot determine different versions of applications, you can't make the same differentiations between versions of files that you can on the Mac. On the Mac you are able, by finding the right type code, to get the open dialog to display only Director 8 movies, and not Director 7 movies. Under Windows, you can only filter by extension, so all .dir files would be displayed.
The save-as dialogs are very similar.
Windows
fileSaveAsDialog(initialDir, filename, dlogTitle, overwritePrompt)
Mac
fileSaveAsDialog(initialDir, filename, prompt)
There are only two differences. The Mac function now has a parameter called prompt. Unlike the Windows dialog title parameter, on the Mac this prompt appears above the text entry field for the file name. If left blank, this defaults to "Save As:".
The Windows overwritePrompt parameter (a boolean value) determines whether the OS prompts the user if they supply a file name that already exists. As with the fileMustExist parameter in the file open dialog, this just adds another level of error checking to the Windows dialog.
Sanitised for your safety
Before I continue the article, I'd like to take a moment to warn you about some of the Lingo commands we will be discussing. Many of the remaining functions and commands deal with moving, copying and deleting files and directories. You can do real, actual damage to the contents of your hard drive using these commands, so always check your code before you actually execute it. Even in the Message Window. As well, the sample movie that accompanies this article contains code that may, if used incorrectly or haphazardly, delete files and directories on your computer.
You have been warned. If any of the following code replaces your command.com file or your System folder with a Pokemon bitmap file you have only yourself to blame. All the code has been tested on my Macintosh and my Windows machine, and will not cause any damage if used correctly.
File functions
Last week's article discussed two file functions: fileExists() and getFileModDate(). There are three more that we need to look at: renameFile(), deleteFile() and copyFile(). All three of these functions are very basic.
The renameFile() function takes two parameters: the path to the original file; and a target path incorporating the new file name. So, for example, if you wanted to change the name of the fileXtra HTML documentation on the Mac from "FILEDOCS.HTM" to "foo.htm", you could use:
renameFile ("Jane:Desktop Folder:FILEDOCS.HTM", "Jane:Desktop Folder:foo.htm")
The first parameter is the path to the current file, and the second parameter is the path to the file after it has been renamed. You cannot use wildcards in the renameFile() function.
The deleteFile() function takes a single parameter: the path to the file that you want to delete. The function doesn't display any prompt asking you to confirm the deletion, it simply deletes the file, so you need to be careful about how you use it.
So, if you wanted to delete the fileXtra docs from your PC, you could use:
deleteFile(C:\docs\FILEDOCS.HTM)
Additionally, under Windows, you can supply the function a wildcard to get it to do things like delete all the .HTM files in a directory.
deleteFile(C:\docs\*.HTM)
The copyFile() function is similar to the rename() function in that it takes two parameters: a source path and a target path. Both of these paths need to be valid. In other words, you can't get the fileXtra to create a series of directories with this command. All it will do is copy a file from one existing directory to another. For example:
copyFile ("Jane:Desktop Folder:foo2.htm", "Jane:projects:foo2.htm")
Even though the file name isn't going to change, you still need to supply one. If you try to use copyFile without a filename in the path, like this:
copyFile ("Jane:Desktop Folder:foo2.htm", "Jane:projects:")
you will generate an error.
But you can actually change the name of the file as you copy it. So, this example:
copyFile ("c:\netlog.txt", "d:\test\netlog.rtf")
would copy the file from the c drive to d:\test and also rename the file to netlog.rtf at the same time (although you haven't actually changed it to rtf format.)
The copyFile() function also allows the use of wildcards, under Windows, in the source file path. If you do use wildcards, you have to make sure that you do not supply a filename in the target parameters. So this:
copyFile ("d:\test\*.rtf", "d:\test2\")
would work, but this:
copyFile ("d:\test\netlog.rtf", "d:\test2\*.rtf")
would generate an error. This is slightly different than you would expect if you are familiar with DOS wildcards.
Directory Functions
Last week's article looked at the directoryToList() function. That leaves 6 functions left to examine: directoryExists(), createDirectory(), deleteDirectory(), XdeleteDirectory(), copyDirectory() and XcopyDirectory().
The first two are fairly simple. The createDirectory() function takes a path (like c:\windows\desktop\newFolder) and attempts to create a folder at that path.
createDirectory("c:\windows\desktop\newFolder")
In this case, it would attempt to create a directory called "newFolder" in the Windows desktop directory.
Similarly, directoryExists() takes a path and checks to see if the directory at that path exists. Similar to fileExists(), it will return a 0 if the directory exists.
put directoryExists ("jane:desktop folder") -- 0
The four remaining functions are actually two sets of similar functions with a "safe" and a more "dangerous" version of each. The XcopyDirectory() and XdeleteDirectory() functions are simply more powerful (and dangerous) versions of the copyDirectory() and deleteDirectory() functions. Let's look at the "safer" versions first.
The copyDirectory() function takes two parameters, a source path and a destination path. The source path is the path to the existing directory, and the destination path is the path to the folder once it has been copied.
put copyDirectory("jane:desktop folder:test", "jane:projects:test") -- 0
As you saw with the copyFile() function, you can change the name of the final directory to effectively rename it.
This command:
put copyDirectory("jane:desktop folder:test", "jane:projects:test2") -- 0
will copy the directory, and also rename it from "test" to "test2".
The copyDirectory() function will copy onlythe directory and any files at the root level of the directory. Any subdirectories and the files they may contain will not be copied.
Examine the snapshots below. The "test" directory is copied to "test2", but the "testInside" directory and the file inside it are not.
You can avoid this problem by using the XcopyDirectory() function instead. Unlike copyDirectory() it "walks" down the entire directory structure and copies all the subdirectories and files instead of just the files at the root level.
Note: On the Macintosh, invisible files are not copied by either the copyDirectory() or XcopyDirectory() functions.
The deleteDirectory() function takes a single parameter, the path to the directory to be deleted, and deletes that directory. The caveat is that it will not delete a directory that has any files or subdirectories in it. So, if you tried to delete the directory from the copy example above:
put deleteDirectory("jane:desktop folder:test") -- -18
it would generate an error, since that directory contains files. If the directory did not contain any files or folders, it would then delete that directory.
The XdeleteDirectory() will delete the directory and any files and subdirectories in it. It will do this without any prompt, so you can quickly and easily destroy your machine with a simple line of code.
Errors
The fileXtra functions normally pass back an integer value as a return. Typically, if the function was successful, it returns a 0; if not, it returns an integer representing an error code. Both this week's and last week's sample movies contain a function called fileXtraError(), which will take that integer and return a string describing the error.
A few functions don't follow this pattern. The fileOpenDialog() and fileSaveAsDialog() functions don't return errors at all, but rather a pathname or an empty string. The getFileModDate() function will return a RETURN character under Windows, and appears to return total garbage on the Mac.
put getFileModDate ("d:\foo.txt") -- " " put getFileModDate("jane:desktop folder:foo.txt") -- ".ø[ƒ.îõp.q.¯????dó$.ø&-.ÜvD.úÍ8"
The directoryToList() and drivesToList() functions return VOID if the path is incorrect, or if there has been some sort of error.
I'm not a Norton utility but I play one in this article
Let's put some of this information to use. Many of you may be familiar with a product called Norton Commander. It was originally a DOS product that acted as a replacement for the rather shoddy file manager that shipped with DOS. It also exists as a Windows product, but has been less successful under that OS. In fact, it was so successful as a DOS product that it is one of the few DOS/Windows programs to get translated to Unix, in the form of the Midnight Commander.
The main premise of the application is that it has two displays that list files and directories, and it allows you to do things like copy files from one directory to another, rename files, delete files and more. The sample movie that accompanies this article won't try to duplicate everything in Norton Commander (due to time constraints and also because it's been years since I used it and I don't remember all the features), but will build quite a few useful commands into the movie.
A sample movie is available for download in Mac or PC format
The display of the files and folders is handled by the same behavior from the fileBrowser.dir movie from last week's article. There have been a few small changes. The behavior no longer colours the folders. It's missing a few properties that it no longer needs; and it has a new property, pLastSelect, to track which entry that the user has clicked on in the list of files and folders. It also now has a "back" command that can be called to get the behavior to display folders back up the directory hierarchy.
on moveBack me
-- move back up the path
oldDelims = the itemDelimiter
the itemDelimiter = pOSDelimiter
limit = pMyPath.items.count
if limit<=2 then
buildDriveList me
else
newPath = pMyPath.item[1..limit-2] & pOSDelimiter
the itemDelimiter = oldDelims
browseFolder me, newPath
end if
end
The behavior also has two new methods that are used to get the path that the Text member is displaying, and also to return the item, if any, that the user has selected.
on returnPath me
return pMyPath
end
on returnSelected me
-- return the the selected line
if pLastSelect <> 0 then
return pMyField.line[pLastSelect]
else
return ""
end if
end
The movie has six main functions:
- copy - copy a file or folder (basic copy not Xcopy)
- move - copy a file and then delete it
- back - move back up the directory hierarchy
- rename - rename a file (uses MUI)
- delete - delete a file (deleting folder not implemented)
- make directory - make a new directory at the current path (uses MUI)
Download the sample movie and use it for a bit to see how it functions, how each of the buttons works, and what sort of errors/limitations the code has.
There are two copy and two move buttons in the movie. One set copies or moves files from the left window to the right, and the other set of buttons does the opposite. There are also two sets of delete, rename, make directory and back buttons, one for each Text member. Let's start out by having a look at the copy behavior.
The copy behavior is actually also the behavior for the move button. Since copying a file is the first step in moving it, it makes sense to combine these functions.
The behavior has three properties:
- pFrom - the sprite number of the Text member that the file is copied/moved from
- pTo - the sprite number of the Text member that the file is being copied/moved to
- pType - a symbol that is either #copy or #move to determine what function the behavior has.
property pFrom, pTo , pType
on getPropertyDescriptionList
pList = [:]
addProp pList, #pFrom, [#default:1, #format:#integer, #comment:"From target?"]
addProp pList, #pTo, [#default:1, #format:#integer, #comment:"To target?"]
addProp pList, #pType, [#default:#copy, #format:#symbol, #comment:"Copy or Move?", #range:[#copy,#move] ]
return pList
end getPropertyDescriptionList
When the user clicks on the copy or move button, the behavior initially does a bit of error checking. It first makes sure that the source and destination displays are currently displaying a directory. When the movie first starts, the Text member's each display a list of the drives on the computer and the behavior needs to check to see if this is the case. This is done by calling the returnPath() function of each sprite's "drive field" behavior.
-- make sure the targets have a path
fromPath = sendSprite(pFrom, #returnPath)
toPath = sendSprite(pTo, #returnPath)
if fromPath = "" or toPath = "" then
errorMessage = "Either from or To drive is not valid"
alert errorMessage
exit
end if
Then the behavior checks to make sure that the source Text member has an item selected. This is done by testing the returnSelected() function of the "drive field" behavior.
-- make sure the from field has a selected value
selectedItem = sendSprite(pFrom, #returnSelected)
if selectedItem = "" then
errorMessage = "Please select an item from the source directory first"
alert errorMessage
exit
end if
Next, the behavior needs to determine if the selected item is a folder or a file. This is done by testing the last character of the selected item to see if it is a path delimiter (a ":" or "\").
-- make sure that the selected item isn't a folder
lastChar = selectedItem.char[selectedItem.chars.count]
if lastChar = ":" OR lastChar = "\" then
if pType <> #move then
-- we need to do a directory copy
fromPath = fromPath & selectedItem
toPath = toPath & selectedItem
success = copyDirectory(fromPath, toPath) = 0
if NOT success then
errorMessage = "Error copying directory" & fileXtraError (success)
alert errorMessage
end if
else
-- but not a move
errorMessage = "DOUG Commander can't move folders yet."
alert errorMessage
exit
end if
If the item is a folder, then you need to check what type of function this behavior has been set to. If it is a #move behavior, you need to display an alert, since we don't currently support moving folders. If it is a #copy behavior, then you can safely copy the folder.
Similarly, if the selected item is a file, you copy it. Then, if this is a #move behavior, you also delete the file from its original location.
else
-- so we copy the file after all that picky error checking...jeez some people!
fromPath = fromPath & selectedItem
toPath = toPath & selectedItem
success = copyFile(fromPath, toPath) = 0
if NOT success then
errorMessage = "Error copying file" & fileXtraError (success)
alert errorMessage
end if
-- do we need to delete the source?
if pType = #move then
success = deleteFile(fromPath) = 0
-- refresh the source
sendSprite (pfrom, #refresh)
if NOT success then
errorMessage = "Error deleting file" & fileXtraError (success)
alert errorMessage
end if
end if
end if
Once all that is done, the behavior gets each Text member to refresh the list of files and folders it is displaying.
-- refresh the targets
sendSprite (pTo, #refresh)
sendSprite (pFrom, #refresh)
end
The rest of the buttons in the movie only have a single target and are, to a degree, much simpler. For example, the "delete" behavior only has a single property, pTarget, which indicates the Text member that it will send its commands to.
property pTarget
on getPropertyDescriptionList
pList = [:]
addProp pList, #pTarget, [#default:1, #format:#integer, #comment:"Delete target?"]
return pList
end getPropertyDescriptionList
When the behavior is triggered it does some error checking, similar to the copy behavior. This is to ensure that the target Text member currently has a path (i.e. it isn't just displaying a list of drives), the user has selected an item to be deleted, and the selected item is not a folder.
Once all of those checks have been passed, the behavior pops up a MUI alert to make sure that the user does indeed want to delete that file (it always pays to be safe).
-- ask the user if they really want to delete the file
alertObj = new(xtra "MUI")
alertInitList = [#buttons: #OKCancel,#title: "Delete this file?",#message: "Are you sure that you want to delete this file?",#movable: FALSE,#default: 2,#icon: #Question]
if objectP (alertObj) then
alertResult = Alert(alertObj, alertInitList)
end if
If the user clicked on the "OK" button (this MUI alert returns either a 1 or a 2 to indicate that the user clicked the "OK" or "Cancel" button), then the file is deleted and the text member is sent a request to refresh its display.
-- did they say ok?
if alertResult = 1 then
-- delete the file
sourceFile = targetPath & selectedItem
success = deleteFile (sourceFile) = 0
if NOT success then
errorMessage = "Error deleting file" & fileXtraError (success)
alert errorMessage
end if
-- refresh the target
sendSprite (pTarget, #refresh)
end if
end
The rest of the functions work in a very similar fashion, with the exception of the rename and make directory behaviors. These behaviors look deceptively simple because they actually call a MUI dialog to allow the user to enter a name, while the actual renaming and directory creation is handled by another handler outside of the behavior. If you're unfamiliar with the working of the MUI Xtra, you should have a look at Robert Wingate's article on the subject.
Once you get accustomed to its peculiarities, the fileXtra is a very powerful addition to Director. It can allow you to not only do basic file operations, but also build some very "complicated" functionality with a minimum of code. In fact, if you look at the sample movies, almost all the code is used either to display and modify data in the two Text members on stage or to handle the MUI dialogs. Between the fileXtra and the fileIO Xtra you can easily create almost any type of file- or directory-related functionality you are possibly going to need.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.