TweetFollow Us on Twitter

Dispatch Aliasing
Volume Number:5
Issue Number:11
Column Tag:Pascal Workshop

Related Info: File Manager (PBxxx) Finder Interface

Dispatch Aliasing

By Roger Horton, Winston-Salem, NC

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Dispatcher - An Application Aliasing and Launching Utility

[Roger Horton is a Research Adjunct Assistant Professor at the Medical School of Wake Forest University, and a private consultant. He is currently in a masters degree program in computer science at the University of North Carolina, and has been programming in the Macintosh environment since 1985.]

Recently, I completed a prototype of a database application for a dental office specializing in the treatment of facial pain. I choose 4th Dimension as the development environment due to its flexibility in reporting and for the ease with which external code routines can be incorporated. However, 4D has the habit of creating a large number of database files (for indexes, resources, data definitions, etc.), which can be very unsettling to the typical office user who happens to take a glimpse inside a 4D application folder. When the users reviewed the prototype they pointed this out, and requested that they be spared the intimidation of wondering what all the files were for. At this point I began to consider the options for simplifying the process of installing and starting the application.

Avoiding Confusion

A frequent point of confusion for users of custom database applications is setting up and running the application. Something as simple as starting an application can be difficult for novice Mac users, if they have to traverse too many volumes and folders. In other text oriented operating systems, developers could rely on shell scripts to simplify a user’s interaction with the file system. On the Mac, macro programs have sprung up to fill this void. These can help, but often add more complexity than they eliminate.

If the designer can personally walk each new user through the process of installation and startup, all is well. But all too often, the system is shipped to distant users who rely primarily on the documentation. As most of us have discovered, the knowledge level of the user varies widely, and the phone time spent in supporting these users, especially in the first weeks of use, can be considerable.

My goal was to keep the user out of the 4D application folder, with its many files, and allow a launch of the program from the Finder, using a single, easily identifiable icon. Finder alternatives were not considered, due to the user’s familiarity with the Finder and our desire to use Multifinder for coordination of other programs with the database application. I reviewed some macro processors that could potentially automate the launch process, but generally found that they were sensitive to the initial state of the desktop and were likely to be confusing to the average user. Since I hoped to simplify, instead of complicate, the training process, I considered a custom solution. My initial thoughts centered around writing a small utility program that would take care of locating and launching the database application. I had previously written code to launch one application from another and so I set about considering how to use this code as a basis for a custom launcher application. I should note that normally I would have developed this type of utility program in C, but found that MPW version 2.0.2 C did not support the in-line launch code (but does in version 3.0). This caused me to turn to MPW Pascal, although I tried to develop the code for easy translation to C (avoiding booleans, ‘with’ statements, etc.).

Help from the Finder

My first intention was to build a launcher utility program that could be placed outside the 4D database folder. It would know (or could be shown) the location of the database folder, and could set up the Finder information record such that 4D would open the database after it was launched. This required storing the location of the 4D folder in the launcher program’s data or resource fork. This appeared to me to be a workable, but inflexible approach to the problem. There was also the problem of storing data in a resource fork, which Apple now discourages due to lack of access control to resource forks on networked systems.

A second approach was to rely on some help from the Finder. Using information from the Desktop file, the Finder can launch an application when the user opens (or prints) one of its documents. By taking advantage of this ability, I could create a dummy document and set its filetype to the filetype of the launcher program. It could then be placed anywhere in the file system (directly on the desktop, for example) and still initiate the launch program without knowing where the program file was. No file system paths need be remembered, but the launch program would always have to reside in the same folder as the application to be launched, and information about the target application would have to be imbedded in the program code.

Generalizing the Dispatcher

Any good computer scientist would realize that up to this point my approach was a very narrow solution to the problem, and lacked the generality we all strive for. So, I chose a solution that allowed for greater flexibility in setting the launch parameters. To do this, I relied on the dummy launch document to store the launch parameters, rather than embedding them in the program code or in the resource fork of the launch program. By doing so, the dummy documents (hereafter called launch settings files) become a sort of alias for the program they cause to be launched. They also store additional information needed for setting up the launch of the target application. Using this solution, many settings files could be created, each capable of launching a different application. These settings files could then reside anywhere in the file system, with a single program acting as the dispatcher of applications, according to the data contained in each separate launch settings file. Hence the name “dispatcher”.

Remembering the Launch Parameters

To launch a target application, and tie in an associated launch document, the dispatcher program stores two data records in each launch settings file. The two records are the same, each containing the name of the file, the directory ID of its parent folder, and the name of the volume containing the directory (if the file is at the volume root level, the directory number is 2 and the directory name is the same as the volume name). Some additional information is also stored with each of the two records to help with the launch. The filetype is remembered, and can be used when it’s necessary to ask the user to help find a file that has changed location. Also as part of the data, a flag is stored that determines whether or not the target application will be told to open the document after it is launched.

Given that multiple dispatcher settings files can be present, it has to be easy to initialize and update the data in each one. To permit this, the dispatcher program tests the command key before launching the target application. If the key is down, a dialog window is presented that allows the user to alter the previous settings directly, or to change the settings by searching for the application and its associated document using the standard file dialog. It is also possible to set or unset the flag that determines whether the application should be instructed to open the associated document.

About Launching

Apple Technical Support has provided detailed code examples illustrating how to launch one program from another (in tech notes 52 & 126). I’ve based my code on these notes, including the recommendation not to use sublaunches. Avoiding sublaunches was not a significant limitation in my particular application, since there was no need to regain control after the launched application finished. In the event that you do use the sublaunch feature, its interesting to note that the Launch routine is now a function that returns a result when run under Multifinder. You can test the result of the launch and inform the user of errors. However, it turns out that a sublaunch, rather than a launch, must be performed in order to have Multifinder return a result code that can be processed by the sublaunching program.

The overall process of launching an application with the dispatcher requires a number of steps (Figure 1). The process begins with the user opening (by double-click or menu) the dispatcher settings file. This causes the Finder to load the data document’s working directory reference number (as well as other information) into the Finder information record referenced by the AppParmHandle global, and then launch the dispatcher. When the dispatcher gets control, it uses GetAppFiles to obtain the reference number of the settings file. This number is then used to locate and open the file for reading/writing. The two data records, one for the target application and one for the associated document, are read in. If the dispatcher can open the file, but can’t read any records, it assumes the data has not been initialized, and sets up some initial dummy data. Normally this is not necessary, since new data documents can be created by duplicating existing documents and then editing the existing settings. However, when setting up the dispatcher for the first time, a file utility needs to be used to create the first data document. Remember to set the filetype to ‘LCHD’, the dispatcher’s settings file filetype (the dispatcher’s filetype is LCHA).

Having read in the necessary launch parameters, the dispatcher must now take care of some housekeeping related to the Finder information that will be passed on to the target application. First the AppParmHandle is obtained from the system global variable at memory location $AEC. The current heap zone is set to the system heap, and the existing handle is disposed. A new handle is allocated in the system heap allowing sufficient space for the information related to a single target application document. Then the current heap pointer is set back to the application’s heap. I can recall the admonition in one of Apple’s early Macintosh training courses to avoid allocating anything in the cramped system heap of the 128K and 512K Mac’s. Fortunately, Apple now provides a larger, variable size system heap and looks more favorably (I think) on developers allocating structures there.

Figure 1.

With a new Finder information record allocated, the launch data is loaded into records corresponding byte-for-byte to the Finder information records, and the records are copied to the system heap at the location referenced by the newly allocated handle. This handle is then loaded into the system global at location $AEC. Be sure to note that if the target application is to be launched without an associated document, then the Finder information must be zeroed out correctly, since the Finder will not be intervening to perform its usual housekeeping. With the application parameters correctly loaded, all that is left to do is to set the default working directory to the target application’s directory and call the launch routine.

Navigating the HFS

Something should be said about the way the dispatcher navigates the hierarchical file system. After the target application and document are initially located, the dispatcher must assure that enough information is saved to permit them to be located on subsequent invocations.

To adequately specify a file to the file system, a number of options are available. A full pathname from the root volume is one possibility. However, tech note #238 discourages this, since the file’s directory may be moved, making the pathname invalid (also note that system 7.0 will provide unique 32 bit file ID numbers). For current system versions, the dispatcher remembers the filename, directory ID number, and the volume name, and uses them together to locate the target file. Since multiple volumes may be mounted, the program checks the stored volume name against each mounted volume for a match. It is possible that two volumes might have the same name and confuse the dispatcher, but this is uncommon and left as an enhancement to the program. Having located the volume, the program tests for the presence of the target file by using the filename and directory ID number. This process is carried out for both target files (application and associated document) and, if successful, is followed by the opening of working directories for both files.

A number of errors can occur while trying to locate the target files. Most commonly, one or both of the files might have been moved to another directory and/or volume. If this occurs, the program responds by asking the user to find the missing files, using the standard file dialog. As each file is found, the volume name, directory ID, and filename are saved so as to be available during the next use of the dispatcher. After the correct location information is obtained, it is written back to the settings file prior to launching the target application. If any unrecoverable errors occur, or the users cancels any of the dialogs, the dispatcher terminates without carrying out the launch.

One other point that should be made comes from personal experience with the dangers of using PBHSetVol. Anyone considering using the low level file manager calls should make sure they read tech note #140. Normally, most file manager calls will accept a working directory reference number in place of a volume reference number. This maintains compatibility between the flat and hierarchical file systems. Unlike other calls though, PBHSetVol has the habit of taking a working directory reference number in ioVRefNum and then setting the default directory back to the root. This can cause more than a few minutes of confusion trying to find out why files suddenly can’t be found.

Figure 2.

Using and Extending the Dispatcher

The dispatcher program has been written to require minimal user intervention. Ideally, a typical user should not even know that an intermediate program has been part of the launch of the target application. However, when initializing the file location data, or accommodating the movement of a file to a new location, the dispatcher’s settings dialog must be used (Figure 2). Its occasionally useful to view the dialog just to confirm what the settings are, especially if the settings haven’t been used in a while. The information presented in the dialog is very simple. Two edittext items are present to allow the user to enter the name of the target application and associated document. A check box item is also present that lets the user specify whether the application should be launched with or without the associated document.

A second method to specify the target file data is also provided. A “Search” button is present that, when pressed, sets the filenames to “XXXXXXXX” and goes directly to the standard file dialogs to locate the files. This method completely clears the previous filenames, and normally is used when new settings are being created.

As mentioned above, the dispatcher program and data documents can be located anywhere in the file system. This has been especially handy for me when using Multifinder. I’m sure that any Multifinder user is familiar with the desktop clutter that occurs when more than one application is open. Having a lot of Finder folders open on the desktop only adds to this clutter. My solution is to keep a set of dispatcher settings files directly on the desktop corresponding to the projects on which I’m currently working (Figure 3). All other folders are kept closed. This makes for much less clutter, and saves me the trouble of opening and closing folders as I launch each application.

Figure 3.

In my case, the original purpose of the dispatcher was to make installation and launching easier for users of 4th Dimension custom applications. My database application is supplied with the dispatcher located in a folder amongst the other 4D files. The settings file is located outside this folder. Installation simply involves copying the settings file and database folder onto the user’s hard disk. The user never has to look inside the database folder, except from the standard file dialog when first installing the program. If the volume on which the application is to be installed happens to be known ahead of time, then its possible to completely preconfigure the settings file.

Its easy to conceive of some enhancements to the dispatcher that would make it more useful. Options could easily be added to allow more than one data document to be associated with the target application. In this way, an application could be opened with a number of associated documents, similar (although much more limited) to a user startup script in the MPW environment. Provision could also be made for retention of the location of the dispatcher program in the data documents. This would make it possible to use the dispatcher with Finder substitutes. The dispatcher could also be made to create its own settings files, making initial use of the program simpler. Its very likely that future developments in the Mac operating systems (real file aliasing, etc.) will help provide for some of the features of the dispatcher program. But for the time being, it provides users with an intuitive method of launching custom database applications. Beyond that, it can help to reduce desktop clutter while providing a general method for launching any application with an associated document.

Listing:  dispatcher.make

dispatcherƒƒdispatcher.r
 Rez dispatcher.r -o dispatcher -append
 SetFile -a B dispatcher -c LCHA -t APPL
dispatcherƒƒdispatcher.p.o
 Link dispatcher.p.o 
 “{Libraries}”Interface.o 
 “{Libraries}”Runtime.o 
 “{PLibraries}”Paslib.o 
 -o dispatcher
dispatcher.p.o   ƒ dispatcher.p
 Pascal dispatcher.p
Listing:  Dispatcher.p

{
 File:      dispatcher.p
 Program:   dispatcher
 Author:    Roger A. Horton
              Verity Software Systems
              Copyright 1989, All Rights Reserved
 Creation:  6/4/89
 Purpose:   Create a small utility program to launch applications
from the Finder with associated launch documents. This
is especially useful for custom applications that require
a number of files, and that may cause a user confusion in
installing and launching the application. 
 Technique: This program performs a launch (not a sublaunch) using the 
code of Mac Tech Note #126 as a model. The utility does not directly 
initiate the launch, but relies on associated
data documents to initiate the process, and to determine
which application and documents are to be launched. The 
launch utility and document(s) can be located anywhere in 
the file system. By double clicking on a launch document, 
the user causes the Finder to locate and launch the launch
utility program. The utility then reads the launch data 
document to obtain the name and location of the application
and its associated document. The Finder application parameters
are then set up, and the application is launched. If the 
location of the application or its associated documents has
changed since the last launch, the user is prompted to find
their new location. In essence, the launch utility documents
act as aliases for target applications, and add additional
usefulness by providing for automatic control of associated
application documents.

   Program History:
}

program scan;
{$D+}               { Macsbug/TMON symbols    }
{$R-}               { Turn off range checking }
uses
 { Macintosh toolbox units } 
 PasLibIntf,Memtypes,QuickDraw,OSIntf,ToolIntf,PackIntf,
 SANE, PrintTraps, ROMDefs
 { Custom units }

const
 active = 0;    { for use in controls and menus }
 inactive = 255;    

 DfltDlogID = 200; { resource ID for config dialog }
 StopDlogID = 201; { resource ID for stop alert dialog }
  
 AppParmGVar= $AEC; { AppParmHandle sys global variable }

type
 { general types }
 pLaunchStruct = ^LaunchStruct; { Launch record }
 LaunchStruct = record
 pfName: ^Str255;   { pointer to application name }
 param: integer;  { alternate video,audio buffers }
 LC: packed array[0..1] of char; { extended parameters }
 extBlockLen: longint; { extra block length }
 fFlags: integer;         { finder file info flags }
 launchFlags: longint;  { bits 30,31 = 1 for sublaunch }
 end;

 FileLInfo = record      { Finder files }
 vRefNum: integer;  { 2 bytes }
 fType: OSType;     { file type - 4 bytes }
 version: boolean;  { 1 byte } 
 unused: boolean;   { 1 byte }
 fName: Str255; { 1 length byte, variable length string }
 end;

 APHdl = ^APPtr;  { Finder info }
 APPtr = ^APRec;
 APRec = record
 message: integer; { 2 bytes }
 count: integer;        { 2 bytes }
 files: array[1..1] of FileLInfo;
 end;
 LaunchData = record {application & document data settings}
 vName: Str255;    { volume name }
 vRefNum: integer; { working directory reference # }
 dirID: longint;   { directory ID # }
 fName: Str255;    { file name }
 fType: OSType;    { file type }
 useIt: integer; { whether to open associated document }
 end;
var
 { file system parameter blocks }
 myPB: ParamBlockRec;
 myHPB: HParamBlockRec;
 myInfoPB: CInfoPBRec;
 myWDPB: WDPBRec;
 { launch data records }
 appLData: LaunchData;  { application }
 docLData: LaunchData;  { document }
 { miscellaneous }
 done: integer;   { done flag }
 lcnt: integer;   { loop counter }

{ ****** Utility functions and procedures ****** }
procedure AlertMsg(msgStr1, msgStr2:Str255);
 { Procedure to print alert message. 
 To print numerics, use something like this:
 testStr1, testStr2: Str255;
 NumToString(reqCnt, testStr1);
 NumToString(reqCnt, testStr2);
 AlertMsg(testStr1, testStr2); }
var
 theRect:rect;
 ignore:integer;
begin
 ParamText(msgStr1, msgStr2, ‘’, ‘’);
 ignore := StopAlert(StopDlogID,NIL);
end;   { procedure AlertMsg }

{ *********      Error Handling     *********** }

function GetErrorMsg(result:OSErr):string;
begin
 result := abs(result);
 case result of
 18: GetErrorMsg := ‘driver error during status operation’;
 19: GetErrorMsg := ‘driver error during read operation’;
 20: GetErrorMsg := ‘driver error during write operation’;
 27: GetErrorMsg := ‘driver I/O error caused abort of operation’;
 28: GetErrorMsg := ‘driver not open’;
 33: GetErrorMsg := ‘the file directory is full’;
 34: GetErrorMsg := ‘all allocation blocks on the volume are full’;
 35: GetErrorMsg := ‘the specified volume is not mounted’;
 36: GetErrorMsg := ‘there was an unspecified I/O error’;
 37: GetErrorMsg := ‘the file or volume name is bad’;
 39: GetErrorMsg := ‘logical EOF reached unexpectedly’;
 40: GetErrorMsg := ‘attempt made to position before start of file’;
 42: GetErrorMsg := ‘too many files are open’;
 43: GetErrorMsg := ‘the file could not be found’;
 44: GetErrorMsg := ‘the volume is locked by hardware setting’;
 45: GetErrorMsg := ‘the file is locked’;
 46: GetErrorMsg := ‘the volume is locked by a software flag’;
 47: GetErrorMsg := ‘the file is already in use’;
 48: GetErrorMsg := ‘a file with specified name exists’;
 49: GetErrorMsg := ‘the file is already open for read/write’;
 50: GetErrorMsg := ‘no volume specified and no default volume’;
 51: GetErrorMsg := ‘a non-existent path was specified’;
 52: GetErrorMsg := ‘the was an error finding current position in file’;
 53: GetErrorMsg := ‘the specified volume is not on-line’;
 54: GetErrorMsg := ‘attempt to open a locked file for writing’;
 55: GetErrorMsg := ‘attempt to mount an already mounted volume’;
 56: GetErrorMsg := ‘the specified drive number is not mounted’;
 57: GetErrorMsg := ‘the volume lacks a Macintosh format directory’;
 58: GetErrorMsg := ‘there was an external file system error’;
 59: GetErrorMsg := ‘there was a problem during renaming’;
 60: GetErrorMsg := ‘the master directory block is bad’;
 61: GetErrorMsg := ‘read/write permission does not allow writing’;
 108: GetErrorMsg := ‘there is insufficient application memory’;
 109: GetErrorMsg := ‘a nil master pointer has been encountered’;
 111: GetErrorMsg := ‘attempt to operate on a free block’;
 112: GetErrorMsg := ‘attempt to purge a locked block’;
 117: GetErrorMsg := ‘the block is locked’;
 120: GetErrorMsg := ‘the directory could not be found’;
 121: GetErrorMsg := ‘too many working directories are open’;
 122: GetErrorMsg := ‘a folder cannot be placed in its own subfolder’;
 123: GetErrorMsg := ‘attempt HFS operations on non-HFS volume’;
 127: GetErrorMsg := ‘there was an internal file system error’;
 128: GetErrorMsg := ‘printing aborted at user request’;
 end;
end;   { function GetErrorMsg }

procedure IOCheck(result:OSErr);
var
 ignore:integer;
 errorString:Str255;
begin
 if result <> NoErr then
 begin
 NumToString(result,errorString);
 ParamText(‘Macintosh OS Error #:’, errorString, ‘Macintosh OS Error 
Desc:’, GetErrorMsg(result));
 ignore := StopAlert(StopDlogID,NIL);
 end
end;   { procedure IOCheck }

procedure LaunchFailed(errNo:OSErr);
var
 ignore:integer;
 errorString:Str255;
begin
 NumToString(errNo,errorString);
 ParamText(‘Launch Error #:’, errorString,’’,’’);
 ignore := StopAlert(StopDlogID,NIL);
end;   { procedure LaunchFailed }

procedure NotFoundMsg(str1,str2:Str255);
var
 ignore: integer;
begin
 ParamText(‘Please help locate’, str1, str2, ‘using the following dialog...’);
 ignore := StopAlert(StopDlogID,NIL);
end;   { procedure LaunchFailed }

{ *********   Configuration routines  *********** }
function ReadDefaultData: integer;
{ Open the launch data file and read the data necessary for
locating launch application and associated document (if any). }
var
 theErr: OSErr;
 myAppFile: AppFile;
 myRefNum: integer;
 theResult: integer;
 appMsg: integer;
 appCount: integer;
begin
 theResult := 0;   { be optimistic }
 CountAppFiles(appMsg, appCount);
 if appCount < 1 then
 begin
 theResult := 1;
 AlertMsg(‘Dispatcher must be launched’,’from a data document’);
 { could create a new data document file instead }
 end
 else
 begin
 GetAppFiles(1, myAppFile);
 { open the file for reading }
 myPB.ioCompletion := nil;
 myPB.ioNamePtr := @myAppFile.fName;
 myPB.ioVRefNum := myAppFile.vRefNum;
 myPB.ioPermssn := fsRdWrPerm; { read/write permission }
 myPB.ioMisc := nil;  { use volume buffer }
 theErr := PBOpen(@myPB, false);
 if theErr <> noErr then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end
 else
 begin
 myRefNum := myPB.ioRefNum;
 { read the application launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @appLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBRead(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 appLData.vName := ‘Uninitialized’; 
 appLData.dirID := 0; 
 appLData.fName := ‘Uninitialized’; 
 appLData.fType := ‘APPL’; 
 appLData.useIt := 1;    { not used }
 end;

 { read the document launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @docLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBRead(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 docLData.vName := ‘Uninitialized’; 
 docLData.dirID := 0; 
 docLData.fName := ‘Uninitialized’; 
 docLData.fType := ‘TEXT’; 
 docLData.useIt := 1; 
 end;
 { close the data file }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 theErr:= PBClose(@myPB, false);
 end;   { if data file opened OK }
 end;   { if app files count is > 0 }
 ReadDefaultData := theResult;
end;   { procedure ReadDefaultData }

function WriteDefaultData: integer;
{ Open the launch data file and write the data necessary for
locating launch application and associated document (if any).
 This should take place any time the data is changed. }
var
 theErr: OSErr;
 myAppFile: AppFile;
 myRefNum: integer;
 theResult: integer;
begin
 theResult := 0;   { be optimistic }
 GetAppFiles(1, myAppFile);
 { open the file for reading/writing }
 myPB.ioCompletion := nil;
 myPB.ioNamePtr := @myAppFile.fName;
 myPB.ioVRefNum := myAppFile.vRefNum;
 myPB.ioPermssn := fsRdWrPerm; { read/write permission }
 myPB.ioMisc := nil;       { use volume buffer }
 theErr:= PBOpen(@myPB, false);
 if theErr <> noErr then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end
 else
 begin
 myRefNum := myPB.ioRefNum;
 { write the application launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @appLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBWrite(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end
 else
 begin
 { write the document launch data }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 myPB.ioBuffer := @docLData;
 myPB.ioReqCount := sizeof(LaunchData);
 myPB.ioPosMode := fsAtMark;
 myPB.ioPosOffset := 0;
 theErr:= PBWrite(@myPB, false);
 if ((theErr <> noErr) or (myPB.ioActCount < 1)) then
 begin
 IOCheck(theErr); 
 theResult := 1;
 end;   { if bad document write }
 end;   { if application write OK }
 { close the data file }
 myPB.ioCompletion := nil;
 myPB.ioRefNum := myRefNum;
 theErr:= PBClose(@myPB, false);
 end;   { if data file opened OK }
 WriteDefaultData := theResult;
end;   { procedure WriteDefaultData }

function FindApplFile: integer;
  { Given the info in the data document, try to find the target
application’s working directory reference number. If it can’t
 be found, then prompt the user to find it. }
var
 theErr: OSErr;
 theResult: integer;
 reply: SFReply;
 topLeft: Point;
 fileFilter: SFTypeList;
 appdone, appfound: integer;
 theName: Str255;
 theIndex: integer;
 theRefNum: integer;
begin
 theResult := 0;   { be optimistic }
 { see if the volume is mounted }
 theIndex := 1;
 appdone := 0; appfound := 0; theName:= ‘’;
 repeat
 myHPB.ioVolIndex := theIndex;
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @theName;
 theErr := PBHGetVInfo(@myHPB, false);
 if theErr <> noErr then
 appdone := 1;   { no more volumes to check }
 if theName = appLData.vName then
 begin
 appfound := 1; appdone := 1;
 theRefNum := myHPB.ioVRefNum;
 end;
 theIndex := theIndex + 1;
 until appdone > 0;

 if appfound = 0 then
 theResult := 1
 else
 begin
 { see if file can be found  }
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @appLData.fName;
 myHPB.ioVRefNum := theRefNum;  { from call above } 
 myHPB.ioFDirIndex := 0; { use file name, not index }
 myHPB.ioDirID := appLData.dirID;  { use stored Dir ID }
 theErr := PBHGetFInfo(@myHPB,false); 
 if (theErr <> noErr) then
 begin
 { IOCheck(theErr); }
 theResult := 1;
 end
 else
 begin
 { get the current application WD Ref Num }
 myWDPB.ioCompletion := nil;
 myWDPB.ioNamePtr := nil;   { directory name }
 myWDPB.ioVRefNum := theRefNum;
 myWDPB.ioWDProcID := longint(‘ERIK’);
 myWDPB.ioWDDirID := appLData.dirID;
 theErr := PBOpenWD(@myWDPB, false);
 appLData.vRefNum := myWDPB.ioVRefNum;
 IOCheck(theErr);
 end;   { getting the current WD Ref Num }
 end;   { if volume was found OK }

 { if application couldn’t be found using default information, make the 
user find it manually. }
 if theResult <> 0 then
 begin
 NotFoundMsg(‘the program file: ‘,appLData.fName);
 topLeft.h := 80; topLeft.v := 70;          
 fileFilter[0] := appLData.fType;  
 SFGetFile(topLeft,’’,NIL,1,fileFilter,NIL,reply);
 if reply.Good then
 begin
 theResult := 0;  { reset the error flag }
 appLData.vRefNum := reply.vRefNum;  { WD RefNum }
 appLData.fName := reply.fName;
 appLData.fType := reply.fType;
 { look up new volume name }
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @appLData.vName;
 myHPB.ioVRefNum := appLData.vRefNum;  
 myHPB.ioVolIndex := 0;  { use vRefNum, not name }
 theErr := PBHGetVInfo(@myHPB,false); 
 IOCheck(theErr);
 { look up new application directory ID }
 myInfoPB.ioCompletion := nil;
 myInfoPB.ioNamePtr:= @appLData.fName;
 myInfoPB.ioVRefNum := appLData.vRefNum;
 myInfoPB.ioFDirIndex := 0;
 myInfoPB.ioDirID := 0;
 theErr := PBGetCatInfo(@myInfoPB,false); 
 IOCheck(theErr);
 appLData.dirID := myInfoPB.ioFLParID;
 { update the default data }
 if WriteDefaultData <> 0 then
 theResult := 1;
 end   { if reply is good }
 else
 begin
 theResult := 1;
 end;   { user cancelled application lookup }
 end;   { if default application location not valid }
 FindApplFile := theResult;
end;   { procedure FindApplFile }

function FindDocFile: integer;
  { Given the info in the data document, try to find launch
    document’s working directory reference number. If it can’t
 be found, then prompt the user to find it. }
var
 theErr: OSErr;
 theResult: integer;
 reply: SFReply;
 topLeft: Point;
 fileFilter: SFTypeList;
 docdone, docfound: integer;
 theName: Str255;
 theIndex: integer;
 theRefNum: integer;
begin
 theResult := 0;   { be optimistic }

 { see if there is an associated document with application }
 if (docLData.useIt = 1) then   { launch with associated document }
 begin
 { see if the volume is mounted }
 theIndex := 1;
 docdone := 0; docfound := 0; theName:= ‘’;
 repeat
 myHPB.ioVolIndex := theIndex;
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @theName;
 theErr := PBHGetVInfo(@myHPB, false);
 if theErr <> noErr then
 docdone := 1;   { no more volumes to check }
 if theName = docLData.vName then
 begin
 docfound := 1; docdone := 1;
 theRefNum := myHPB.ioVRefNum;
 end;
 theIndex := theIndex + 1;
 until docdone > 0;

 if docfound = 0 then
 theResult := 1
 else
 begin
 { see if the file can be found in its directory }
 myHPB.ioCompletion := NIL;
 myHPB.ioNamePtr := @docLData.fName;
 myHPB.ioVRefNum := theRefNum;   { from call above } 
 myHPB.ioFDirIndex := 0; {use file name, not index}
 myHPB.ioDirID := docLData.dirID;{use stored Dir ID}
 theErr := PBHGetFInfo(@myHPB,false); 
 if (theErr <> noErr) then
 begin
 { IOCheck(theErr); }
 theResult := 1;
 end
 else
 begin
 { get the current document WD Ref Num }
 myWDPB.ioCompletion := nil;
 myWDPB.ioNamePtr := nil;   { directory name }
 myWDPB.ioVRefNum := theRefNum;
 myWDPB.ioWDProcID := longint(‘ERIK’);
 myWDPB.ioWDDirID := docLData.dirID;
 theErr := PBOpenWD(@myWDPB, false);
 docLData.vRefNum := myWDPB.ioVRefNum;
 IOCheck(theErr);
 end;   { getting the current WD Ref Num }
 end;   { if volume was found OK }

{ if document couldn’t be found using default information, make the user 
find it manually. }
 if theResult <> 0 then
 begin
 NotFoundMsg(‘the associated document: ‘,docLData.fName); 
 topLeft.h := 80; topLeft.v := 70;          
 fileFilter[0] := docLData.fType;  
 SFGetFile(topLeft,’’,NIL,-1,fileFilter,NIL,reply);
 if reply.Good then
 begin
 theResult := 0;  { reset the error flag }
 docLData.vRefNum := reply.vRefNum;
 docLData.fName := reply.fName;
 docLData.fType := reply.fType;
 { look up new volume name }
 myHPB.ioCompletion := nil;
 docLData.vName := ‘’;
 myHPB.ioNamePtr := @docLData.vName;
 myHPB.ioVRefNum := docLData.vRefNum;  
 myHPB.ioVolIndex := 0; { use vRefNum, not name }
 theErr := PBHGetVInfo(@myHPB,false); 
 { look up new document parent directory ID }
 myInfoPB.ioCompletion := nil;
 myInfoPB.ioNamePtr:= @docLData.fName;
 myInfoPB.ioVRefNum := docLData.vRefNum;
 myInfoPB.ioFDirIndex := 0;
 myInfoPB.ioDirID := 0;
 theErr := PBGetCatInfo(@myInfoPB,false); 
 IOCheck(theErr);
 docLData.dirID := myInfoPB.ioFLParID;
 { parent directory ID }

 { update the default data }
 if WriteDefaultData <> 0 then
 theResult := 1;
 end   { if reply is good }
 else
 begin
 theResult := 1;
 end;   { user cancelled document lookup }
 end;   { if default document location not valid }
 end;   { if associated document exists }
 FindDocFile := theResult;
end;   { procedure FindDocFile }

function EditDefaults: integer;
var
 itemHit: integer;
 itemType: integer;
 itemRect: Rect;
 dfltDlg: DialogPtr;
 itemHdl1, itemHdl2: Handle;   { edittext handles }
 itemHdl3: Handle;    { check box dialog handle }
 cBoxHdl1: ControlHandle; { check box control handle }
 theResult: integer;
begin
 theResult := 0;
 { get dialog & edittext handles }
 dfltDlg := GetNewDialog(DfltDlogID, nil, Pointer(-1));
 GetDItem(dfltDlg, 4, itemType, itemHdl1, itemRect);
 GetDItem(dfltDlg, 5, itemType, itemHdl2, itemRect);
 GetDItem(dfltDlg, 6, itemType, itemHdl3, itemRect);
 cBoxHdl1 := ControlHandle(itemHdl3);
 SetIText(itemHdl1, appLData.fName);     { app filename }
 SetIText(itemHdl2, docLData.fName);     { doc filename }
 SetCtlValue(cBoxHdl1, docLData.useIt);  { use/don’t use document }
 
 { put up dialog }
 itemHit := 0;
 while ((itemHit > 3) or (itemHit < 1))  do
 begin
 ModalDialog(nil, itemHit);
 if itemHit = 6 then   { toggle the check box }
 begin
 if (GetCtlValue(cBoxHdl1) = 0) then
 SetCtlValue(cBoxHdl1, 1)
 else
 SetCtlValue(cBoxHdl1, 0);
 end;
 end;   { while }
 { get dialog results }
 case itemHit of 
 1: begin { use names entered in dialog }
 { application name }
 GetIText(itemHdl1, appLData.fName);
 { document name }
 GetIText(itemHdl2, docLData.fName);
 end;
 2: begin  { cause a search for new files }
 appLData.fName := ‘XXXXXXXX’;
 docLData.fName := ‘XXXXXXXX’;
 end;
 3: theResult := 1; { cancel the launch }
 end;
 if (theResult = 0) then
 begin
 docLData.useIt := GetCtlValue(cBoxHdl1);
 theResult := WriteDefaultData;  { update the defaults }
 end;
 DisposDialog(dfltDlg);
 EditDefaults := theResult;
end;   { EditDefaults }

function TestCommandKey: integer;
{ if command is held down while starting, the user will
    be allowed to edit the default settings. The file data
 has already been read at this point. If the user cancels,
 the data defaults are not changed. }
var
 theResult: integer;
 eventReady: boolean;
 myEvent: EventRecord;
begin
 theResult := 0;
 eventReady := GetNextEvent(everyEvent, myEvent);
 if (BAND(myEvent.modifiers,cmdkey) > 0) then
 theResult := EditDefaults;
 TestCommandKey := theResult;  
end;   { TestCommandKey }

{ *********   Transfer and launch routines  *********** }

function ResetFinderInfo: integer;
{ This is an example of how the finder parms could be changed
    without allocating a new structure in the system heap. However, the 
changes could not exceed the size of the existing heap object.  This 
routine is not used. }
var
 fInfoHdl: Handle;
 hSize: integer;
 appParmRec: APRec;        { my Finder info }
 theResult: integer;
begin
 theResult := 0;   { be optimistic }
 { get AppParmHandle, lock it, and copy parms record }
 fInfoHdl := Handle(AppParmGVar);
 fInfoHdl := Handle(fInfoHdl^);
 HLock(fInfoHdl);
 hSize := GetHandleSize(fInfoHdl);
 BlockMove(Pointer(fInfoHdl^), Pointer(@appParmRec), hSize);
 if (appParmRec.count > 0) then
 begin
 if (length(appParmRec.files[1].fName) >= length(docLData.fName)) then
 begin  
 { now change the launch parameters }
 appParmRec.files[1].fName := docLData.fName;
 appParmRec.files[1].fType := docLData.fType;
 appParmRec.files[1].vRefNum := docLData.vRefNum;
 BlockMove(Pointer(@appParmRec), Pointer(fInfoHdl^), hSize);
 end
 end
 else 
 begin
 AlertMsg(‘Unable to reset’, ‘launch file parameters’);
 theResult := 1;
 end;
 HUnlock(fInfoHdl);
 ResetFinderInfo := theResult;
end;   { ResetFinderInfo }

function ZeroFinderInfo: integer;
{ If no associated documents are needed, then the number of
    files in app parm handle block must be set to zero, and
 the file types set to zero so the Finder can clean up when
 it gets control back. The number of files will always be 1
 when this routine is called. }
var
 fInfoHdl: Handle;   { Finder info handle }
 hSize: integer;
 appParmRec: APRec; { my Finder info }
 theResult: integer;
begin
 theResult:= 0;   { be optimistic }
 { get handle, lock it, and copy parms record }
 fInfoHdl := Handle(AppParmGVar);
 fInfoHdl := Handle(fInfoHdl^);
 HLock(fInfoHdl);
 hSize := GetHandleSize(fInfoHdl);
 BlockMove(Pointer(fInfoHdl^), Pointer(@appParmRec), hSize);
 if (appParmRec.count > 0) then
 begin
 { now change the launch parameters }
 appParmRec.count := 0;
 appParmRec.files[1].fType := OSType(longint(0));
 { write it back out }
 BlockMove(Pointer(@appParmRec), Pointer(fInfoHdl^), hSize);
 end;
 HUnlock(fInfoHdl);
 ZeroFinderInfo := theResult;
end;   { ZeroFinderInfo }

function ReplaceFinderInfo: integer;
  { To prepare for the launch, the finder launch information must be 
reset to hold the name and type of the database startup file. This routine 
disposes the old AppParmHandle heap object and allocates a new one on 
the system heap. Returns 0 if successful, 1 if an error occurred. }
var
 fInfoHdl: Handle;      { handle to Finder info }
 sysZone: THz;          { system heap zone pointer }
 appZone: THz;          { application heap zone pointer }
 hSize: integer;
 appParmRec: APRec;     { my Finder info }
 theErr: OSErr;
 theResult: integer;
 saveAppParm: ^longint; { pointer used to access $AEC }
begin
 theResult := 0;   { be optimistic }
 { get handle, lock it, and copy parms record }
 fInfoHdl := Handle(appParmGVar);
 fInfoHdl := Handle(fInfoHdl^);
 HLock(fInfoHdl);
 hSize := GetHandleSize(fInfoHdl);
 BlockMove(Pointer(fInfoHdl^), Pointer(@appParmRec), hSize);
 
 { dispose the old handle in the system zone}
 HUnlock(fInfoHdl);
 appZone := GetZone;
 sysZone := SystemZone;
 SetZone(sysZone);
 DisposHandle(fInfoHdl);
 
 { allocate a new handle in the system zone }
 hSize := 13 + length(docLData.fName); { enough for one doc }
 fInfoHdl := NewHandle(hSize);
 theErr := MemError;
 SetZone(appZone);
 if theErr <> noErr then
 begin
 IOCheck(theErr);
 theResult := 1;
 end
 else
 begin
 HLock(fInfoHdl);
 { put the new handle back at $AEC }
 saveAppParm := Pointer(AppParmGVar);
 saveAppParm^ := longint(fInfoHdl);
 
 { now change the launch parameters }
 appParmRec.message:= 0;    { open it }
 appParmRec.count:= 1;      { number of documents }
 appParmRec.files[1].vRefNum := docLData.vRefNum;
 appParmRec.files[1].fType := docLData.fType;
 appParmRec.files[1].version := false;   
 appParmRec.files[1].unused := false;
 appParmRec.files[1].fName := docLData.fName;
 BlockMove(Pointer(@appParmRec), Pointer(fInfoHdl^), hSize);
 HUnlock(fInfoHdl);
 end;   { if sys heap handle allocated OK }
 ReplaceFinderInfo := theResult;
end;   { ReplaceFinderInfo }

function LaunchIt(pLnch: pLaunchStruct): OSErr; 
{ from tech note #52 & 126 }
 INLINE $205F, $A9F2, $3E80;

procedure DoLaunch;
{ Launch the application, given the information in the
  launch data records for the application & document. }
var
 fileInfo: FInfo;
 ignore: integer;
 myLaunch: LaunchStruct;
 theErr: OSErr;
begin
 { set default working directory to application’s folder }
 myWDPB.ioCompletion := NIL;
 myWDPB.ioNamePtr := NIL;
 myWDPB.ioVRefNum := appLData.vRefNum;
 theErr := PBSetVol(@myWDPB,false);  

 { get finder flags for launch record}
 myInfoPB.ioNamePtr:= @appLData.fName;
 myInfoPB.ioVRefNum := appLData.vRefNum;
 myInfoPB.ioFDirIndex := 0; { use name instead of index }
 myInfoPB.ioDirID := 0;
 theErr := PBGetCatInfo(@myInfoPB,false); 

 { now launch the application }
 myLaunch.pfName := @appLData.fName;    
 myLaunch.param := 0; 
 myLaunch.LC := ‘LC’;
 myLaunch.extBlockLen := 6;
 myLaunch.LaunchFlags:= $00000000; {$C0000000 for sublaunch}
 myLaunch.fFlags := myInfoPB.ioFlFndrInfo.fdFlags;  { from GetCatInfo 
}

 theErr := LaunchIt(@myLaunch);
 if theErr < 0 then   
 LaunchFailed(theErr);
end;   { procedure DoLaunch }

function PrepareForLaunch: integer;
{ set up the AppParmsHandle data }
var
 theResult: integer;
begin
 theResult := 0;
 if (docLData.useIt = 1) then  { application & document }
 theResult := ReplaceFinderInfo
 else       { application only }
 theResult := ZeroFinderInfo;
 PrepareForLaunch := theResult;
end;   { function PrepareForLaunch }

procedure Initialize;
{ initialize managers & globals }
begin
 { initialize the managers }
 InitGraf(@thePort);               
 InitFonts;                        
 InitWindows;                      
 InitMenus;                        
 TEInit;                           
 InitDialogs(NIL);                 
 FlushEvents(everyEvent,0);        
end; { Initialize }

{ main program }
begin 
 Initialize;
 lcnt := 1; done := 0;
 while done = 0 do
 begin
 case lcnt of
 1: done := ReadDefaultData;
 2: done := TestCommandKey;
 3: done := FindApplFile;
 4: done := FindDocFile;
 5: done := PrepareForLaunch;
 6: DoLaunch;    { returns only if sublaunched }
 end;
 lcnt := lcnt + 1;
 if lcnt > 6 then done := 1;
 end;   { while }
end. { dispatcher }
Listing:  dispatcher.r

/* dispatcher.r
 Copyright Verity Software Systems 1989
     All rights reserved.
 Resource definitions for dispatcher application
 Rez format
*/
#include “Types.r”
resource ‘BNDL’ (128) {
 ‘LCHA’,
 0,
 { /* array TypeArray: 2 elements */
 /* [1] */
 ‘ICN#’,
 { /* array IDArray: 2 elements */
 /* [1] */
 0, 128,
 /* [2] */
 1, 129
 },
 /* [2] */
 ‘FREF’,
 { /* array IDArray: 2 elements */
 /* [1] */
 0, 130,
 /* [2] */
 1, 131
 }
 }
};
resource ‘ICN#’ (128, “Dispatcher Application Icon”) {
 { /* array: 2 elements */
 /* [1] */
 $”FFFF FFFE 8000 0003 8120 0003 8120 0003"
 $”8220 0003 8440 0003 B880 07C3 8118 1823"
 $”861C 2023 B80F FF23 8006 0023 8005 0043"
 $”8004 8043 8004 4083 800C 2083 800C 0103"
 $”8014 0203 8014 0603 8024 0A03 8024 1203"
 $”8020 7203 8021 D203 801E 1203 8000 1203"
 $”8000 1203 8000 1203 8000 3303 8000 2103"
 $”8000 2103 8000 2103 FFFF FFFF 7FFF FFFF”,
 /* [2] */
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 }
};
resource ‘ICN#’ (129, “Dispatcher Document Icon”) {
 { /* array: 2 elements */
 /* [1] */
 $”FFFF FFFE 8000 0003 8000 1E03 8000 0183"
 $”8000 1C43 8000 0243 83C0 0123 8430 18A3"
 $”8408 38A3 84FF F0A3 8400 6003 8200 A003"
 $”8201 2003 8102 2003 8104 3003 8080 3003"
 $”8040 2803 8060 2803 8050 2403 8048 2403"
 $”804E 0403 804B 8403 8048 7803 8048 0003"
 $”8048 0003 8048 0003 80CC 0003 8084 0003"
 $”8084 0003 8084 0003 FFFF FFFF 7FFF FFFF”,
 /* [2] */
 $”FFFF FFFE FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF”
 $”FFFF FFFF FFFF FFFF FFFF FFFF 7FFF FFFF”
 }
};
resource ‘FREF’ (130) {
 ‘APPL’,
 0,
 “Dispatcher Application”
};
resource ‘FREF’ (131) {
 ‘LCHD’,
 1,
 “Dispatcher Document”
};
data ‘LCHA’ (0) {
 $”4469 7370 6174 6368 6572 2041 7070 6C69"        
 /* Dispatcher Application */
 $”6361 7469 6F6E 202D 2043 6F70 7972 6967"        
 /* Copyright 1989 Verity Software Systems. */
 $”6874 2031 3938 3920 5665 7269 7479 2053"        
 $”6F66 7477 6172 6520 5379 7374 656D 732E”        
 $”2052 6F67 6572 2041 2E20 486F 7274 6F6E”        
 /*  Roger A. Horton */
 $”202D 2041 6C6C 2072 6967 6874 7320 5265"        
 /*  - All rights Reserved */
 $”7365 7276 6564"                                 
};
resource ‘DLOG’ (200, “Configuration Settings”) {
 {38, 116, 312, 390},
 altDBoxProc,
 visible,
 noGoAway,
 0x0,
 200,
 “Configuration Settings”
};
resource ‘DITL’ (200, “Configuration Settings”) {
 { /* array DITLarray: 13 elements */
 /* [1] */
 {184, 24, 204, 84},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {184, 109, 204, 169},
 Button {
 enabled,
 “Search”
 },
 /* [3] */
 {184, 192, 204, 252},
 Button {
 enabled,
 “Cancel”
 },
 /* [4] */
 {74, 31, 92, 239},
 EditText {
 enabled,
 “4th Dimension® v1.0.6”
 },
 /* [5] */
 {128, 30, 146, 238},
 EditText {
 enabled,
 “FPR”
 },
 /* [6] */
 {151, 56, 171, 212},
 CheckBox {
 enabled,
 “Open App Document”
 },
 /* [7] */
 {4, 81, 24, 182},
 StaticText {
 enabled,
 “• Dispatcher •”
 },
 /* [8] */
 {25, 75, 45, 190},
 StaticText {
 enabled,
 “Current Settings”
 },
 /* [9] */
 {51, 31, 70, 239},
 StaticText {
 enabled,
 “Application Name:”
 },
 /* [10] */
 {106, 32, 125, 239},
 StaticText {
 enabled,
 “Document Name:”
 },
 /* [11] */
 {220, 30, 238, 249},
 StaticText {
 enabled,
 “© Verity Software Systems 1989”
 },
 /* [12] */
 {239, 15, 256, 128},
 StaticText {
 enabled,
 “by Roger Horton”
 },
 /* [13] */
 {239, 132, 256, 263},
 StaticText {
 enabled,
 “All Rights Reserved”
 }
 }
};
resource ‘ALRT’ (201) {
 {80, 100, 210, 400},
 201,
 { /* array: 4 elements */
 /* [1] */
 OK, visible, sound1,
 /* [2] */
 OK, visible, sound1,
 /* [3] */
 OK, visible, sound1,
 /* [4] */
 OK, visible, sound1
 }
};
resource ‘DITL’ (201) {
 { /* array DITLarray: 2 elements */
 /* [1] */
 {100, 125, 120, 175},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {10, 70, 90, 290},
 StaticText {
 disabled,
 “^0 \n^1 \n^2 \n^3”
 }
 }
};
resource ‘SIZE’ (-1) {
 dontSaveScreen,
 ignoreSuspendResumeEvents,
 dontDoOwnActivate,
 128000,
 128000
};

 
AAPL
$101.91
Apple Inc.
+0.33
MSFT
$46.60
Microsoft Corpora
+0.08
GOOG
$587.50
Google Inc.
+2.73

MacTech Search:
Community Search:

Software Updates via MacUpdate

Xcode 6.0.1 - Integrated development env...
Apple Xcode is Apple Computer's integrated development environment (IDE) for OS X. The full Xcode package is free to ADC members and includes all the tools you need to create, debug, and optimize... Read more
Apple Safari 7.1 - Apple's Web brow...
Apple Safari in OS X Mavericks brings you all-new ways to find and enjoy the best of the web. It works with iCloud to give you a seamless browsing experience across all your devices. It looks out for... Read more
Delivery Status 6.1.2 - Check delivery s...
Delivery Status displays delivery status of packages for a variety of shipment services. Can't wait for your packages to arrive? Don't waste your time checking the site constantly, just open this all... Read more
Mavericks Cache Cleaner 8.0.9 - Clear ca...
Mavericks Cache Cleaner is an award-winning general purpose tool for OS X. MCC makes system maintenance simple with an easy point-and-click interface to many OS X functions. Novice and expert users... Read more
OneNote 15.2.2 - Free digital notebook f...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
Apple Configurator 1.6 - Configure and d...
Apple Configurator makes it easy for anyone to mass configure and deploy iPhone, iPad, and iPod touch in a school, business, or institution. Three simple workflows let you prepare new iOS devices... Read more
SpamSieve 2.9.16 - Robust spam filter fo...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
OS X Server 3.2.1 - For OS X 10.9.5 Mave...
OS X Server is the next generation of Apple's award winning server software. Designed for OS X and iOS devices, OS X Server makes it easy to share files, schedule meetings, synchronize contacts, host... Read more
Apple Security Update 2014-004 - For OS...
Apple Security Update is recommended for all users and improves the security of Mac OS X. For information on the security content of this update, please visit this website: http://support.apple.com/... Read more
OS X Mavericks 10.9.5 - The latest versi...
Apple OS X Mavericks is the latest release of the world's most advanced desktop operating system. Now free! With more than 200 new features, OS X Mavericks brings Maps and iBooks to the Mac,... Read more

Latest Forum Discussions

See All

It Came From Canada: Overkill 3
Overkill 3 is like every trope of big modern gaming rolled into one. It’s a sequel to an action-packed military shooter. It’s flashy and scripted and flaunts its sophisticated graphics. And it’s a mobile game with a heavy emphasis on in-app... | Read more »
New Modes and Leader Boards in Update fo...
New Modes and Leader Boards in Update for Rules! Posted by Jessica Fisher on September 18th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
TwistedRun Review
TwistedRun Review By Rob Thomas on September 18th, 2014 Our Rating: :: DON'T TWIST YOUR ANKLE!Universal App - Designed for iPhone and iPad TwistedRun is kind of like running up a giant curly fry into the sky. Or maybe that was just... | Read more »
Scope Review
Scope Review By Jennifer Allen on September 18th, 2014 Our Rating: :: LOCATION AWAREiPhone App - Designed for the iPhone, compatible with the iPad Want to easily find photos from around the world based on their location? Scope is a... | Read more »
HipstaFox Review
HipstaFox Review By Jordan Minor on September 18th, 2014 Our Rating: :: FANTASTIC MR. FOXUniversal App - Designed for iPhone and iPad HipstaFox is a great single that makes players long for the whole album.   | Read more »
Ninja Raft (Games)
Ninja Raft 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: ** Special Launch Price ** "Ninja Raft is definitely the game to play if you’re into Tower Defense games and want to play something... | Read more »
Paste+ | Clipboard Action Widget (Produ...
Paste+ | Clipboard Action Widget 1.0.0 Device: iOS iPhone Category: Productivity Price: $3.99, Version: 1.0.0 (iTunes) Description: Powerful clipboard widget for iOS 8. Reimagine what you can do with your clipboard! Paste+ is a... | Read more »
Agenda+ | Calendar & Reminder Widget...
Agenda+ | Calendar & Reminder Widget 1.0.0 Device: iOS iPhone Category: Productivity Price: $1.99, Version: 1.0.0 (iTunes) Description: Best Calendar and Reminder Widget Ever for iOS 8 | Read more »
Leg·end·ar·y (Games)
Leg·end·ar·y 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Simple and yet complex. Leg•end•dar•y is a grid-based puzzle game based on numbers. Discover the adventure of Leg•end•dar•y and... | Read more »
KuaiBoard (formerly QuickBoard) (Utilit...
KuaiBoard (formerly QuickBoard) 1.0 Device: iOS Universal Category: Utilities Price: $1.99, Version: 1.0 (iTunes) Description: KuaiBoard is currently 50% off for launch! Billing info. Signatures. Locations. KuaiBoard allows you to... | Read more »

Price Scanner via MacPrices.net

Save up to $300 on the price of a new Mac wit...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
13-inch 2.8GHz Retina MacBook Pro available f...
B&H Photo has the new 2014 13″ 2.8GHz Retina MacBook Pro on sale for $1699.99 including free shipping plus NY sales tax only. They’ll also include free copies of Parallels Desktop and LoJack for... Read more
16GB iPad Air on sale for $449, save $50
Walmart has the 16GB iPad Air WiFi on sale for $449 on their online store for a limited time. Choose free home shipping or free local store pickup. Their price represents a $50 savings over standard... Read more
13-inch 256GB MacBook Air on sale for $1099,...
B&H Photo has the 2014 13″ 1.4GHz 256GB MacBook Air on sale for $1099.99. Shipping is free, and B&H charges NY sales tax only. Their price is $100 off MSRP. Read more
Toshiba Introduces TransMemory ID High-Speed...
Toshiba’s Digital Products Division (DPD), a division of Toshiba America Information Systems, Inc., today introduced the TransMemory ID USB 3.0 Flash Drive, a simpler storage solution for people who... Read more
New iPads and OS X Yosemite Release Coming Oc...
The DailyDot’s Micah Singleton reports that Apple is planning to hold its next product announcement event on Oct. 21, at which it will unveil the iPad Air 2 and iPad mini 3 and release a final build... Read more
Logitech Bluetooth Multi-Device Cross-Platfor...
Logitech has an enviable track record of making some of the best computer keyboards and mice. At least in my estimation, the best freestanding keyboards I’ve ever used have been Logitech units,... Read more
Roundup of Apple refurbished iPad Airs and iP...
Apple is offering Certified Refurbished iPad Airs for up to $140 off MSRP. Apple’s one-year warranty is included with each model, and shipping is free. Stock tends to come and go with some of these... Read more
Sprint offers 16GB iPad mini for $199.99 with...
Sprint is offering 1st generation 16GB iPad minis for $199.99 with a 2-year service agreement. Standard MSRP for this iPad is $429. Their price is the lowest available for this model. Read more
2.5GHz Mac mini remains on sale for $549, sav...
B&H Photo has the 2.5GHz Mac mini on sale for $549.99 including free shipping. That’s $50 off MSRP, and B&H will also include a free copy of Parallels Desktop software. NY sales tax only. Read more

Jobs Board

Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.