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
};

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.