TweetFollow Us on Twitter

Inside Mac XCMD
Volume Number:6
Issue Number:3
Column Tag:XCMD Corner

Related Info: Control Manager Event Manager Resource Manager

Inside Macintosh

By Donald Koscheka, Ernst & Young, MacTutor Contributing Editor

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

“Inside Macintosh” poses a paradox for the Hypercard developer. Hypertalk greatly reduces the stresses and strains of developing a Macintosh application but a quick flip through the pages of Inside Macintosh reveals a wealth of features and capabilities that aren’t available to the script writer. This column attempts to remedy this situation but has focussed in on I/O routines, in part because I/O is where Hypercard is weakest.

The paradox of XCMD programming is that some of the easiest problems are the most difficult to solve. We’ll take as one example, adding a custom control to Hypercard. Before I go into too much detail, I need to warn you. Much of what this column discusses is stuff that ought to be in Hypercard in the first place. I have to believe that some future release will address these issues. Even so, bear with me; there’s still a lot to learn and sometimes going back to the basics can be quite illuminating.

Here is the problem: You want to add a custom control to a Hypercard window, perhaps a horizontal scroll bar. Sounds simple enough so you whip up a little xcmd that calls the toolbox utility “NewControl”. To your bemusement, the control draws correctly but it doesn’t work! Worse yet, the button erases on updates. You dump Hypercard’s window record to discover something even more horrifying - your control is the only object in the control list; Hypercard doesn’t trifle with the control manager.

It turns out that adding a control to the Hypercard window isn’t enough. You need to add a mechanism to track control events by intercepting the event trap (currently GetNextEvent). Although the accompanying code focuses on the control manager, you can modify this technique to manage windows, lists and key events (some of these will be featured in future columns).

By patching GetNextEvent, you can peek at events before Hypercard sees them. You decide whether to pass an event on to Hypercard or to “consume the event”.

Listing 1, Control XFCN.c, creates a control and installs the event patch (if it isn’t already installed). Listing 2 contains the patch which is stored as resource type “EXEC”. Control XFCN installs this patch into the system heap so that we can begin handling events that are germane to the control manager.

In keeping with the tradition of Hypercard, parameters are passed to the XCMD using nomenclature that is as close to human language as possible. For instance, the control type is passed as “Button”, “Check Box”, etc. You can use any names that you like as long as you remember to add them to the STR# resource that represents the “symbol table”.

Listing 3 contains the parsing routines for this symbol table. Entries in this table have two fields, the first is the pattern that needs to be matched, the second is the value that this pattern codes for. If the pattern is “BUTTON”, the corresponding value is 0 since that is the procID of a control button. The value is separated from the pattern by a comma, thus “,” cannot appear in the pattern. To simplify editing, the symbol table is stored as a string, the parser converts the value from a string to a number for you.

Control XFCN takes the following parameters: the control type, the control rect and the control’s title. The header states that the xcmd also accepts the control’s setting, min and max. The “paramtoNum” routine is a simple interface to “StrToNum”. If you want to use my code, I’ve provided two additional files, “HyperUtils.c” and “HyperUtils.h” on this month’s disk.

To code for the standard mac control types, this STR# resource should look like:

BUTTON, 0 \n
CHECKBOX, 1 \n
RADIOBUTTON, 2 \n
SCROLLBAR, 16 \n

The strings are stored in capital letters so that the parse can be case insensitive, we shift any lowercase characters in the pattern. This simple parsing scheme is suitable only for small collections of patterns, it won’t scale very well to very large symbol tables or to more generalized input streams.

Here is an example of how you might add a horizontal scroll bar to Hypercard positioning the thumb at the midway point:

Put NewControl( “SCROLLBAR”, “100, 40, 116, 300”,¬

“MY SCROLL”, 128, 0, 256 ) into theControl

The parse done, we create the control assigning it to the front window. Since our little system requires that the user click on a button in Hypercard to activate the XCMD, we are reasonably assured that Hypercard is the front window. This scheme is not foolproof so you might want to explore ways of making this scheme more robust (I’ll drop a hint later).

I use the concept of appending a resource to the system file for two reasons: it provides a handy place to store information about the patch and such a resource is “globally available”; code in the host stack can communicate with the event patch via this resource. There is a chink in this approach - the system resource gets written out to the file if the stack unexpectedly quits while under multifinder (even if it’s marked as “unchanged”). When the stack runs next, it finds the resource already exists and crashes if the information in that previous invocation isn’t valid. You can make everything whole again by deleting this resource. Better yet, check to see if the resource is in the system file but not loaded into memory, if so, delete the resource, it doesn’t belong there. If the patch installs ok, we save off the old address and the new address in our control information resource .

The patch is now live. The next activation of GetNextEvent will be dispatched to our handler.

Disposing the control requires that we somehow get access to it. Control XFCN returns the control handle as its return value, although it’s better form to store this type of information in the resource that represents our global pool and have Hypercard refer to controls by name (e.g. RemoveControl “OK” ).

Our event handler implements a fairly standard event loop in a fairly non-standard way (see listing 2). The handler starts by setting up A4 as a pointer to our globals. The A4 globals are referenced off the address of the code resource that they were built in, unlike A5 globals which are installed above the application heap by the application launcher. We use a little trick here: register A0 points to the entry point of the EXEC. This implies that the code resource was invoked with the instruction, Jsr (A0). This is nice to know, and I hope Apple doesn’t change this in future manifestations of the toolbox.

A4 is not a free register; we must save off the old contents before moving A0 into A4. We can’t push A4 onto the stack since GetNextEvent is a stack-based routine and we want to keep the stack as clean as possible as we’ll be calling GetNextEvent in just a few brief cycles of the CPU’s clock. So we move the old A4 into a safe place (static oldA4) in our global pool. The static storage class assigns variables to the A4 pool as opposed to the stack. Some programmers prefer to save the oldA4 in some lowmem global like Scratch8 or Scratch20. I’ve never acquired a taste for fiddling with the lowmem globals in this way, I prefer to make room in my own memory pool for my data.

GetNextEvent, like most toolbox routines, unbiases the stack. We need to save the pointer to the event record so that we can look at it AFTER the call to getnextevent. For completeness, we also save off the event mask although EventPatch itself doesn’t use this information.

We saved the address of the actual GetNextEvent in the oldtrap field of resource. We gain access to that resource and then put old trap into A0 for our JSR(A0) call. Now the real (or more accurately, the previous) trap handler will be invoked, cleaning the stack up to the return value which will be pointed to by A7 on return. If the function result of getNextEvent is true, we have an event, otherwise do nothing.

I’ve implemented an update and mousedown method to redraw the controls and to invoke TrackControl . This event loop assumes that the Hypercard window is the front window which may or may not be valid ( here’s the hint: store the windowPtr of your window in your globals. Once in Event patch, compare the front window to our window, if the same,handle the event, if not ignore it).

For the sake of space, I’ve omitted the code for such calls as AddToSystemResource. Such routines are straightforward: Before adding the resource, call useresfile to switch to the system fork. You should also save the oldresource file id and restore it in such calls. If you need this code, it’s on this month’s disk.

When done with a control, you should delete it, when done with all controls, you should remove the patch to getnextevent. Listing 5, RemoveControl, does this. Once we’ve unloaded all controls (controllist goes empty), we remove our patch and then deallocate all memory. TrashHandle unlocks and sets a handle purgeable before disposing it.

That’s the starting point. Play around with the event loop to meet your needs. This example doesn’t provide full support for custom controls in Hypercard. It does provide a mechanism for unlocking the toolbox for anyone who needs access to the event loop.

The following is an apology:

My November column inadvertently identified a MacTutor reader as Joe Palooka. This was not intended in any way as a negative reaction to the letter that this reader sent to the editor. The misidentification was purely mechanical. I write this article while on my daily train ride and I did not have this letter with me when I wrote the article. I used a comic character’s name to remind myself to look up the letter in a back copy of MacTutor (which I’ve misplaced so I still can’t identify the letter’s author). I was up against deadline and the article went out without my properly editing it. I apologize to all readers for this inexcusable error, which was solely my mistake.

I will answer all letters that are sent to me via US mail or via America on line (AFC Donald). My phone manners are terrible. If you call, don’t be offended if I’m somewhat less polite than an AT&T operator. Send your letters to the editor. For faster response, copy me at the following address. Please let the magazine know that you’ve sent me a copy so that they don’t forward a second copy (I hate answering the same question twice).

Donald Koscheka

c/o Ernst & Young

787 Seventh Avenue, 20th Floor

New York, NY 10019

Listing 1:  Control XFCN.c

/************************************/
/* File: Control XFCN.c   */
/* */
/* Parameters:   */
/* params[0] == control type*/
/* params[1] == control Rect*/
/* params[2] == control title */
/* params[3] == control setting    */
/* params[4] == control min */
/* params[5] == control max */
/* */
/* --------------------------------*/
/* © 1989 Donald Koscheka */
/* All Rights Reserved    */
/************************************/

#define UsingHypercard

#include<MacTypes.h>
#include<OSUtil.h>
#include<ControlMgr.h>
#include  <HyperXCmd.h>
#include<HyperUtils.h>
#include“Control XFCN.h”

#define ScrollSize 15
#define CONTROL_TYPES6000
#include“miniParser.c”

pascal void main( paramPtr )
 XCmdBlockPtr  paramPtr;
{
 short  procID   = pushButProc;
 WindowPtrtheWindow= FrontWindow();
 Rect   boundsRect;
 short  min = 0;
 short  max = 1;
 short  value    = 0;
 char   title[256];
 ControlHandle theControl = NIL;
 
 if( paramPtr->params[0] )
 procID = (short)matchToken( paramPtr->params[0], CONTROL_TYPES);
 
 parseRect( paramPtr->params[1], &boundsRect );
 
 paramtoPString( paramPtr, 2, title );
 
 value = (short)paramtoNum( paramPtr, 3 );
 min = (short)paramtoNum( paramPtr, 4 );
 max = (short)paramtoNum( paramPtr, 5 );
 if( !max )
 max = 1;
 
 theControl = NewControl( theWindow, &boundsRect,
 title, TRUE, value, min, max, procID, NIL);

 if( validHandle( theControl ) ){  /* install event patch */
 wdGlobalsHand windH;
 Handle event_handler;    
 Handle sysPlace;
 void   *oldTrap;
 long   hSiz;

 SetCtlValue( theControl, value );
 
 if( !(windH = (wdGlobalsHand)GetSystemResource(CONTROL_INFO, CONTROL_PATCH))){
 windH = (wdGlobalsHand)NewSysHandle((long)sizeof( wdGlobals));
 if( validHandle( (Handle)windH ) ){
 
 /*** 11.26.89   ***/
 AddSystemResource((Handle)windH, CONTROL_INFO, CONTROL_PATCH,”\p control 
record” );
 event_handler = GetResource( ‘EXEC’, CONTROL_PATCH );
 
 if( validHandle( event_handler  ) ){
 /* copy our handler into system heap*/
 hSiz = GetHandleSize( event_handler );
 
 sysPlace = NewHandle( hSiz );
 /* would like this in system heap */
 
 if( validHandle( sysPlace ) ){
 MoveHHi( sysPlace );
 HLock( sysPlace );/* our code can’t move*/
 HNoPurge( sysPlace );  /*must stay put in heap*/
 
 BlockMove( *event_handler, *sysPlace, hSiz );
 
 oldTrap = (void *)NGetTrapAddress( EVENT_TRAP, ToolTrap);
 
 NSetTrapAddress(*sysPlace, EVENT_TRAP, ToolTrap);
 
 /* need to save off oldTrap where */
 /* everyone can find it  */
 (*windH)->evtProc = sysPlace;
 (*windH)->oldTrap = oldTrap;
 (*windH)->userData= NIL; 
 /* initialize this for the user */
 }
 }/*** if validHandle( event_handler ) ***/
 }
 }
 }
 
 paramPtr->returnValue = NumToParam( paramPtr, (long)theControl );
}
Listing 2:  EventPatch.c

/************************************/
/* File: EventPatch.c*/
/* */
/* Patch to get next event*/
/* to allow us to intercept events */
/* from Hypercard. */
/* */
/* check to see if we get the event*/
/* if not, pass it back on, */
/* otherwise, handle the event and */
/* return a null event to Hypercard*/
/* */
/* The real getnextevent will unbias*/
/* the stack parameters so we need */
/* to save them off before we call */
/* the real trap */
/* */
/* The Boolean result of GetNextEvt*/
/* will be at 0(A7) immediately  */
/* after calling the real trap*/
/* */
/* If we took the event, then all we*/
/* need do is set the result of  */
/* GetNextEvent to false. */
/* --------------------------------*/
/* ©1989 Donald Koscheka  */
/* All Rights Reserved    */
/************************************/
#include<SetUpA4.h>
#include<MacTypes.h>
#include<OSUtil.h>
#include<ControlMgr.h>
#include  <HyperXCmd.h>
#include<HyperUtils.h>
#include“Control XFCN.h”

/* NOTE: you can have no stack frames in the */
/* intercept since this is a stack based trap */
/* That is to say, the stack is already biased */
/* correctly for trap -- any local variables */
/* must be declared as statics below, no locals */
/* are allowed in main. */

static  void*returnAddress;
static  EventRecord*theEvent;
static  short    evtMask;
static  short    haveEvt;
static  WindowPtrwhichWindow;
static  short    windoPart;
static  void*oldA4;
static  ProcPtr  oldTrap;
static  Point    thePoint;
static  short    controlPart;
static  ControlHandlewhichControl;
static  wdGlobalsHandwindH;

void main(){
 asm{
 Move.l A4, D0   ; we need to save the old A4
 Move.l A0, A4   ; pointer to our globals.
 Move.l D0, oldA4; note that oldA4 is referenced off A4
 Move.l (A7)+, returnAddress; save real return address...
 Move.l (A7), theEvent  ; save the event record pointer
 Move.w 4(A7), evtMask  ; and the event mask
 }

 windH = (wdGlobalsHand)GetSystemResource( CONTROL_INFO, CONTROL_PATCH 
);
 oldTrap = (*windH)->oldTrap;
 
 asm{   ; left the stack intact for the call
 Move.l oldTrap, A0
 Jsr    (A0)
 Move.b (A7), haveEvt; did we get an event?
 }
 
 whichWindow = FrontWindow();
 
 if(windH && haveEvt && (((WindowPeek)whichWindow)->windowKind != dialogKind) 
 ){
 switch( theEvent->what ){
 case mouseDown:
 
 windoPart = FindWindow(theEvent->where, &whichWindow);
 
 if( windoPart == inContent ){
 thePoint = theEvent->where;
 GlobalToLocal( &thePoint );
 
 if( controlPart = FindControl( thePoint, whichWindow, &whichControl 
)){
 
 /* need to temp unpatch GetNextEvent */
 /* for mouse tracking stuff because some */
 /* tracking calls also call getNextEvent! */
 
 NSetTrapAddress( (*windH)->oldTrap, EVENT_TRAP, ToolTrap);
 
 controlPart = TrackControl(whichControl, thePoint, NIL);
 
 /*** now re-install the patch***/
 NSetTrapAddress( *((*windH)->evtProc), EVENT_TRAP, ToolTrap);
 }

 } /*** if mouse down in our window ***/
 break;
 
 case updateEvt: 
 whichWindow = (WindowPtr)theEvent->message;
 DrawControls( whichWindow );
 break;
 
 default: 
 break;
 } /* switch theEvent->what */
 } /* if haveEvt */
 
 asm{ 
 Move.b haveEvt, (A7)
 Move.l returnAddress, A0
 Move.l oldA4, A4
 Jmp    (A0)
 }
}
Listing 3: MiniParser.c

/********************************/
/* File: ParseCommands.c  */
/* */
/* Parse commands coming in from*/
/* Hypercard.    */
/* */
/* Once the object is parsed, it*/
/* is added to the draw list for*/
/* the window.   */
/* */
/* Eventually need to match */
/* multiple tokens so that we */
/* can have parameters such as:  */
/* BOLD&ITALIC&OUTLINE    */
/* ----------------------------  */
/* © 1989 Donald Koscheka */
/* All Rights Reserved    */
/********************************/
#define CR0x0D
#define NEWLINE  0x0D
#define LF0x0A
#define TAB 0x09
#define ETX 0x03 /* the enter key */

long matchToken( buf, tabl )
 Handle buf;
 short  tabl;
/*******************************
* given an input buffer and the resource 
* id of the parse table, return 
* the token that represents the input
* string.
* A token of 0 is returned if no
* match is found.  This way, you
* can use the first item in the list
* as the default item!
*
* The symbol table should have the 
* format:
*<string>, <token>
* where string mathces to the input
* string and token is that value for 
* a given match.
*******************************/
{
 char *bp;/* pointer to input strings */
 Handle strH;/* handle to parse strings resource */
 long token = 0; /* return default if no match */
 short  indx= 0;
 short  theID;
 ResTypetheType; 
 short  done = 0;
 long len;
 char theNum[31];
 char *np;
 char theName[256];
 char theString[256];
 
 bp = *buf;
 
 while( *bp ){
 toUpper( *bp );
 bp++;
 }
 
 strH   = GetResource( ‘STR#’, tabl );
 
 if( strH ){
 
 GetResInfo( strH, &theID, &theType, &theName );
 
 /*** compare the string to the allowable tokens ***/
 indx = 1 ;
 
 while( !done ){
 theString[0] = ‘\0’;
 GetIndString( &theString, tabl, indx );
 
 if( theString[0] == ‘\0’ ){
 /* no strings matched the input   */
 done = 1;
 }
 else{  /* attempt to match to current str */
 
 PtoCstr( (char *)&theString );
 
 len = 0;
 
 bp = theString;
 while ( *bp != ‘,’ ){
 bp++;
 len++;
 }
 
 if( strncmp( *buf, (char *)theString, len ) == 0){
 /* have a match so extract the token */                       
 
 /* move past any garbage in the string */
 while( (*bp < ‘0’ || *bp > ‘9’) && *bp != ‘-’)
 bp++;
 
 /* now copy what bp points to into a */
 /* a pascal style string */
 
 theNum[0] = ‘\0’;
 np = &theNum[1];
 
 while( *bp >= ‘0’ && *bp <= ‘9’ ){
 theNum[0]++;
 *np++ = *bp++;
 }
 
 /* np is a valid p-string */
 StringToNum( theNum, &token );
 done = 1;
 }
 else
 indx++;
 }
 }
 }
 
 return( token );
}

long  parseNum( bp )
char  *bp;
/***********************
* parse the data stream 
* and return a numeric 
* value.  The stream is null
* terminated.
***********************/
{
 long num = 0;
 
 char theString[256];
 short  done = 0;
 char *ps;
 
 /* move input pointer until we’re looking at a number */
 while( *bp && ( *bp < ‘0’ || *bp > ‘9’ ) && *bp != ‘-’ )
 bp++;
 
 /*** copy the data into a pascal string     ***/
 ps= theString;
 ps++;
 
 while( *bp >= ‘0’ && *bp <= ‘9’ )
 *ps++ = *bp++;
 
 /*** moved one past the output so try this  ***/
 theString[0] = (char)( ps - theString -1  );      
 StringToNum( theString, &num );
 
 return( num );  
}

char  *nextToken( bp )
 char *bp;
/***********************
* given a pointer to an
* input stream, move to the 
* next token in the stream. 
* 
* Token’s are delineated by
* ‘,’ or whitespace
* Assumes we are pointing to the current token
* Move past the current token and the white
* space that follows it.
***********************/
{
 /*** move past the current token  ***/
 while( *bp &&(*bp != ‘,’ && *bp != SPACE && *bp != CR && *bp != LF && 
*bp != TAB) )
 bp++;

 /*** move past the white space to the next token  ***/
 while( *bp == ‘,’ || *bp == SPACE || *bp == CR || *bp == LF || *bp == 
TAB )
 bp++;
 
 return( bp );
}

void  parseRect( buf, theRect )
 Handle buf;
 Rect *theRect;
/***********************
* parse the data stream 
* into a rectangle
*
* default is the NULL rect
***********************/
{
 char *bp;
 
 theRect->top = theRect->left = theRect->bottom = theRect->right = 0;
 
 if( validHandle( buf ) ){
 HLock( buf );
 
 bp = *buf;
 theRect->top = parseNum( bp );
 bp= nextToken( bp );
 
 theRect->left = parseNum( bp );
 bp= nextToken( bp );
 
 theRect->bottom = parseNum( bp );
 bp= nextToken( bp );
 
 theRect->right = parseNum( bp );
 bp= nextToken( bp );
 
 HUnlock( buf );
 }
}
Listing 4:  Control XFCN.h

/************************************/
/* Needed for communication between*/
/* Hypercard and the event loop    */
/************************************/
#define CONTROL_PATCH1001
#define CONTROL_INFO ‘cinf’
#define EVENT_TRAP 0xA970


typedef struct{
 Handle evtProc; /* Points to our event handler */
 ProcPtroldTrap; /* previous address of event trap */
 Handle userData;/* for use by the application */
} wdGlobals, *wdGlobalsPtr, **wdGlobalsHand;
Listing 5:  RemoveControl XFCN.c

/************************************/
/* File: RemoveControl XFCN.c */
/* */
/* Parameters:   */
/* params[0] == control handle*/
/* */
/* If no more control in the list  */
/* go ahead and dispose the patch  */
/* */
/* returns NIL to indicate the     */
/* control was removed    */
/* --------------------------------*/
/* © 1989 Donald Koscheka */
/* All Rights Reserved    */
/************************************/
#define UsingHypercard

#include<MacTypes.h>
#include<OSUtil.h>
#include<ControlMgr.h>
#include  <HyperXCmd.h>
#include<HyperUtils.h>
#include“Control XFCN.h”

#define ScrollSize 15
#define CONTROL_TYPES6000

#include“miniParser.c”

pascal void main( paramPtr )
 XCmdBlockPtr  paramPtr;
{
 ControlHandle theControl = NIL;
 WindowPtrtheWindow= FrontWindow();
 WindowPeek wPeek= (WindowPeek)theWindow;
 wdGlobalsHand windH;
 
 if( paramPtr->params[0] ){
 theControl = (ControlHandle)paramtoNum( paramPtr, 0 );
 
 if( validHandle( theControl ) )
 DisposeControl( theControl );

 }/*** if paramPtr[0] ***/

 /*** if no more control in the list ***/
 /*** remove the patch since it isn’t***/
 /*** needed any longer   ***/
 if( !wPeek->controlList ){
 if( windH = (wdGlobalsHand)GetSystemResource( CONTROL_INFO, CONTROL_PATCH 
) ){
 wdGlobalsPtr  wp  = *windH;
 Handle patch  = (Handle)(wp->evtProc);
 
 /*** unload the patch  ***/
 NSetTrapAddress( wp->oldTrap, EVENT_TRAP, ToolTrap);
 TrashHandle( patch );
 RemoveSystemResource( (Handle)windH );
 } 
 }

 paramPtr->returnValue = NIL;
}
Listing: 6:  HyperUtils.c

/********************************************************/
/* HyperUtils.c    */
/*      */
/* A collection of useful   */
/* routines...     */
/* --------------------------------------------------*/
/* 10/23/89 Added Routines to Support HyperAppleTalk */
/********************************************************/
#include<MacTypes.h>
#include<OSUtil.h>
#include<MemoryMgr.h>
#include<FileMgr.h>
#include<ResourceMgr.h>
#include<StdFilePkg.h>
#include  <HyperXCmd.h>
#include  <HyperUtils.h>


#define UsingHypercard

#ifdef  UsingHypercard

/****************************************************/
/* -- HyperCard UTILITIES --*/
/****************************************************/

void  paramtoPString( paramPtr, i, str )
 XCmdBlockPtr  paramPtr;
 short  i;
 char   *str;
/************************
* Given an index into the 
* parameter list, convert the data
* in the handle into a pstring
*
* 10.25.89 DK if handle empty, return empty.
************************/
{
 HLock( paramPtr->params[i] );
 ZeroToPas( paramPtr, (char *)*(paramPtr->params[i]), (char *)str );
 HUnlock( paramPtr->params[i] );
}

long  paramtoNum( paramPtr, i )
 XCmdBlockPtr  paramPtr;
 short  i;
/************************
* Given a handle to an input
* argument in the paramBlk
* return an integer representation
* of the data.
*
* 10.25.89 DK if handle empty, return empty.
************************/
{
 Str31  theStr;
 
 theStr.data[0] = ‘\0’;
 
 if( paramPtr->params[i] ){
 HLock( paramPtr->params[ i ] );
 ZeroToPas( paramPtr, (char *)*(paramPtr->params[ i ]), (char *)&theStr 
);
 HUnlock( paramPtr->params[ i ] );
 }
 return( StrToLong( paramPtr, (char *)&theStr ) );
}

Handle  NumToParam( paramPtr, num )
 XCmdBlockPtr  paramPtr;
 long   num;
/************************
* Given a long,  return a
* Handle to a string
* representation of the data.
************************/
{
 Str31  theStr;
 
 NumToStr( paramPtr, num, (char *)&theStr );
 
 return( PasToZero( paramPtr, (char *)&theStr ) );
}

#endif

/****************************************************/
/* -- Generic UTILITIES --*/
/* */
/* Routines that do generic things like allocate*/
/* memory in the system heap*/
/****************************************************/

void  *sys_alloc( len )
 long len;
/***************************
* Return a chunk of system heap
* requested (len bytes)
***************************/
{
 asm{
        move.l  len, D0
        NewPtr SYS
        move.l  A0, D0
      }
}

Handle  NewSysHandle( len )
 long len;
/***************************
* allocate a handle in the 
* system heap.  
*
* requested (len bytes)
*
* 10.31.89 added this routine to file
***************************/
{
 asm{
        move.l  len, D0
        NewHandleSYS
        move.l  A0, D0
      }
}

short validHandle( h )
 void *h;
/***************************
* This will be my handle checker
* in the future.  Right now, all 
* it does is check to see if the
* handle is nil.
* 
* In the future, I will use
* the information in the header
* to determine if the handle is
* a valid reference in the heap.
*
* (need to make sure this works
* across heap zones).
***************************/
{
 if( h )  
 return( TRUE );
 else
 return( FALSE );
}

void  TrashHandle( h )
 Handle h;
/***************************
* if the handle passed in is not
* NIL, unlock the handle and 
* dispose its contents
*
* ADD A TEST FOR VALID HANDLE!!!
*
* 10.31.89 sets it purgeable first
***************************/
{
 if( validHandle( h ) ){
 HPurge( h );
 HUnlock( h );
 DisposHandle( h );
 }
}

Handle  GetSystemResource( typ, id )
 ResTypetyp;
 short  id;
/***************************
* Load a resource from the system
* resources. Returns nil if handle
* not available.
*
* DEBUG: Return nil if the resource
* is unallocated (size == 0).
*
* 10.31.89 sets it purgeable first
***************************/
{
 short  oldRes = CurResFile();
 Handle h = NIL;

 UseResFile( SYS_RES );
 h = GetResource( typ, id );
 
 if( GetHandleSize( h ) == 0 )
 h = NIL;
 
 UseResFile( oldRes );
 
 return( h );
}

void  RemoveSystemResource( h )
 Handle h;
/***************************
* Remove the specified resource
* from the system resource file
*
* the handle is deleted from 
* memory and its reference in the
* resource map is removed.
***************************/
{
 short  oldRes = CurResFile();

 if( validHandle( h ) ){
 HPurge( h );
 HUnlock( h );

 UseResFile( SYS_RES );
 RmveResource( h );
 UpdateResFile( SYS_RES );
 
 UseResFile( oldRes );
 TrashHandle( h );
 }
}

void  AddSystemResource( h, typ, id, name )
 Handle h;
 ResTypetyp;
 short  id;
 char *name;
/***************************
* Given a handle to something,
* allocate the something as a 
* resource in the system file.
*
* Note that the resource gets
* locked down (this is a special
* routine that is used to allocate
* pseudo-global data.
*
* EVENTUALLY: WANT TO RETURN THE 
* ID SO THAT YOU CAN MAKE SURE YOU
* ALLOCATE A UNIQUE ID EACH TIME!!!
* THEN THE ID CAN BECOME A TASK
* REFERENCE NUMBER!!!
***************************/
{
 short  oldRes = CurResFile();
 short  attributes;
 
 if( validHandle( h ) ){
 MoveHHi( h );
 HLock( h );
 HNoPurge( h );

 UseResFile( SYS_RES );
 AddResource( h, typ, id, name );

 /*** test ***/
 /*** don’t want this resource written out ***/
 attributes = GetResAttrs( h );
 SetResAttrs( h, attributes & 0xfffe );

 UseResFile( oldRes );
 }
}

void  AppendCharToHandle( theHand, theChar )
 Handle theHand;
 char theChar;
/****************************
* Given a valid handle, append
* the character passed in to 
* the end of the handle
* 
* This is a useful way to embed
* \r, \t or \0 into a container
* for use by hypercard.
****************************/
{
 long   hsiz = GetHandleSize( theHand );

 SetHandleSize( theHand, hsiz + 1 );
 *(*theHand + hsiz) = theChar;
}

void  appendChar( theStr, theChar )
 char *theStr;
 char theChar;
/************************
* append the character passed
* to the end of the string 
************************/
{
 long len = strlen( theStr );
 char *theEnd;

 theEnd = theStr + len;
 *theEnd++  = theChar;
 *theEnd  = ‘\0’;
}

char  *CopyAscii( outStr, theChar )
 char *outStr;
 char theChar;
/************************
* if the character passed  
* in the input stream is a 
* printing character, append
* it to the output string,
* otherwise, append the ‘.’
* return the update output 
* string.
************************/
{
 if ( theChar >= SPACE  &&  theChar <= 0x0D8  ) 
 *outStr++ = theChar;
 else
 *outStr++ = ‘.’;
 
 return( outStr );
}
 
void CopyStrToHandle( theStr, hand )
 char *theStr;
 Handle hand;
/************************
* Copy the input data to the
* output handle.
*
* The input string is a “C”
* string.
************************/
{
 long cnt = strlen( theStr );
 long oldSize  = GetHandleSize( hand );

 SetHandleSize( hand, oldSize + cnt );
 BlockMove( theStr, *hand + oldSize, cnt );
}

void CopyDataToHandle( theStr, theLen, theHand, delim )
 char *theStr;
 long theLen;
 Handle theHand;
 char delim;
/************************
* Copy the input data to the
* output handle and append
* the requested character to
* the data.
************************/
{
 long oldSize  = GetHandleSize( theHand );

 SetHandleSize( theHand, oldSize + theLen );
 BlockMove( theStr, *theHand + oldSize, theLen );
 AppendCharToHandle( theHand, delim );
}

short pstrcmp( s1, s2 )
 char *s1;
 char *s2;
/************************
* Compare the two pascal strings
* passed in regardless of case.
*
* returning:
*FALSE  == don’t match
*  TRUE == do match
* 
* We first check to see if 
* both strings are the same
* length.  If not, no sense
* in proceeding.
************************/
{
 short  i;
 short  test= TRUE;
 short  len = (short)*s1;
 register char *str1 = s1;
 register char *str2 = s2;
 
 for( i = 0; i <= len; i++ )
 if( toUpper( *str1 ) == toUpper( *str2 ) ){
 str1++;
 str2++;
 }
 else{
 test = FALSE;
 break;
 }
 
 return( test );
}

short pStrToField( str, delim, list )
 char *str; 
 char delim;
 Handle list;
/*********************************
* Given a pascal string, append it to
* the end of the handle passed in list
* which is assumed to be a valid handle
* of length >= 0.
*
* delim is some character to stick on the
* end of the string to delimit it.
* if you want to build a list for presentation
* in a field, pass ‘\r’ as the delimiter
*
* If you are building items pass a comma
* 
* A value of 0 for delim is ignored.  Pass
* 0 when you don’t want a delimiter.
*********************************/
{
 long   strlen;  /* length of input string   */
 long   oldHSize;/* size of input handle     */
 char   *end;  /* pointer to end of data           */
 
 if( validHandle( list ) ){
 strlen = (long)str[0];   
 oldHSize = GetHandleSize( list );
 
 SetHandleSize( list, oldHSize + strlen );
 end = *list + oldHSize;
 
 BlockMove( (char *)&str[1] , end, strlen );
 AppendCharToHandle( list, delim );
 }
}

void  DataToField( str, len, delim, list )
 char *str; 
 short  len;
 char delim;
 Handle list;
/*********************************
* copy <len> bytes from the input
* stream to the output handle and
* append char <delim> to the end of 
* output handle.
*
* delim is some character to stick on the
* end of the string to delimit it.
* if you want to build a list for presentation
* in a field, pass ‘\r’ as the delimiter
*
* If you are building items pass a comma
* 
* A value of 0 for delim is ignored.  Pass
* 0 when you don’t want a delimiter.
*********************************/
{
 long   oldHSize;/* size of input handle */
 char   *end;  /* pointer to end of data     */
 
 if( validHandle( list ) ){
 oldHSize = GetHandleSize( list );
 
 SetHandleSize( list, oldHSize + len );
 end = *list + oldHSize;
 
 BlockMove( str, end, len );
 AppendCharToHandle( list, delim );
 }
}

void ClimbTree( child, cpb, fullName )
 long   child;
 CInfoPBPtr cpb;
 char   *fullName;
/*************************
* Climb the directory tree
* until we reach the root
*
* Allocate the records in the
* heap to keep the stack frame
* as small as necessary.  Too
* large a stack frame can 
* lead to a case of terminal
* terminal.
*
* child is the working directory
* id of the “current folder”, vol
* is the volume reference number and
* fullName points to the 
* output string
*************************/
{
 StringPtrfolder_name= (StringPtr)NewPtr( 256 );
 char   colon[2];
 
 colon[0] = ‘\1’;
 colon[1] = ‘:’;
 
 /* setting the file directory index to -1 */
 /* lets us get information about the */
 /* directory whose id is specified in the */
 /* ioDrDIrID field of the parameter block */
 folder_name[0] = ‘\0’; 
 
 cpb->dirInfo.ioNamePtr   = (StringPtr)folder_name;
 cpb->dirInfo.ioDrDirID   = child; 

 if( PBGetCatInfo( cpb, 0) == noErr ){
 ClimbTree(cpb->dirInfo.ioDrParID,cpb,fullName);

 Concat( (char *)fullName, (char *)folder_name );
 Concat( (char *)fullName, (char *)&colon );
 }
 DisposPtr( folder_name   );
}

void  CenterWindow( wptr )
 WindowPtrwptr;
/***************************
* Center a window in the current
* screen port.  Note: Does not
* attempt to work with multi-screen
* systems.
*
* This code is inspired by a
* similar routine written by Steve
* Maller in MPW Pascal.  Thanks Steve.
***************************/
{
 short  hWindSize = wptr->portRect.right - wptr->portRect.left;
 short  vWindSize = wptr->portRect.bottom - wptr->portRect.top;
 short  hSize = wptr->portBits.bounds.right - wptr->portBits.bounds.left;
 short  vSize = wptr->portBits.bounds.bottom - wptr->portBits.bounds.top;
 
 MoveWindow( wptr, ( hSize - hWindSize ) / 2, 
 ( vSize - vWindSize + 20) / 2, false);
}

void Concat( str1, str2 )
 char *str1;
 char *str2;
/*****************************
* Append string 2 to the end of
* string 1.  Both strings are 
* pascal-format strings.
*
* str1 must be large enough to hold
* the new string and is assumed to 
* be of Type Str255 (a pascal string)
*****************************/
{
 short len1 = *str1; /* number of chars in string 1 */
 short len2 = *str2++; /* number of chars in string 2 */
 char  *temp;  /* string pointer   */
 
 if( len1 +len2  > 255 )
 len2 = 255 - len1;
 
 *str1++ += len2 ; /* add sizes together to get new size */
 
 temp = str1 + len1; /* move to the end of string 1 */
 while( len2 ){
 *temp++ = *str2++;/* add a char to temp and move along */
 --len2; /* until all characters are added */
 }
}

void  CopyPStr( pStr1, pStr2 )
 char *pStr1;
 char *pStr2;
/****************************
* Copy the contents of pstr1 into
* pstr2.  The strings are assumed 
* to be of type STR255 (length byte
* precedes data 
****************************/
{short  i;
 char *tstr;
 
 tstr = pStr2;
 
 for( i = 0; i <= *pStr1; i++ )
 *tstr++ = *pStr1++;
}

short GetFileNameToOpen( typs, typCnt,theName, theWDID )
 SFTypeList typs;
 short  typCnt;
 char   *theName;
 short  *theWDID;
/*****************************
* Invokes SFOpenFile to query the 
* user for the name of a file to 
* open. 
*
* In:   List of types of files to
*filter for (up to 4)
*
* Out:  fileName if picked in theName
*working directory in theWDID
*nil otherwise
*the file’s volum ref num.
*
* ( Note that the space for the 
* string must be allocated by the
* caller).
*****************************/
{
 Point  where;
 char   prompt[1];
 SFReplyreply;
 GrafPort *oldPort;
 WindowPtrdlogID;
 
 prompt[0]  = ‘\0’;
 
 /*** Get and put up the standard file ***/
 /*** dialog.  You will only see the file***/
 /*** types that you filtered for.  If ***/
 /*** you filtered for no files, then  ***/
 /*** all files will display***/
 
 GetPort( &oldPort );
 dlogID = GetNewDialog( (short)getDlgID, (Ptr)NIL, (Ptr)UPFRONT );
 
 SetPort( dlogID );
 CenterWindow( dlogID );
 where.h = dlogID->portRect.left;
 where.v = dlogID->portRect.top;
 LocalToGlobal( &where );
 
 SFGetFile( where, prompt, (Ptr)NIL, typCnt, typs, (Ptr)NIL, &reply );
 
 DisposDialog( dlogID );
 SetPort( oldPort );
 
 /*** If the user selected a file, let’s ***/
 /*** get the information about it ***/
 
 if (reply.good){
 *theWDID = reply.vRefNum;
 PtoCstr( (char *)&reply.fName );
 strcpy( theName, &reply.fName  );
 }
 return( reply.good );
}

OSErr CopyFork( inref, outref, siz )
 short  inref;
 short  outref;
 long siz;
/*****************************
* Given that the caller has opened
* a fork and passed you the names of 
* the input, copy the number of bytes
* from the input fork to the output 
* fork.
*
* The input mark should be set to 
* start of fork.
* We use a “semi-smart” algorithm
* to do the copy.  If the entire
* fork can be copied, we try doing 
* that, otherwise, we keep dividing
* the size by two until we get enough
* room to read some data in.
*
******************************/
{
 OSErr  rd_err   = noErr;
 OSErr  wrt_err  = noErr;
 long rd_len;    /*** actual bytes read in   ***/
 Ptr    inbuf;
 
 /*** make sure that the size is even  ***/
 if( siz % 2 )
 ++siz;
 
 if( siz > 0 ){
 do{
 inbuf = NewPtr( siz );
 if( !inbuf )
 siz = ( siz >> 1 );
 
 }while( !inbuf );
 
 /* inbuf is the buffer that we read into    ***/
 /*** it ***/
 if( inbuf ){
 do{
 rd_len = siz;
 rd_err = FSRead( inref, &rd_len, inbuf );
 wrt_err= FSWrite( outref, &rd_len, inbuf );
 }while( !rd_err && !wrt_err );
 
 DisposPtr( inbuf );
 }
 }
 return( wrt_err );
}

OSErr CopyFile( inFile, inWD, outFile, outWD )
 char *inFile;
 short  inWD;
 char *outFile;
 short  outWD;
/*****************************
* (1) Determine the size of the input 
* file. 
*
* (2) Attempt to allocate that 
* much space for the output file.
*
* (3) If allocation successful,
* create the output file.
*
* (4) Once the file is created,
* copy the data fork, the resource
* fork and the finder information
* from the input file.
* 
* The file will be called “copy of...”
* Each time we create the file, first
* see if that name exists, if so, keep
* sticking “copy of” onto the name.
******************************/
{
 OSErr  err;
 OSErr  err2;
 short  inref;
 short  outref;
 long   data_eof = 0L;
 long   rsrc_eof = 0L;
 FInfo  fndrinfo;
 short  vref;  /** volume the file is on     ***/
 
 /* (2) Determine how big the input file is */
 if( (err = FSOpen( inFile, inWD, &inref )) == noErr){
 err = GetEOF( inref, &data_eof );
 err = FSClose( inref );
 } 
 
 if( (err = OpenRF( inFile, inWD, &inref )) == noErr ){
 err = GetEOF( inref, &rsrc_eof );
 err = FSClose( inref );  
 }
 
 /* (2) Create  output file and allocate space */
 if( ( err = GetFInfo( inFile, inWD, &fndrinfo ) ) != noErr )
 return( err );
 
 if( ( err = Create( outFile, inWD, fndrinfo.fdCreator, fndrinfo.fdType 
)) != noErr )
 return( err );
 
 /* (3) Try to allocate enough space for both */
 /* forks. Note that if we get enough space. */
 
 if( (err = FSOpen( outFile, outWD, &outref )) != noErr)
 return( err );
 
 if( (err = SetEOF( outref, data_eof  )) != noErr ){
 err2 = FSClose( outref );
 err2 = FSDelete( outFile, outWD );
 return( err );
 }

 err2   = FSClose( outref );
 err  = OpenRF( outFile, outWD, &outref );
 
 if( (err = SetEOF( outref, rsrc_eof  )) != noErr ){
 err2 = FSClose( outref );
 err2 = FSDelete( outFile, outWD );
 return( err );
 }
 err2 = FSClose( outref );
 
 /*** (4) Copy the Data fork***/
 err  = FSOpen( inFile, inWD, &inref );
 
 if( !err ){
 err2   = SetFPos( inref, fsFromStart, 0L );
 err    = CopyFork( inref, outref, data_eof );
 err2   = FSClose( inref );
 err2   = FSClose( outref );
 }
 
 if( err ){
 err2 = FSDelete( outFile, outWD );
 return( err );
 }
 
 /*** (5) Now copy the resource fork ***/
 err  = OpenRF( inFile, inWD, &inref );
 
 if( !err ){
 err2   = SetFPos( inref, fsFromStart, 0L );
 err  = OpenRF( outFile, outWD, &outref );
 err    = CopyFork( inref, outref, rsrc_eof );
 err2   = FSClose( inref );
 err2   = FSClose( outref );
 }
 
 if( err ){
 err2 = FSDelete( outFile, outWD );
 return( err );
 }
 
 return( noErr );
}

/*** 10/23/89 ***/
#ifdef  UsingHypercard

void SaveHandle(paramPtr, containerName, theData)
 XCmdBlockPtr  paramPtr;
 Handle containerName;
 long   theData;
/**********************************
* Saves a long integer (handle) away 
* in the Hypercard global container 
* whose name is passed as a parameter. 
* containerName is a Handle containing
* a null terminated string.
*
* IN: 
* containerName  =the name of
*a Hypercard container
* theData = the handle to store
*in this container.
**********************************/
{
 Handle theDataHand;
 char pasStr[256];
 char   buffHandle[256];

 buffHandle[0] = ‘\0’;
 
 /*** convert the data into a string since   ***/
 /*** that is the friendliest form for HC    ***/
 LongToStr( paramPtr, theData, (char *)&buffHandle );
 theDataHand = PasToZero( paramPtr, (char *)buffHandle );
 
 /*** convert the name to a string ***/
 HLock( containerName );
 ZeroToPas( paramPtr, *containerName, (char *)&pasStr);

 SetGlobal( paramPtr, pasStr, theDataHand );
 HUnlock( containerName );
}

void PutEmptyIntoGlobal(paramPtr, containerName)
 XCmdBlockPtr  paramPtr;
 Handle containerName;
/**********************************
* Sets the given global to the empty
* container.
*
* IN: 
* containerName  =the name of
*a Hypercard container
**********************************/
{
 Handle theDataHand;
 char pasStr[256];
 char   buffHandle[256];

 buffHandle[0] = ‘\0’;
 
 /*** convert the data into a string since   ***/
 /*** that is the friendliest form for HC    ***/
 theDataHand = PasToZero( paramPtr, (char *)buffHandle );
 
 /*** convert the name to a string ***/
 HLock( containerName );
 ZeroToPas( paramPtr, (char *)*containerName, (char *)&pasStr);

 SetGlobal( paramPtr, pasStr, theDataHand );
 HUnlock( containerName );
}

long RetrieveHandle(paramPtr, containerName )
 XCmdBlockPtr  paramPtr;
 Handle containerName;
/**********************************
* Retrieve a handle from the Hypercard
* global pool.  The name of the global 
* container is passed as a parameter
*
* If no data could be extracted from the
* container, we return empty.
**********************************/
{
 Handle theData; /* handle to the data as string */
 char   localString[256];
 long   returnHandle = 0L;
 
 if( containerName ){
 HLock( containerName );  
 ZeroToPas( paramPtr, (char *)*containerName, (char *)&localString );
 theData = GetGlobal( paramPtr, (char *)&localString ); 
 HUnlock( containerName );
 
 if ( theData ){
 MoveHHi( theData );
 HLock( theData );
 ZeroToPas( paramPtr, *theData, (char *)&localString );
 returnHandle = StrToLong( paramPtr, (char *)&localString);
 TrashHandle( theData );
 return( (long)returnHandle );
 }
 }
 
 return( 0L );
}

Handle ErrorReturn( theErr )
 short theErr;
/**********************************
* Given a system error number in theErr
* extract its string from the resource
* fork and returns it to HyperCard
*
* If we can’t work out the error string,
* build one using the error number.
**********************************/
{
 Handle strhand = NIL;
 char s[256];
 char num[256];
 long len = 0;
 
 if(theErr == noErr)
 return( NIL );

 *s = ‘\0’;
 
 strhand = GetResource( ‘STR ‘, theErr );
 if( strhand ){
 /* copy the string to another handle and save it off */
 len = GetHandleSize( strhand );
 BlockMove( *strhand, s, len );
 PtoCstr( (char *)&s );
 }
 else{
 s[0]=’E’; s[1]=’R’; s[2]=’R’; s[3]=’O’; s[4]=’R’; s[5]=’ ‘; s[6]=’\0';
 NumToString( (long)theErr, num );
 sappend( s, num );
 len = (long)slength( s );
 }
 
 if( *s ){
 strhand = NewHandle( len );
 BlockMove( s, *strhand, len );
 }
 return( strhand );
}

/*    Miscellaneous Support Routines */

void pStrCopy( inStr,outStr )
 char *inStr, *outStr;
/*********************************
* copy the pascal-style string from
* the input string to the output 
* string.
*********************************/
{
 short i;
 
 for (i = 0; i <= inStr[0]; i++)
 outStr[i] = inStr[i];
}

short slength( str )
 char *str;
/*********************************
* return the length of a c string
* including the ‘\0’ byte. 
*********************************/
{
 short i = 0;
 
 while( *str++ )
 i++;
 
 return( (i+1) );
}

short strcopy( dst, src )
 char *dst, *src;
/*********************************
* Copy a “C” string to another
* “C” string. 
*********************************/
{
 while( *dst++ = *src++ );
}

short sappend( dst, src )
 char  *dst, *src;
/*********************************
* NOTE: the destination
* string must be large enough to hold
* both strings going in!
*
* tacks source onto destination.
*********************************/
{
 while( *dst )
 dst++;
 while( *dst++ = *src++ );
}

short strCMP( s1, s2 )
 char *s1, *s2;
/*********************************
* compare two c strings regardless
* of case returning:
*
* <0 if s1 < s2
* 0  if s1 == s2
* >0 if s1 > s2
*********************************/
{
 for( ; toUpper( *s1 ) == toUpper( *s2 ); s1++, s2++ )
 if( *s1 == ‘\0’ )
 return( 0 );
 
 return( *s1 - *s2 );
}

#endif

Listing 7:  HyperUtils.H

/********************************/
/* HyperUtils.H  */
/* */
/* Header file for HyperUtils.c  */
/* routines...   */
/* */
/********************************/
#define UPFRONT  -1L
#define SPACE    32
#define SYS_RES  0 /* resource id of the system */

#ifndef NIL  /* standard definition of NIL   */
 #define NIL ( (void *)0 )
#endif

#ifndef empty   /* an alias for nil */
 #define empty ( (void *)0 )
#endif

#define isUpper( c ) ( (c >= ‘A’ && c <= ‘Z’) ? 1 : 0 )
#define isLower( c ) ( (c >= ‘a’ && c <= ‘z’) ? 1 : 0 )
#define toUpper( c ) ( (isLower( c )) ? c - ‘a’ + ‘A’: c )
#define toLower( c ) ( (isUpper( c )) ? c + ‘a’ - ‘A’: c )
#define isAlpha( c ) ( ((c >= ‘A’ && c <= ‘Z’) || (c >= ‘a’ && c <= ‘z’)) 
? 1 : 0 )

#ifdef UsingHypercard
void  paramtoPString( XCmdBlockPtr paramPtr, short i, char *n);
long  paramtoNum();
Handle  NumToParam( XCmdBlockPtr paramPtr, long num );
#endif

void  *sys_alloc( long len );
short validHandle( void *h );
void  TrashHandle( Handle h );
Handle  NewSysHandle( long len );
Handle  GetSystemResource( ResType typ, short id );
void  RemoveSystemResource( Handle h );
void  AddSystemResource( Handle h, ResType typ, short id, char *name 
);

void  appendChar( char *theStr, char theChar );
void  CopyStrToHandle( char *theStr, Handle hand );
void  CopyDataToHandle( char *theData, long theLen, Handle theHand, char 
delim );
char  *CopyAscii( char *outStr, char theChar );
void  AppendCharToHandle( Handle theHand, char theChar );

short pStrToField( char *str, char delim, Handle list );
void  DataToField( char *str, short len, char delim, Handle list );

void  ClimbTree( long child, CInfoPBPtr cpb, char *fullName );
void  CenterWindow( WindowPtr wptr );
void  Concat( char * str1, char * str2 );
void  CopyPStr( char * pStr1, char * pStr2 );
short GetFileNameToOpen(SFTypeList typs,short typcnt, char *theName, 
short *theWDID);
OSErr CopyFile( char *inFile, short inWD, char *outFile, short outWD 
);

Handle  ErrorReturn( OSErr theErr );

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Numi 3.15.1 - Menu-bar calculator suppor...
Numi is a calculator that magically combines calculations with text, and allows you to freely share your computations. Numi combines text editor and calculator Support plain English. For example, '5... Read more
TextSoap 8.4.1 - Automate tedious text d...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
TextSoap 8.4.1 - Automate tedious text d...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
Backblaze 4.3.0.44 - Online backup servi...
Backblaze is an online backup service designed from the ground-up for the Mac. With unlimited storage available for $5 per month, as well as a free 15-day trial, peace of mind is within reach with... Read more
Numi 3.15 - Menu-bar calculator supports...
Numi is a calculator that magically combines calculations with text, and allows you to freely share your computations. Numi combines text editor and calculator Support plain English. For example, '5... Read more
EtreCheck 3.3.3 - For troubleshooting yo...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
BusyContacts 1.1.8 - Fast, efficient con...
BusyContacts is a contact manager for OS X that makes creating, finding, and managing contacts faster and more efficient. It brings to contact management the same power, flexibility, and sharing... Read more
TunnelBear 3.0.14 - Subscription-based p...
TunnelBear is a subscription-based virtual private network (VPN) service and companion app, enabling you to browse the internet privately and securely. Features Browse privately - Secure your data... Read more
Apple Final Cut Pro X 10.3.4 - Professio...
Apple Final Cut Pro X is a professional video editing solution.Completely redesigned from the ground up, Final Cut Pro adds extraordinary speed, quality, and flexibility to every part of the post-... Read more
Hopper Disassembler 4.2.1- - Binary disa...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32-bit and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about... Read more

Latest Forum Discussions

See All

The best new games we played this week
We were quite busy this week. A bunch of big mobile games launched over the past few days, alongside a few teeny surprises. There're lots of quality games to load your phone with. We've gone and picked out five of our favorites for the week. [... | Read more »
Magikarp Jump beginner's guide
Magikarp Jump is a mystifying little game. Part Tamagotchi, part idle clicker, there's not a whole lot of video game there, per se, but for some reason we can't help coming back to it again and again. Your goal is to train up a little Magikarp to... | Read more »
Goat Simulator PAYDAY (Games)
Goat Simulator PAYDAY 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: ** IMPORTANT - SUPPORTED DEVICES **iPhone 4S, iPad 2, iPod Touch 5 or better Goat Simulator: Payday is the most... | Read more »
GRID Autosport delayed until autumn
Sorry mobile racing fans -- GRID Autosport has been delayed a few months. The game is now expected to launch this fall on iOS. Feral Interactive announced that they wanted more time to work on the game's UI and overall performance before launching... | Read more »
Zombie Gunship Survival Beginner's...
The much anticipated Zombie Gunship Survival is here. In this latest entry in the Zombie Gunship franchise, you're tasked with supporting ground troops and protecting your base from the zombie horde. There's a lot of rich base building fun, and... | Read more »
Mordheim: Warband Skirmish (Games)
Mordheim: Warband Skirmish 1.2.2 Device: iOS Universal Category: Games Price: $3.99, Version: 1.2.2 (iTunes) Description: Explore the ruins of the City of Mordheim, clash with other scavenging warbands and collect Wyrdstone -... | Read more »
Mordheim: Warband Skirmish brings tablet...
Legendary Games has just launched Mordheim: Warband Skirmish, a new turn-based action game for iOS and Android. | Read more »
Magikarp Jump splashes onto Android worl...
If you're tired ofPokémon GObut still want something to satisfy your mobilePokémon fix,Magikarp Jumpmay just do the trick. It's out now on Android devices the world over. While it looks like a simple arcade jumper, there's quite a bit more to it... | Read more »
Purrfectly charming open-world RPG Cat Q...
Cat Quest, an expansive open-world RPG from former Koei-Tecmo developers, got a new gameplay trailer today. The video showcases the combat and exploration features of this feline-themed RPG. Cat puns abound as you travel across a large map in a... | Read more »
Jaipur: A Card Game of Duels (Games)
Jaipur: A Card Game of Duels 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** WARNING: iPad 2, iPad Mini 1 & iPhone 4S are NOT compatible. ** *** Special Launch Price for a limited... | Read more »

Price Scanner via MacPrices.net

Memorial Day savings: 13-inch Touch Bar MacBo...
B&H Photo has the 2016 Apple 13″ Touch Bar MacBook Pros in stock today and on sale for up to $150 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 13″ 2.9GHz/512GB... Read more
Apple refurbished 13-inch MacBook Airs availa...
Apple has Certified Refurbished 2016 13″ MacBook Airs available starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: - 13″ 1.6GHz/8GB/128GB MacBook Air: $... Read more
Apple restocks refurbished 11-inch MacBook Ai...
Apple has Certified Refurbished 11″ MacBook Airs (the latest models recently discontinued by Apple), available for up to $170 off original MSRP. An Apple one-year warranty is included with each... Read more
12-inch 1.2GHz Retina MacBooks on sale for up...
B&H has 12″ 1.2GHz Retina MacBooks on sale for up to $150 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 12″ 1.2GHz Space Gray Retina MacBook: $1449.99 $150 off... Read more
15-inch 2.7GHz Silver Touch Bar MacBook Pro o...
MacMall has the 15-inch 2.7GHz Silver Touch Bar MacBook Pro (MLW82LL/A) on sale for $2569 as part of their Memorial Day sale. Shipping is free. Their price is $230 off MSRP. Read more
Free Tread Wisely Mobile App Endorsed By Fath...
Just in time for the summer driving season, Cooper Tire & Rubber Company has announced the launch of a new Tread Wisely mobile app. Designed to promote tire and vehicle safety among teens and... Read more
Commercial Notebooks And Detachable Tablets W...
Worldwide shipments of personal computing devices (PCDs), comprised of traditional PCs (a combination of desktop, notebook, and workstations) and tablets (slates and detachables), are forecast to... Read more
Best value this Memorial Day weekend: Touch B...
Apple has Certified Refurbished 2016 15″ and 13″ MacBook Pros available for $230 to $420 off original MSRP. An Apple one-year warranty is included with each model, and shipping is free: - 15″ 2.6GHz... Read more
13-inch MacBook Airs on sale for up to $130 o...
Overstock.com has 13″ MacBook Airs on sale for up to $130 off MSRP including free shipping: - 13″ 1.6GHz/128GB MacBook Air (sku MMGF2LL/A): $869.99 $130 off MSRP - 13″ 1.6GHz/256GB MacBook Air (sku... Read more
2.8GHz Mac mini available for $973 with free...
Adorama has the 2.8GHz Mac mini available for $973, $16 off MSRP, including a free copy of Apple’s 3-Year AppleCare Protection Plan. Shipping is free, and Adorama charges sales tax in NY & NJ... Read more

Jobs Board

*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
Best Buy *Apple* Computing Master - Best Bu...
**509643BR** **Job Title:** Best Buy Apple Computing Master **Location Number:** 001482- Apple Valley-Store **Job Description:** **What does a Best Buy Apple Read more
*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
*Apple* Mac and Mobility Engineer - Infogrou...
Title: Apple Mac and Mobility Engineer Location: Portland, OR Area Type: 12 month contract Job: 17412 Here's a chance to take your skills to the limit, learn new Read more
*Apple* Retail - Multiple Positions, White P...
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.