LOOPE - Lingo Object Oriented Programming Environment by Irv Kalb
Section 4 - Miscellaneous Objects
Cross Platform applications
On of the great benefits of using Director is that, for the most part, when writing code in Lingo, we don't have to worry much about the details of making programs work on a PC and a Macintosh. Most of the code we write in Lingo is automatically"cross platform". The engineers at Macromedia that developed Lingo did a great job of hiding many cross platform details.
However, there is one area that we as programmers must deal with in order to make programs truly cross platform compatible: the file system. The file system of PC's and Macintoshes are very similar but different, and therefore we must build file specs differently on the different platforms. This chapter will give us a way to encapsulate the code needed to deal with the difference in file systems into a single object.
Externally Linked files
As you start to build larger and larger Director projects, we will often find that it makes sense to have assets outside of the Director program and utilize linked cast members. That is, a member in the cast which is basically a pointer to a file on the hard disk. And with many types of projects, the external media we may want to use can change over time. For example, we may want to utilize different external files as the program runs. With many media types (Quicktime, Flash, etc.), we could change the "filename" property of a castmember to point to different files at different times:
member("MyLinkedQuickTimeFile").filename = <someFullFileSpec>
Using this approach, we can have a program use many different external files of the same type, using only a single cast member as a placeholder.
Other external files
There could be many other types of external files that a program may need to open (to read and/or write). The program may have configuration files, status files, or other text files that remember the state from the last time the user used the program. The program could also have database files that supply information. We need a cross platform way of generating these file specs.
Project Structure
When a project involves external files (either linked or not linked) to the project, such files typically live in the same folder as the projector. In larger projects, it is often a good idea to group similar types of media into subfolders that live in the same folder as the projector. For example, we might have a subfolder for Sound, one for QuickTime, one for Flash, perhaps one for Students, etc. The file tree of a such a project might look like this:
We need the ability to generate a full file spec to any file like these within the project hierarchy.
Folder separator
The Windows and Mac operating systems use a different character as the folder separator. Windows uses the slash character, "\", while the Mac uses the colon ":". A typical path on windows might look like:
C:\SomeFolder\SubFolder1\SubFolder2\SubFolder3\Filename.ext
whereas a similar path on a Mac might look like this:
MyHardDisk:SomeFolder:SubFolder1:SubFolder2:SubFolder3Filename.ext
To determine the proper folder separator character, here is a little trick. There is a Lingo system variable called "the moviePath". It returns the full path to the current movie. The last character of that path will be the appropriate folder separator for the operating system that the program is currently running on. Therefore, we can use the following Lingo statement to get the folder separator character:
sDelim = the
last char of
the moviePath
Building the File Spec
In order to build file specs for each of the types of files mentioned above, we have to be concerned about building the full file spec using the appropriate folder separator (also known as a "delimiter" character). Without object oriented programming, the typical way to build file specs would be to declare a global variable and initialize it at the beginning of the program:
global gsDelim
on prepareMovie
gsDelim = the last
char of the
moviePath
-- anything else you want to do here
end
Then later, when we want to build a file spec, we make use of this separator character to move down the folder hierarchy to build a file spec. To get to a file called "abc.mov" in a project subfolder called QuickTime, we might use the following code:
sFileSpec = the moviePath & gsDelim & "QuickTime" & gsDelim & "abc.mov"
This does work and will generate the correct filespec for this file. However, this approach is often prone to error because you must remember to add gsDelim in between all subfolders.
The OOP approach using a Path Manager
Using an object oriented approach, we would want to have a way to have an object build such filespecs for us. The knowledge of the folder separator would be kept within the object so we wouldn't have to carry around that piece of information throughout the program. Ideally, we would build a method in the object that would build these paths for us. That's exactly what we will do.
Here is a simple Path Manager that will do what we want:
-- Simple PathMgr
property psDelim
property psApplicationPath
on new
me
psApplicationPath = the moviePath
psDelim = the last
char of
psApplicationPath -- get the delimiter
return me
end
on mBuildPath
me, lsFolders, sFileName
sPathToReturn = psApplicationPath -- starting
point
-- Add in subfolders
repeat with
sThisFolder in lsFolders
put (sThisFolder &
psDelim) after sPathToReturn
end repeat
-- If a file name was
passed in, add that to the end
if not(voidp(sFileName))
then
put sFileName after
sPathToReturn
end if
return sPathToReturn
end
on mCleanUp
me
nothing
end
To start with, it only has a new method, an mBuildPath method, and our standard mCleanUp method (which doesn't do anything in this object).
In the new method, we get the current application path and store it away in a property variable. Then, we get the folder separator character from the end of the application path, and store it away in a property variable. These two property variables will be used later to build paths.
The key to this object is the mBuildPath method. As inputs, it takes a list of folders and an optional file spec. The idea is really quite simple. The method builds up a path string by starting with the previously saved application path, then appending each given subfolder followed by the previously saved separator character. If there was a filename passed in, it appends that filename to the end of the string before returning the result. The file name is optional because there are cases where we might only need the folder path, without referring to a specific file.
Usage:
At the start of the program, we instantiate a Path Manager object and store the reference to it in a global variable called goPathMgr, like this:
global goPathMgr
on prepareMovie
goPathMgr = new(script
"PathMgr")
-- anything else you want to do here
end
Then when we want to build a path, we call the mBuildPath method of the object. To use the same example as we did earlier in this chapter, let's say we want to build a filespec to the file "abc.mov" in the QuickTime subfolder. We would do this by making the following call:
sFileSpec = goPathMgr.mBuildFileSpec(["QuickTime"], "abc.mov")
The power of this approach becomes more evident when in larger projects which involve subfolders within subfolders within subfolders of external files. For example, for better organization, we may want to have a "media" folder which contains a subfolder for each of your different media types. In this case, to get to a specific QuickTime file, we must go to the media folder and then into the QuickTime subfolder. To build such a path, we build up the hierarchy in the list of subfolders passed in to the mBuildFileSpec method like this:
sFileSpec = goPathMgr.mBuildFileSpec(["media", "QuickTime"], "abc.mov")
Alternative approach:
If, however, you think of filespecs with colons or slashes as separator characters to start with, then you could use that style as a different way (or an additional approach) to managing paths. You could specify the path layed out in either Windows or Mac OS format, and modify the path based to match the platform on which the code is running. For example, if you "think in Windows", then you could call a method like this:
sFileSpec = goPathMgr.mBuildFileSpecFromPath("media/QuickTime/abc.mov")
Or if you "think in Mac OS", then you could call a routine like this:
sFileSpec = goPathMgr.mBuildFileSpecFromPath("media:QuickTime:abc.mov")
Then you could build a new method in the Path Manager called mBuildFileSpecFromPath that would accept this type of path as a parameter. It would go through the input string and change all occurrances or colons or slashes to the value of the property variable psDelim, then return the appropriate full path.
Additional Features:
The Path Manager can be extended to allow for more functionality. For example, on a large project, we might make reference to certain subfolders very often, for example, we might often need to build file specs in the Sounds or QuickTime or Flash subfolders. We might like to compute such paths once, and save them away. Rather than saving such paths in global variables, e.g., gSoundsPath, gQuickTimesPath, etc. it would be a nice addition to the Path Manager to do this for us. This final version of the Path Manager has this ability:
-- PathMgr Full Script
property psDelim
property psApplicationPath
property plsSavedPaths
on new
me
psApplicationPath = the moviePath
psDelim = the last
char of
psApplicationPath -- get the delimiter
plsSavedPaths = [:] -- start as an empty prop list
return
me
end
on mBuildPath
me, lsFolders, sFileName
sPathToReturn = psApplicationPath -- starting
point
-- Add in subfolders
repeat with
sThisFolder in lsFolders
put (sThisFolder &
psDelim) after sPathToReturn
end repeat
-- If a file name was
passed in, add that to the end
if not(voidp(sFileName))
then
put sFileName after
sPathToReturn
end if
return sPathToReturn
end
on mSetSavedPath
me, symPath, sPath
plsSavedPaths[symPath] = sPath
end
on mGetSavedPath
me, symPath
sPath = plsSavedPaths[symPath]
if voidp(sPath)
then
alert("No
path has been saved for folder:" &&
symPath)
return ""
end if
return sPath
end
on mAddFoldersToPath
me, sPath, lsFolders
-- add in subfolders
repeat with
sThisFolder in lsFolders
put (sThisFolder &
psDelim) after sPath
end repeat
return sPath
end
on mGetFolderDelimiter
me
return psDelim
end
on mCleanUp me
nothing
end
The mSetSavedPath allows to us save away a path inside the Path Manager and associate it with a symbol. For example, we might have the following code at the startup of our program:
global goPathMgr
on prepareMovie Then to build the a filespec for a sound called "Hello.aif",
all we need is this: sFileSpec = goPathMgr.mGetSavedPath(#sounds)
& "Hello.aif"
goPathMgr = new(script
"PathMgr")
sSoundPath = goPathMgr.mBuildPath(["media",
"Sounds"])
goPathMgr.mSetSavedPath(#sounds, sSoundPath)
sFlashPath = goPathMgr.mBuildPath(["media",
"Flash"])
goPathMgr.mSetSavedPath(#flash, sFlashPath)
-- anything else you want to do here
end
The Path Manager above also has a method for getting the folder separator character should we need it. And it has another method that will add subfolders to any given path should we need to dig down deeper into a folder hierarchy
Conclusion
Using a Path Manager object allows us to isolate the knowledge of how path specs differ on different platforms, and put this knowldege into a single script. By encapsulating this into the Path Manager, the rest of the program can use paths without ever having to deal with the operating specific folder separator character. The Path Manager can also be used to store commonly used paths for use later in the program. The Path Manager can be extended by adding any methods we may need, to deal with paths in a cross platform way.