TweetFollow Us on Twitter

Vital Signs
Volume Number:7
Issue Number:7
Column Tag:C Forum

Related Info: Desk Manager Device Manager OS Utilities

Vital Signs

By Ted Johnson, President, T. Bear Software

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

[Ted Johnson graduated from MIT with a BSEE in June of 1987 and works for Hewlett-Packard now in Santa Clara]

MacTutor is a great magazine, but it can be a bit overwhelming for the beginning Macintosh programmer. This article is an attempt to change that.

I did a fair amount of programming during my undergrad years at MIT in all sorts of languages, including C, Pascal, Scheme, FORTRAN, assembly language, and microcode. Most of it was on fairly large and robust multi-user systems, so I didn’t have to worry about a bug in my program crashing the whole machine. Such is not the case with the Macintosh.

I just began programming on the Mac last June, and quickly discovered that fatal bugs in my program could be fatal to the Mac’s operating system as well. I found that the best way to get something to work was to look at the source code for someone else’s program who got it to work. I like to call this “leveraging off of the existing technology”.

To make life easier for beginning Macintosh programmers, I have come up with a THINK C™ desk accessory example, Vital Signs, which shows how to do just about everything that you would ever want a DA to do. I hope that this desk accessory can shorten the learning curve for beginning programmers, but also demonstrate some useful techniques to the more seasoned programmers out there. I tried to follow all of Inside Macintosh’s commandments, including checking all of the Toolbox routines’ return codes for errors. Feel free to cut and paste pieces of Vital Signs code into your own DA. Don’t reinvent the wheel; leverage off of the existing technology!

What does Vital Signs do?

Vital Signs puts up a window with two bar graphs and an animated heart and displays statistics about your Macintosh. As you can see in Figure 1, it is time for me to clean some of the files off of my hard disk!

Figure 1. The Vital Signs display.

The heart beats (audibly!), and the various displays (time, RAM usage, etc.) are updated automatically, to reflect the current state of your Mac. Since I use the SysBeep() trap to generate the heartbeat sound, you can use the Control Panel DA to adjust the volume.

It is kind of interesting to run Vital Signs when you are using different programs. Some programs (did someone say Excel?) are real memory hogs, whereas others are surprisingly frugal.

The RAM and default disk displays are updated every ten seconds; the time display is updated every second. None of the other information is updated, because there is no way it can change without closing Vital Signs.

The source code for Vital Signs will show you how to do the “standard” DA stuff, i.e.:

-put up a window, and draw text and graphics in it

-compute owned resources

-put up a menu

-display an “About...” box (like mine, in Figure 2)

Figure 2. Our “About...” box.

-respond to update, activate, keydown, autokey, and all other events

In addition, Vital Signs shows you how to do some things I’ve haven’t yet seen in MacTutor, i.e.:

-calculate and display the amount of disk space (used and total) on the default disk drive

-find the amount of total RAM, and the amount of used RAM

-find the System and Finder version numbers

-use the GetDateTime(), IUDateString(), and IUTimeString() traps to find the current date and time.

Hunting for version information

One of the first problems I had to solve was how to determine the version number of the System and Finder system files from inside a program.

I had a feeling that there was a version number text string buried in an obscure resource somewhere in the System and Finder system files, so I went hunting with ResEdit. Sure enough, I found what I was looking for.

As a matter of fact, I found several resources with version information. The ‘MACS’, ‘FNDR’, and ‘STR ‘ resources which have ID = 0 all contain version information.

Great! So I can just pick one of these resources and display its text string? Unfortunately not. It turns out that Apple has been inconsistent over the various System/Finder pairs (see Figures 3 and 4).

Figure 3. Resources in the System file which contain version information.

System/Finder 4.1/5.5 have a ‘FNDR’ and ‘STR ‘ resources (respectively) that are the perfect length to be displayed in Vital Signs’ window. However the next release (System/Finder 4.2/6.0) was incredibly verbose, so I had to abandon my idea. Oh well.

Figure 4. Resources in the Finder file which contain version information.

I ended up just using the new SysEnvirons() trap to display the System file version number. But at least now we know what resources to look at if we ever want to identify the version number of a System or Finder system file without booting off of it.

General DA Information

In THINK C™, all DAs have a main() which takes three arguments:

/* 1 */

main(ioPB, theDCE, n) 
cntrlParam *ioPB;
DCtlPtr   theDCE;
int     n;

Don’t be intimidated. Things aren’t as complicated as they appear. ioPB is our DA’s I/O Parameter Block. It is used by the Mac operating system to tell our DA which event it got, and which menu item was selected from our DA’s menu(s). It is also used to tell our DA when it is time to perform some periodic action (e.g., update a clock display).

theDCE is our DA’s device control entry. By setting certain flags in this structure, we can ask the Mac operating system to notify our DA every x ticks (a tick is 1/60 of a second). This is useful for doing things like updating a clock display. theDCE is where we ask to be “woken up” every x ticks. ioPB is where we receive the “wake up” call. We also use theDCE to store our WindowPtr and our global data.

n is our DA’s entry point selector. Desk accessories have five different entry points (open, prime, control, status, and close). The value of n is used to decide which one of them was called.

To correspond to these five entry points there must be five subroutines. Two of them (prime and status) are rarely used by DAs, but they must exist nonetheless.

What DAs Can’t Do

Desk accessories cannot access QuickDraw global variables. This means that you can’t use the predefined Patterns (ltGray, dkGray, black, etc.), or the global variable screenBits, among other things.

Hmm, if we can’t access screenBits (which contains information about the physical size and resolution of the Mac’s monitor), how is Vital Signs able to display the resolution of the screen?

The trick is to grab the portBits of the current port, and use its “bounds” information. The code fragment which does this is shown below:

/* 2 */

GrafPtr oldPort;  
BitMap ScreenInfoBitMap;
GetPort(&oldPort);
ScreenInfoBitMap = (*oldPort).portBits;

Now we can get the horizontal and vertical resolution from the values:

/* 3 */

ScreenInfoBitMap.bounds.right

and

ScreenInfoBitMap.bounds.bottom

Defining your own Patterns

I used a black Pattern to fill in the bar graph for the Vital Signs RAM and disk space displays. I had to define my own because I couldn’t access QuickDraw’s predefined black global variable. It is quite easy to define your own Patterns.

According to Inside Macintosh I-145, “A pattern is a 64-bit image, organized as an 8-by-8-bit square, that’s used to define a repeating design (such as stripes) or tone (such as gray). QuickDraw provides [five] predefined patterns in global variables named white, black, gray, ltGray,and dkGray. Any other 64-bit variable or constant can also be used as a pattern.”

The following code fragment shows how I created a black pattern:

/* 4 */

Ptr myBlackPattern;
/*First allocate some space.*/
myBlackPattern = NewPtr(sizeof(Pattern));
/*Now create the pattern.*/
StuffHex(myBlackPattern, “\pFFFFFFFFFFFFFFFF”);

Creating a non-black pattern is a lot easier than it looks. Instead of playing around with hex codes, just use ResEdit and create a ‘PAT ‘ resource in a scratch resource file. Now you can edit your pattern graphically! To see what the16 digit hex representation for this pattern is, simply close the ‘PAT ‘ resource and then do an “Open General” on it. Voila!

You can also just look at the ‘PAT ‘ resources of the SuperPaint application.

Owned Resources Tutorial

A desk accessory can have non-code resources, just like an application. Your DA certainly doesn’t have to use resources, but it often makes life easier if you do.

A DA’s resources are said to be “owned” by the DA. A resource is “owned” if it has a negative resource ID number.

When you install a DA, the Font/DA Mover moves that DA’s ‘DRVR’ resource into the System file. If the DA has any owned resources, those are moved into the System file also. If the DA has resources but they aren’t owned, then they will be left behind and the DA will soon die an ugly death.

A resource ID is a 16 bit integer. For owned resources, the top two bits of the resource ID are always 1. Since the top bit is interpreted as a sign bit, this means the resource ID is a negative number. The bottom 5 bits (the base ID) of the resource ID number are constant.

In Vital Signs I #define’d the base id of my resouces to be the value decimal 1 (which is 00001 in binary). I could have chosen any value between 0 (0b00000) and 32 (0b11111), inclusive.

Desk accessories are “drivers”, which means that they have a DRVR number. The DRVR number of a desk accessory is determined by the Font/DA Mover when you install the DA into your System file. Thus, the same desk accessory on two different Macs could have different DRVR numbers.

Every desk accessory that is created with LightspeedC™ gets a default DRVR ID of 12. The Font/DA Mover may change this (from 12 to 26) if DRVR slot #12 is already taken. When it changes your DA’s DRVR number, it also changes the ID number all of the resources that your DA owns.

This means that your DA has to calculate (“resolve”) the owned resource numbers of all of its resources at run time!!! You can’t just embed a constant in your DA’s code! Fortunately there is a formula to determine exactly what your owned resource number is:

owned resource # = -16384
+ (32 * DRVR ID)
+ base id

Dan Weston’s book (see reference section, below) has a good derivation and explanation of this formula. Also see Inside Macintosh I-109, 110.

Summary:

1. owned resource # = f(DRVR ID, base id)

2. You #define the base id in your source code. The Font/DA Mover determines the value of DRVR ID when you install the DA.

3. LightSpeedC™ gives all DAs a DRVR ID of 12.

4. In our source code, “Vital Signs.c”, we have #define’d the base id of our owned resources to be equal to 1.

5. We use the above formula to figure out what the owned resource # of our resources are.

Walking through the code

Now that we know how a typical DA works, lets take a look at how Vital Signs operates.

When Vital Signs is selected from the Apple menu, main() is called with an entry point selector, n, which has the value of the constant drvrOpen.

The switch(n) statement in main() then passes control to doOpen(). doOpen() does a lot of error checking, and beeps and quits if everything isn’t kosher. Some of the checks it does are: making sure that this is the first time Vital Signs has been opened, and making sure that the Mac is running HFS. If all is well, then doOpen() will go ahead and initialize all of Vital Signs’ global variables.

Vital Signs is next sent an activateEvt by the Mac operating system, so that main() is called with an entry point selector, n, which has the value of the constant drvrControl. The switch(n) in main() then calls doControl(), which parses the event type and draws the Vital Signs display.

Vital Signs should never be sent drvrPrime or drvrStatus entry point selectors, but if it is then either doPrime() or doStatus() are called. Neither of these subroutines do anythng.

If Vital Signs is told to close, then doClose() disposes of its storage and deletes its menu from the menu bar.

If the user opens Vital Signs from inside an application and then kills the application without closing Vital Signs, doGoodBye() will be called. doGoodBye() is used to respond to a “goodbye kiss”. A goodbye kiss is a message that the Mac operating system sends to your DA if the user quits an application without closing your DA. Your DA then has an opportunity to clean up after itself (e.g., remove any hooks it installed, or restore any low memory global variables values that it changed) before the application heap is reinitialized.

Calculating Default Disk Space

I use the PBHGetVInfo() trap to get information about the default disk ( its name, how many files and folders it has, etc.).

To calculate the capacity of the disk, multiply the number of volume allocation blocks, ioVNmAlBlks, by the volume allocation block size, ioVAlBlkSiz.

To calculate the free space on the disk, multiply the number of free volume allocation blocks, ioVFrBlk, by the volume allocation block size, ioVAlBlkSiz.

The code which does this is in the ShowDefaultDiskInfo() subroutine.

Calculating total RAM

To calculate the amount of RAM, use the trap TopMem(). This returns a pointer to the end of RAM. The code which does this is in the ShowMemoryInfo() subroutine.

Animating the heart

Animating the heart was a bit tricky. At first I just redrew the entire Vital Signs display, once with a large heart and once with a small heart. This produced an embarrassingly noticeable amount of flicker.

By reducing the amount of redrawing to the actual heart itself, I managed to reduce the flicker to a negligible level.

I made the two heart pictures in MacPaint and used ResEdit to paste them into my resource file (“Vital Signs proj.rsrc”) as ‘PICT’ resources. The MacPlus is also a ‘PICT’ resource.

All of these ‘PICT’s are drawn by the DrawOurPicture() subroutine, which calls the DrawPicture() trap. After drawing the MacPlus and the large heart, I use EraseRect() to erase it and then draw the small heart. It isn’t necessary to erase the small heart before drawing the next large heart because the large heart ‘PICT ‘ completely covers the small heart ‘PICT’.

The heartbeat sound is done by calling SysBeep(). This means that if you have the “BeepInit” or “Lunar Crack” INITs in your System Folder you should remove them and reboot. Both of these INITs patch theSysBeep() trap, and could cause Vital Signs to behave very strangely if you use Vital Signs’ audible heartbeat option.

What time is it? What day is it?

Did you ever wonder where clock DAs get the time from? And how calendar programs know what day it is?

Three trap calls (from the “International Utilities” and “OperatingSystem Utilities” sections of Inside Macintosh) are all it takes.

GetDateTime() gives the number of seconds between midnight, January 1, 1904, and the current time. If you pass this number to IUTimeString(), you will get a Pascal string which contains the current time (in terms of hours, minutes, and seconds).

If you pass the seconds to IUDateString(), you will get a Pascal string which contains the current date (in terms of the month, year, and day of the week).

The format of the time and date are highly variable, so you can customize it to suit your own tastes and/or a particular foreign country’s customs.

A few comments about comments

Some people don’t like to comment their code. One engineer I approached about this said, “Hey, if it was hard [for me] to write, it should be hard [for you] to read!”. This person was a jerk. Comments can be helpful to yourself as well as to others. Things which seem obvious when you write the program can become obscure when you look at the code several months later. The source code for Vital Signs isn’t as heavily commented as I would like, but all of the really important stuff is documented.

Compilation Instructions

Use LightSpeedC™, version 2.15.

The first step is to create the Vital Signs resource file, “Vital Signs proj.rsrc”. Since I used ResEdit to create the resource file, I don’t have a RMaker file to show you. Fortunately, there is a neat little public domain program called ResTools which can decompile resource files into RMaker-like text files. ResTools is to LightspeedC™ what DeRez is to MPW.

Create a project called “Vital Signs proj”, and add the “MacTraps” library to it. Now select “Set Project Type...” from the Project menu. Choose the “Desk Accessory” radio button, and type “Vital Signs” for the name of the DA.

Type in the file “Vital Signs.c”, and add it to the project. Select “Build Desk Accessory” from the Project menu. Name the file “Vital Signs DA”. The project window should now look like Figure 5.

Figure 5. Vital Signs’ project window.

Now exit LightspeedC™ and install Vital Signs just like any other DA.

LightspeedC™ bugs (and fixes)

There are two bugs in the header file “FileMgr.h”. The volume parameter block variable ioVNmAlBlks should be declared as an unsigned int, not an int. The same goes for the volume control block variable vcbNmBlks.

This bug took a long time to track down. Both Rich Siegel (a THINK QA technician) and Ephraim Vishniac (a helpful fellow MIT alumus) helped considerably.

The symptoms were that I was getting strange results when I tried to compute the capacity of my hard disk. It turned out that the top bit of ioVNmBlks was mistakenly being interpreted as a sign bit. The problem is that Inside Macintosh IV-130 listed these parameters as type word. So naturally THINK translated them into int’s. But if you read theWarning on page IV-130, it says that these parameters are actually unsigned integers! Why then didn’t Inside Macintosh specify that in the Pascal definitions of the parameters? Because Pascal doesn’t have an unsigned int data type!

Another LightspeedC™ bug (and fix)

There is also a bug in the header file “MacTypes.h”. The declaration:

typedef unsigned char *StringPtr;

should read:

typedef Str255 *StringPtr;

I had a lot of trouble getting the name of the default disk drive via the PHBGetVInfo() trap. After much digging around, I discovered that LightspeedC™’s StringPtr definition didn’t agree with the one given on page 78 of Inside Macintosh I. As soon as I made the above change in “MacTypes.h”, it worked.

Vital Signs’ imperfections

I developed Vital Signs on a SE HD20 which is running System/Finder 4.1/5.5. It works fine with all the programs I tried it with (SuperPaint 1.0, MacPaint 1.5, LightspeedC™ 2.15, MacWrite 4.5, Red Ryder 9.4, Excel 1.00), but it behaves strangely under Multifinder. The function TopMem() returns strange results: it appears that I have 179k of RAM instead of 1024k. I suspect that MultiFinder is playing games with the low memory global TopMem.

Vital Signs appears to work okay with Switcher though!

I am guilty of violating one of the unwritten rules of DAs: thy DA shalt not hog the applications’s heap zone. My MaxApplZone() call from inside main() may make some applications unhappy, but so far I have been pretty lucky. I had to call it because I wanted my FreeMem() call to tell me all the RAM the application had available to it, not just how much was in its (not necessarily maximum) heap zone.

Good reference sources

If you are just starting out programming in LightspeedC™ and/or are not familiar with the Mac User Interface Toolbox, then you might want to start by reading the Technical Introduction to the Macintosh Family and/or the Programmers Introduction to the Macintosh Family. Both of these are hot off the (Addison-Wesley) press, and both are official Apple publications.

A very good introductory text for C programming on the Mac is Using the Macintosh Toolbox with C, by Takatsuka, Huxham, and Burnard. It uses Consulair Mac C instead of LightspeedC™, but it isn’t too hard to translate between the two dialects.

I would recommend staying away from Scott Knaster’s How to Write Macintosh Software until you have become familiar with the Macintosh Toolbox. This is not a book for beginners.

It would also be worth your while to look at a lot of back issues of MacTutor. Bob Gordon wrote a series of articles aimed at getting beginners started programming the Mac with LightspeedC™; see the June, July, August, October, November, and December ’86 issues. 1987 was a really good year for LightspeedC™ examples; there was one in just about every issue. I hope MacTutor keeps up this trend!

Some really good DA articles can be found in the March and June ’86 issues of MacTutor. Oddly enough, I wasn’t able to glean too much useful material from the April ’86 MacTutor, which was dedicated to DAs.

Other good sources of information about DAs are Dan Weston’s “The Complete Book of Assemby Language, Vol. 1”, and the Desk Manager and Device Manager chapters of “Inside Macintosh”.

/*
* Vital Signs, version 1.0. 
*
* A “do everything” desk accessory, written
* for MacTutor magazine by Ted C. Johnson.
* 
* Feel free to use this code for your own
* desk accessories.
*
* Created with Lightspeed C, version 2.15.
*
* Last modification:  Wed, March 9, 1988.
*
* Copyright 1988 by Ted C. Johnson.
* All Rights Reserved.
*/ 
 
#include <MacTypes.h>
#include <DeskMgr.h>
#include <DeviceMgr.h>
#include <DialogMgr.h>
#include <EventMgr.h>
#include <MemoryMgr.h>
#include <MenuMgr.h>
#include <FontMgr.h>
#include <FileMgr.h>
#include <OSUtil.h>
#include <TextEdit.h>
#include <QuickDraw.h>
#include <WindowMgr.h>
#include <ToolboxUtil.h> 
#include <IntlPkg.h>
#include <HFS.h>
#include <pascal.h>

#define NIL  0L   

#define YES    1
#define NO    0

#define FSFCBLen   0x03F6 

/*
*These are the five different message types that
*can be sent to a desk accessory.  
*/

#define drvrOpen    0 
#define drvrPrime  1 
#define drvrControl2
#define drvrStatus  3
#define drvrClose  4

/*These constants used to decide which PICT to draw.*/

#define MAC_PLUS    1
#define BIG_HEART  2
#define SMALL_HEART3

/*
*The following defines are for the “base id”’s of our
*DA’s owned resources.  
*
*Lightspeed C sets our DRVR ID to 12, so the owned
*resources for this DA start at -16000.  Therefore
*our base ids should start at 0.  However, for the
*sake of clearity, I start with a base id of 1 
*(defining something to be “1” just SEEMS t invite
*less speculation and be less misleading than defining
*it to be “0”).  Therefore I use an owned resource ID
*of -15999 in the resource file “Vital Signs proj.rsrc”.
*/

/*The ‘MENU’ resource’s base id.*/
#define OUR_MENU_ID    1  

/*The ‘DLOG’ and ‘DITL’ resources’ base ids.*/
#define ABOUT_BOX_DIALOG_ID 1 

/*The four ‘PICT’ resources’ base ids.*/
#define ABOUT_BOX_PICT_ID   1
#define MAC_PLUS_PICT_ID     2
#define BIG_HEART_PICT_ID   3
#define SMALL_HEART_PICT_ID 4 

/*
*Declare our global variables.
*/
MenuHandle   ourMenu;
BitMap       ScreenInfoBitMap;
Str255       OLDDate, OLDDefaultDisk;
Boolean      UseAnimationFlag, UseSoundFlag;
unsigned long  OLDFreeSpaceOnDisk, OLDNumFiles,
                OLDNumFolders;
long          OLDFreeRAM, OLDLargestBlock;
int      ourDRVR_ID, AlreadyOpen = NO;
int            HeartBeatTickCounter, 
                MemoryUpdateTickCounter;

/*
*This is where all of the calls to our DA get routed to
*the proper subroutines.  
*        *ipPB -- a pointer to our I/O parameter block.
*   theDCE -- our DA’s device control entry.
*   n ------ our DA’s entry point selector.  
*                  It’s value is used to indicate which
*                  of the 5 possible entry points (open, 
*                  prime, control, status, or close) was 
*                  called.
*/

main(ioPB, theDCE, n)
cntrlParam*ioPB; 
DCtlPtr   theDCE;
int    n; 
{
SysEnvRec EnvironmentInfo;
OSErr     SysEnvironsErrorMsg;
int        *hasHFS;
Boolean hasNewROMs, SysEnvironsMissing;

/*
*Check for new ROMs.
*This subroutine was gleaned from I.M. IV-97.  The
*global (low memory) system variable FSFCBLen, located
*at address 0x3F6, is positive if the hierarchical *version (HFS, Hierarchical 
File System) of the File
*Manager is active, and -1 if the 64k ROM version (MFS,
*the Macintosh File System) version of the File Manager *is running. 
 See I.M. IV-309 to see that FSFCBLen is *indeed located at 0x3F6.
*/
 hasHFS = (int *)FSFCBLen;
 if (*hasHFS > 0) {
 hasNewROMs = TRUE;
 }
 else {
 hasNewROMs = FALSE;
 }
 
 /*
 *Make sure the SysEnvirons() trap is present.
 */
 
 SysEnvironsErrorMsg = SysEnvirons(1, &EnvironmentInfo);
 
 if (SysEnvironsErrorMsg == envNotPresent) {
 SysEnvironsMissing = TRUE;
 }
 else {
 SysEnvironsMissing = FALSE;
 }
 
 /*
 *Make sure that the data area for our global variables 
  *was actually allocated for us.  Give up (without  
  *putting up an error message) if they weren’t.  
  *Allocation of our dynamically allocated data
  *succeeded if dCtlStorage != 0.
 */
 
 if ((theDCE->dCtlStorage == 0) && (n == drvrOpen)) {
 /*
 *If we got an “open” call, but we didn’t get any
   *data area, then beep and close the DA.
 */
 
 SysBeep(3);
 CloseDriver(theDCE->dCtlRefNum);
 return(0);
 }
 else if ((hasNewROMs == FALSE) 
         || (SysEnvironsMissing == TRUE)){
 /*We don’t have HFS, so can’t run this DA, because
    *it calls some HFS routines (to display the number
    *of files and folders on the default volume.  Also
    *exit if the SysEnvirons() trap is missing.
  */  
  
 SysBeep(3);
 CloseDriver(theDCE->dCtlRefNum);
 return(0);
 }
 
 /*
 *Inside Macintosh II-188 explains the meaning of the
  *various flags in the drvrFlags field of the device
  *control entry.  We are setting the dNeedLock flag
  *because want our DA to be locked in memory as soon as 
  *it is opened.  We set the dNeedTime flag because we
 *want our DA to be notified so that it can perform a
 *periodic action.  The dNeedGoodBye flag is set
  *because we want our DA to be called before the
  *application heap is reinitialized.
 */
 
 theDCE->dCtlFlags |= dNeedLock | dNeedTime |  \
                       dNeedGoodBye;
 theDCE->dCtlDelay = 1; /*Notify Vital Signs every 
                           1/60 of a second!*/
 
 switch(n) {
 
 case drvrOpen:  
 doOpen(ioPB, theDCE);
 break;
 
 case drvrPrime:
 doPrime(ioPB, theDCE);
 break;
 
 case drvrControl:
 doControl(ioPB, theDCE);
 break;
 
 case drvrStatus:
 doStatus(ioPB, theDCE);
 break;
 
 case drvrClose:
 doClose(ioPB, theDCE);
 break;
 }
 
}

/*
*The 5 driver subroutines that every DA must have.  
*Note that I DON’T do any sort of drawing or displaying
*of text (via DrawString(), etc.) inside doOpen().  Why?  *Because it 
doesn’t show up!  I’m not sure why .  
*Instead, I draw “static” text and pictures inside 
*doEvent() (inside doControl()) in response to an
*activateEvt event.
*/

doOpen(ioPB, theDCE)
cntrlParam  *ioPB; 
DCtlPtr    theDCE; 
{
WindowPtr  ourWindow;
GrafPtr    oldPort;
Rect        windowRect;
OSErr       MemoryErrorMessage;

 if (AlreadyOpen == YES) {
 return;
 }
 else {
 AlreadyOpen = YES;
 } 

 SetRect(&windowRect, 20, 45, 270, 205);

 /*
 *This is a bit tricky.  To compute the DRVR ID of a
  *DA, you negate the dCtlRefNum, and then subtract 1.
  *The DRVR ID number *is not* simply the value of
  *”theDCE->dCtlRefNum”!!!  See Inside Macintosh II-191.
 */
 
 ourDRVR_ID = -(theDCE->dCtlRefNum) - 1;
 
 ourWindow = theDCE->dCtlWindow;
 
 MaxApplZone();
 MemoryErrorMessage = MemError();
 
 /*
 *If our DA doesn’t already have a window, then it 
  *hasn’t been opened yet.  Make a window, initialize
  *the global variables, and install
 *our menu.
 */
 
    if ((ourWindow == NIL) 
       && (MemoryErrorMessage == noErr)) { 
 GetPort(&oldPort);
 
 /*
 *Initialize the global variable ScreenInfoBitMap.
   *We will use it later (inside
   *ShowMachineAndSystemInfo()) to determine 
 *the resolution of the screen.  We can’t use the
   *QuickDraw global variable “screenBits” because DAs 
   *don’t have access to QuickDraw global variables!
 */
 
 ScreenInfoBitMap = (*oldPort).portBits;
 
      ourWindow = NewWindow(0, &windowRect, “\pVital Signs”, 0, 
                      rDocProc, -1, 1, 0);
 SetPort(ourWindow);
 
      ((WindowPeek)ourWindow)->windowKind = 
                      theDCE->dCtlRefNum;
 theDCE->dCtlWindow = ourWindow;
 
 /*
 *Initialize the global variables.
 */
 
 PascalStrCpy(“\p”, OLDDate);
 HeartBeatTickCounter = 0;
 MemoryUpdateTickCounter = 0;
 UseAnimationFlag = YES;
 UseSoundFlag = NO;
 OLDFreeRAM = 0;
 OLDLargestBlock = 0;
 OLDFreeSpaceOnDisk = 0;
 OLDNumFiles = 0;
 OLDNumFolders = 0;

 /*
 *Create the menu and append it to the menu bar.
   *Check the “Use animated heart” menu item, and
   *uncheck the “Use sound” menu item.
 */
 
 ourMenu = GetMenu(CalcTheOwnedRsrcID(ourDRVR_ID, 
                                        OUR_MENU_ID));   
 theDCE->dCtlMenu = CalcTheOwnedRsrcID(ourDRVR_ID,
                                        OUR_MENU_ID);
 
 if (ourMenu != NIL) {
 InsertMenu(ourMenu, 0);
 CheckItem(ourMenu, 2, YES);
 CheckItem(ourMenu, 3, NO);
 }
 
 SetPort(oldPort);
 }
 else if ((ourWindow == NIL) 
          && (MemoryErrorMessage != noErr)) {
 CloseDriver(theDCE->dCtlRefNum);
 SysBeep(3);
 }
 
   return(0);
}
 
doControl(ioPB, theDCE)
cntrlParam  *ioPB; 
DCtlPtr    theDCE; 
{
GrafPtr    oldPort;
WindowPtr  ourWindow;
long        markChar;
int          MenuItemNumber;

 ourWindow = theDCE->dCtlWindow;
 
 if (validWindow(ourWindow) == YES) {
 
 /*
   *Save the current GrafPort so that we can restore
   *it later.
   */
 
 GetPort(&oldPort);
 SetPort(ourWindow);
 
 /*
 *HERE is where you handle events, but this 
   *subroutine is NOT in a loop!  Pretty wierd, no?
   *There is no GetNextEvent() or WaitNextEvent() 
   *in any DA!
 */
 
 switch(ioPB->csCode) {
 
 /*
 *Here is where we handle all of our Desk Accessory events.
 */
 
 case accEvent:
 doEvent(((EventRecord *) * (long *)   
                          &ioPB->csParam), 
     ourWindow);
 break;
 
 /*
 *The accRun case is  where we actually USE the 
     *time that we asked the Finder to give us.
 */ 
 
 case accRun:
 if (FrontWindow() == ourWindow) {
 doPeriodicStuff(theDCE);
 }
 break;
 
 case accCursor:
 break;
 
 case accMenu:
 MenuItemNumber = ioPB->csParam[1];
 
 /*
 *We handle our menu items here.
 */
 
 switch(MenuItemNumber) { 
 
 case 1:
 doAboutBox(ourWindow);
 break;
 
 case 2:
 GetItemMark(ourMenu, MenuItemNumber,
                    &markChar);
 
 if (UseAnimationFlag == YES) {
 UseAnimationFlag = NO;
 CheckItem(ourMenu, MenuItemNumber, NO);
 DisableItem(ourMenu, 3);/*Disable the sound
                 menu item.*/
 }
 else {
 UseAnimationFlag = YES;
 CheckItem(ourMenu, MenuItemNumber, YES);
 EnableItem(ourMenu, 3);/*Enable the sound 
                 menu item.*/
 }
 break;
 
 case 3:
 GetItemMark(ourMenu, MenuItemNumber,
                               &markChar);
 
 if (UseSoundFlag == YES) {
 UseSoundFlag = NO;
 CheckItem(ourMenu, MenuItemNumber, NO);
 }
 else {
 UseSoundFlag = YES;
 CheckItem(ourMenu, MenuItemNumber, YES);
 }
 break;
 
 case 4:
 ShowMemoryInfo(YES);
 break;

 default:
 break;
 } 
 
 HiliteMenu(0);
 break;
 
 case goodBye: /*We asked for a “goodbye kiss”.*/
 doGoodBye();
 break;
 
 case accUndo:
 SysBeep(2);
 break; 
 
 case accCut:
 SysBeep(2); 
 break; 
 
 case accCopy:
 SysBeep(2); 
 break; 
 
 case accPaste:
 SysBeep(2); 
 break; 
 
 case accClear:
 SysBeep(2);
 break; 
 } /*switch(ioPB->csCode)*/
 
 SetPort(oldPort);
 
 }/*if (validWindow(ourWindow) == YES))*/
 
 return(0);
}

/*
*Usually DA’s don’t respond to status or prime calls;
*only “real” device drivers do that.
*/

doStatus(ioPB,dce)
cntrlParam  *ioPB; 
DCtlPtr    dce;  
{
 return(0);
}
 
doPrime(ioPB, dce)
cntrlParam  *ioPB; 
DCtlPtr    dce;  
{
 return(0);
}
 
doClose(ioPB,dce)
cntrlParam  *ioPB; 
DCtlPtr    dce;  
{
WindowPtr ourWindow;
   
   ourWindow = dce->dCtlWindow;
   
 if (validWindow(ourWindow) == YES) {
      DeleteMenu(CalcTheOwnedRsrcID(ourDRVR_ID,
                                OUR_MENU_ID));
      dce->dCtlMenu = NIL;
        DrawMenuBar();
        DisposeWindow(ourWindow);
        dce->dCtlWindow = NIL;
 }
 
 return(0);
}

doEvent(theEvent, ourWindow)
EventRecord *theEvent;
WindowPtr  ourWindow;
{
 switch(theEvent->what) {
 
 case keyDown:
 case autoKey:
 if ((theEvent->modifiers & cmdKey) == YES) {
 switch((char)(theEvent->message)) {
 
 /*We don’t do anything with the Edit menu.*/
 
 case ‘c’:
 case ‘C’:
 case ‘v’:
 case ‘V’:
 case ‘x’:
 case ‘X’:
 SysBeep(2);
 break;
 
 default:
 SysBeep(2);
 break;
 }/*switch*/
 }/*if*/
 
 case mouseDown:
 GlobalToLocal(&theEvent->where);
 break;
 /*
 *Update events occur when all or part of a window’s 
     *contents need to be drawn or redrawn, usually as a 
     *result of a user’s opening, closing, activating,
     *or moving a window.  See Inside Macintosh I-244.
 */
 
 case updateEvt:
 BeginUpdate(ourWindow);
 
 DrawVitalSignsBox();
 ShowMachineAndSystemInfo();
 ShowMemoryInfo(YES);
 ShowDefaultDiskInfo(YES);
 ShowTimeAndDate(YES);
 DrawOurPicture(MAC_PLUS);

 EndUpdate(ourWindow);
 break;
 
 /*
 *Activate events are generated whenever an inactive 
     *window becomes active or an active window becomes
     *inactive.  They generally occur in pairs (that is, 
     *one window is deactivated and then another is
     *activated.  See Inside Macintosh I-244.  
 */
 
 case activateEvt:
 if ((theEvent->modifiers & activeFlag) == YES) {
 
 /*Add our menu title to the menu bar.*/
 
 InsertMenu(ourMenu, 0);  
 DrawMenuBar();
 
 TextFont(geneva);
 TextSize(9);
 
 DrawVitalSignsBox();
 ShowMachineAndSystemInfo();
 ShowMemoryInfo(NO);
 ShowDefaultDiskInfo(NO);
 ShowTimeAndDate(NO);
 DrawOurPicture(MAC_PLUS);
 }
 else {
   DeleteMenu(CalcTheOwnedRsrcID(ourDRVR_ID,OUR_MENU_ID));
   DrawMenuBar();
 }
 
 break;
 
 /*We just ignore the other event types.*/
 
 case diskEvt:
 case networkEvt:
 case driverEvt:
 case app1Evt:
 case app2Evt:
 case app3Evt:
 case app4Evt:
 
 default:
 break;
 
 }/*switch(theEvent->what)*/
}
 
ShowMachineAndSystemInfo()
{
SysEnvRec  Environment;
StringHandle   FinderVersionNum;
StringHandle   SystemVersionNum;
Handle    tempHandle;
OSErr      errCode;
char         istring[100];
int          SystemInteger, SystemFraction;
int          temp1, temp2;
 
 /*
 *See Technical Note #129 or Inside Macintosh V for
 *info. about SysEnvirons().  It is just used to get
 *information about the hardware in the Mac we are
 *running on.  I don’t need to check the errCode 
 *because I already did when I called SysEnvirons() 
 *in main().
 */
 
 errCode = SysEnvirons(1, &Environment);
 
 MoveTo(10, 12);
 DrawString(“\pMacintosh model:  “);
   
 TextFace(bold);/*Write the Mac model type in 
                   boldface font.*/
 
 switch(Environment.machineType) {
 
 case envXL:
 DrawString(“\pXL (Lisa)”);
 break;
 
 case envMac:
 DrawString(“\pMac”);
 break;
 
 case envMachUnknown:
 DrawString(“\punknown”);
 break;
 
 case env512KE:
 DrawString(“\p512K Enhanced”);
 break;
 
 case envMacPlus:
 DrawString(“\pPlus”);
 break;
 
 case envSE:
 DrawString(“\pSE”);
 break;
 
 case envMacII:
 DrawString(“\pII”);
 break;
 }
 
 TextFace(0);/*Restore to plain font.*/
 
 /*
 *The “systemVersion” field of “Environment” is an int
  *which is really two numbers (one in each of the 2 
  *bytes).  The high byte is the part to the left of the 
  *decimal, and the low byte is the fractional part.
  *The decimal point is implied.  See Technical 
  *Note #129, p.3.
 */
 
 temp1 = temp2 = Environment.systemVersion;
 
 SystemInteger = temp1 >> 8;
 SystemFraction = temp2 && 0x100;
 
 MoveTo(10, 22);
 DrawString(“\pSystem Version:  “);
 NumToString(SystemInteger, istring);
 DrawString(istring);
 DrawString(“\p.”);/*Draw the decimal point.*/
 NumToString(SystemFraction, istring);
 DrawString(istring);
    
 MoveTo(10, 32);
 DrawString(“\pScreen size (pixels): “);
 NumToString(ScreenInfoBitMap.bounds.right, istring);
 DrawString(istring);
 DrawString(“\p x “);
 NumToString(ScreenInfoBitMap.bounds.bottom, istring);
 DrawString(istring);
}

ShowMemoryInfo(anUpdateEvent)
Boolean    anUpdateEvent;
{
PolyHandle  thePolygon;
Ptr          myPatternPtr;
Rect        RAMRect, AreaToShade;
Rect        largestblockBox,freeRAMBox, RAMpercentBox;
double     doublePercentUsed;
long        totalRAM, freeRAM, 
            LargestContiguousBlockOfRAM;
int          intPercentUsed, delta, base;
char        istring[100]; /*Use this string to hold 
                            integers.*/

 /*Gather all the data.*/
 
 totalRAM = (long)TopMem()/1024;
 /*MaxApplZone(); just do this once, in doOpen()*/
 freeRAM = FreeMem();
 freeRAM /= 1024; /*Change it from units of bytes to
                     units of kbytes.*/
 
 /*
 *Ask for 8 Meg of RAM to be allocated.  CompactMem() 
  *returns the size (in bytes) of the largest block it
  *is able to allocate.
 */
 
 LargestContiguousBlockOfRAM = 
                      CompactMem(0x800000)/1024;
 
 /*
 *If this is an update or activate event, the redraw 
  *the whole memory display.  Else, just draw it if 
  *it has changed from the last time we calculated
  *freeRAM and LargestContiguousBlockOfRAM.
 */
 
 if (  (anUpdateEvent == YES) 
     ||((OLDFreeRAM != freeRAM) 
   || (OLDLargestBlock != LargestContiguousBlockOfRAM)))
 {
 /*Update our  state variables.*/
 OLDFreeRAM = freeRAM;
 OLDLargestBlock = LargestContiguousBlockOfRAM;
 
 /*Erase old displays.*/
 SetRect(&largestblockBox, 75, 60, 155, 70);
 SetRect(&freeRAMBox, 61, 50, 155, 60);
 SetRect(&RAMpercentBox, 175, 12, 202, 34);
 EraseRect(&largestblockBox);
 EraseRect(&freeRAMBox);
  EraseRect(&RAMpercentBox);
   
 NumToString(totalRAM, istring); /*Draw the Total 
                                     RAM display.*/
 MoveTo(10, 50);
 DrawString(“\pTotal RAM: “);
 DrawString(istring);
 DrawString(“\pk”);
 
 NumToString(freeRAM, istring); /*Draw the Free 
                                    RAM display.*/
 MoveTo(10, 60);
 DrawString(“\pFree RAM:    “);
 DrawString(istring);
 DrawString(“\pk”);
 
 NumToString(LargestContiguousBlockOfRAM, istring);
 MoveTo(10, 70);
 DrawString(“\pLargest block: “);
 DrawString(istring);
 DrawString(“\pk”);
 
 /*Draw RAM bar graph*/
 MoveTo(175, 10);
 DrawString(“\pUsage of:”);
 TextFace(bold);
 MoveTo(175, 20);
 DrawString(“\pRAM”);
 TextFace(0);
 
 /*
 *Calculate the percent of RAM space used, via 
   *the formula:
 *
 * % RAM used = used RAM/total RAM
 *            = (total RAM - free RAM)/total RAM
  */
 
  doublePercentUsed = ((double)totalRAM  
                       - (double)freeRAM)
               /(double)totalRAM;

 /*Truncate.*/
  intPercentUsed = (int)(doublePercentUsed * 100.0);
      
 NumToString(intPercentUsed, istring);/*Display the 
                                          new percent.*/
 MoveTo(175, 30);
 DrawString(istring);
 DrawString(“\p%”);

  /*The dimensions of the RAM bar graph.*/
 SetRect(&RAMRect, 175, 35, 202, 155);
 CalcAndFillBarGraph(doublePercentUsed, &RAMRect);
 }/*if*/
}

ShowDefaultDiskInfo(anUpdateEvent)
Boolean     anUpdateEvent;
{
HVolumeParam    vp;
Rect          DiskRect;
Rect          filesBox, foldersBox, freespaceBox;
Rect           capacityBox, nameofdiskBox;
double      doublePercentUsed;
StringPtr     diskname;
Str255      vname, dname, DefaultDiskName;
unsigned long  FreeSpaceOnDisk;
unsigned long  TotalSpaceOnDisk;
int            intPercentUsed;
char          istring[100];

 /*Gather the data.*/
 
 vp.ioVolIndex = 1;
 vp.ioNamePtr = diskname;
 vp.ioCompletion = 0L;
 
 PBHGetVInfo(&vp, 0);
 
 TotalSpaceOnDisk = (unsigned int)vp.ioVNmAlBlks * 
 (unsigned long)vp.ioVAlBlkSiz;
 
 /*Convert units from bytes to kbytes.*/ 
 TotalSpaceOnDisk = TotalSpaceOnDisk/1024;
 
 FreeSpaceOnDisk = (unsigned long)vp.ioVFrBlk 
                  * vp.ioVAlBlkSiz;

 /*Convert units from bytes to kbytes.*/ 
 FreeSpaceOnDisk = FreeSpaceOnDisk/1024; 
 
 /*
 *Calculate the percent of disk space used, via 
  *the formula:
 *
 * % disk used = used disk space/total disk space
 *             = (total disk space - free disk       
  *                space)/total disk space
 */
 
 doublePercentUsed = ((double)TotalSpaceOnDisk  
                      - (double)FreeSpaceOnDisk)
                /(double)TotalSpaceOnDisk;

 /*truncate.  Oh well.*/
 intPercentUsed = (int)(doublePercentUsed * 100.0);
 
 if ((anUpdateEvent == YES)  ||
 (OLDNumFiles != vp.ioVFilCnt) ||
 (OLDNumFolders != vp.ioVDirCnt) ||
 (OLDFreeSpaceOnDisk != FreeSpaceOnDisk)) {
 
 /*Update state variables.*/
 OLDNumFiles = vp.ioVFilCnt;
 OLDNumFolders = vp.ioVDirCnt;
 OLDFreeSpaceOnDisk = FreeSpaceOnDisk;
 
 /*Erase old displays.*/
 SetRect(&filesBox, 84, 106, 120, 118);
 SetRect(&foldersBox, 87, 118, 125, 127);
 SetRect(&freespaceBox, 61, 96, 125, 106);
 SetRect(&capacityBox, 53, 87, 125, 96);
 SetRect(&nameofdiskBox, 70, 77, 155, 87);
 EraseRect(&filesBox);
 EraseRect(&foldersBox);
 EraseRect(&freespaceBox);
 EraseRect(&capacityBox);
 EraseRect(&nameofdiskBox);
 
 NumToString(TotalSpaceOnDisk, istring);
 
 MoveTo(10, 85);
 DrawString(“\pDefault volume: “);
 DrawString(*(vp.ioNamePtr));
 MoveTo(10, 95);
 DrawString(“\pCapacity: “);
 DrawString(istring);
 DrawString(“\pk”);
 
 NumToString(FreeSpaceOnDisk, istring);
 MoveTo(10, 105);
 DrawString(“\pFree space: “);
 DrawString(istring);
 DrawString(“\pk”);
 
 MoveTo(10, 115);
 DrawString(“\pNumber of files: “);
 NumToString(vp.ioVFilCnt, istring);
 DrawString(istring);

 MoveTo(10, 125);
 DrawString(“\pNumber of folders: “);
 NumToString(vp.ioVDirCnt, istring);
 DrawString(istring);
 
 MoveTo(215, 20);
 TextFace(bold);
 DrawString(“\pDisk”);
 TextFace(0);
 
 NumToString(intPercentUsed, istring);
 MoveTo(215, 30);
 DrawString(istring);
 DrawString(“\p%”);
 
 SetRect(&DiskRect, 218, 35, 243, 155);
 CalcAndFillBarGraph(doublePercentUsed, &DiskRect);
 }
}

ShowTimeAndDate(anUpdateEvent)
Boolean  anUpdateEvent;
{
Rect    timeBox, dateBox;
Str255  AbbreviatedDate, HoursMinsAndSecs;
long    SecondsSince1904;
 
 /*Get the data.*/
 
 GetDateTime(&SecondsSince1904);
 IUTimeString(SecondsSince1904, YES, &HoursMinsAndSecs);
 IUDateString(SecondsSince1904, abbrevDate,&AbbreviatedDate);

 /*Erase the old time display, and show the new time.*/
 
 SetRect(&timeBox, 35, 142, 118, 150);
 EraseRect(&timeBox);
 MoveTo(10, 150);
 DrawString(“\pTime: “);
 DrawString(HoursMinsAndSecs);
 
 /*
 *ONLY if the date has changed or if this is an update
 *event, erase the old date display and draw the new
 *date.
 */
 if ((anUpdateEvent == YES) ||
     (EqualString(OLDDate, AbbreviatedDate, YES, YES) != YES)) {
     
 PascalStrCpy(AbbreviatedDate, OLDDate);
 SetRect(&dateBox, 36, 132, 120, 143);
 EraseRect(&dateBox);
 MoveTo(10, 140);
 DrawString(“\pDate: “);
 DrawString(AbbreviatedDate); 
 }
}

doPeriodicStuff(theDCE)
DCtlPtr    theDCE;
{
WindowPeek  GuessWhichWindow;
Str255    HoursMinsAndSecs, AbbreviatedDate;
long        SecondsSince1904;

 GuessWhichWindow = (WindowPeek)theDCE->dCtlWindow;
 
 SetPort(GuessWhichWindow);
 
 HeartBeatTickCounter += 1;
 MemoryUpdateTickCounter += 1;
 
 /*
   *Start a new heart beat cycle every 5/6 
   *of a second.
   */
 
 if (HeartBeatTickCounter == 50) {
 HeartBeatTickCounter = 0;
 }
 
 /*
   *Update the RAM and default disk display once 
   *every 10 seconds.
   */
 if (MemoryUpdateTickCounter == 360) {
 ShowMemoryInfo(NO);
 ShowDefaultDiskInfo(NO);
 MemoryUpdateTickCounter = 0;
 }
 
   /*
    *Don’t play the beep sound unless the heart 
    *is also animated.
    */
 switch(HeartBeatTickCounter) {
 case 1:
 if (UseAnimationFlag == YES) {
 DrawOurPicture(BIG_HEART);
 
 if (UseSoundFlag == YES) {
 SysBeep(1);
 }
 }
 break;
 
 case 45:
 if (UseAnimationFlag == YES) {
 DrawOurPicture(SMALL_HEART);
 
 if (UseSoundFlag == YES) {
 SysBeep(1);
 }
 }
 break;
 
 case 49:/*Update the time display every 5/6 
             of a second.*/
 ShowTimeAndDate(NO);
 break;
 
 default:
 break;
 
 }/*switch()*/
}

/*
*doAboutBox() puts up the About Vital Signs  box.  It 
*is displayed until the user hits the mouse button.
*/
doAboutBox(ourWindow)
WindowPtr ourWindow;
{
DialogPtr theDialog;
PicHandle thePicture;
Handle    DITLHandle, DLOGHandle;
Rect        PictureFrameRect;
int         OwnedRsrcID;

 /*
 *I.M. I-413 says:  If either the dialog template
  *resource (DLOG) or the dialog item list resource
  *(DITL) can’t be read, the function return result 
  *for GetNewDialog() is undefined!!!  Very bad news!!! 
  *Use Scott Knaster’s tip (p298 of “How to Write 
  *Macintosh Software”) to do the error checking so that 
  *we won’t bomb due to a missing resource file.
 *
 *Use GetResource() to get the DITL and DLOG that make
  *up this dialog, and THEN call GetNewDialog().  If 
  *either of the GetResource() calls returns a NIL 
  *value, then that resource file is missing, so DON’T 
  *do the GetNewDialog().  Just beep (to signal an 
  *error), and exit.
 */
 DITLHandle = 
     GetResource(‘DITL’,CalcTheOwnedRsrcID(ourDRVR_ID, 
             ABOUT_BOX_DIALOG_ID));
 DLOGHandle = 
       GetResource(‘DLOG’,CalcTheOwnedRsrcID(ourDRVR_ID, 
                 ABOUT_BOX_DIALOG_ID));
 
 if ((DITLHandle == NIL) || (DLOGHandle == NIL)) {
 SysBeep(3);/*Yikes!  Missing resource file! 
                Bail out!*/
 return;
 } 
 else { /*It’s safe to do the GetNewDialog() call.*/
 
 theDialog = 
       GetNewDialog(CalcTheOwnedRsrcID(ourDRVR_ID, 
                ABOUT_BOX_DIALOG_ID), 
                 NIL, -1);
 SetPort(theDialog);
 
 OwnedRsrcID = CalcTheOwnedRsrcID(ourDRVR_ID, 
                                  ABOUT_BOX_PICT_ID);
 thePicture = GetPicture(OwnedRsrcID);
 
 if (thePicture != NIL) {
 MoveHHi(thePicture);
 HLock(thePicture);
 
 /*
 *Get these numbers from the Rect dimensions 
 *you see in ResEdit when you Open the PICT.
 */
 SetRect(&PictureFrameRect, 0, 0, 289, 130);

 DrawDialog(theDialog);
 DrawPicture(thePicture, &PictureFrameRect);

   do { 
        ;
        } while (!Button());
    
        HUnlock(thePicture);
 
 CloseDialog(theDialog);
 SetPort(ourWindow);
 }
 }
}

doGoodBye(ourWindow)
{
 ;
}

/*
*Utility subroutines.
*/
CalcAndFillBarGraph(percentUsed, theRect)
double    percentUsed;
Rect        *theRect;
{
Rect         AreaToShade;
PolyHandle  thePolygon;
Ptr          myPatternPtr;
int          delta, base;
 
 myPatternPtr = NewPtr(sizeof(Pattern));

 /*Just use a solid black pattern.*/
 StuffHex(myPatternPtr, “\pFFFFFFFFFFFFFFFF”);

 EraseRect(&(*theRect));
 FrameRect(&(*theRect));
 
 base = theRect->top;
 
 delta = (((int)(   (double)(theRect->bottom 
                            - theRect->top)  
      * (1.0 - percentUsed))));
      
 SetRect(&AreaToShade, 
 theRect->left,
 base + delta,
 theRect->right,
 theRect->bottom);
 
 /*
 *OpenPoly() returns a handle to a new polygon.  All
 *the following QuickDraw calls define a polygon.  
 *ClosePoly() tells QuickDraw when we are done defining
 *it.  We then fill it with a pattern via FillPoly(), 
 *and then call KillPoly() to release the memory 
 *occupied by our PolyHandle.
 */
 thePolygon = OpenPoly();
 MoveTo(AreaToShade.left, AreaToShade.top);
 LineTo(AreaToShade.right, AreaToShade.top);
 LineTo(AreaToShade.right, AreaToShade.bottom);
 LineTo(AreaToShade.left, AreaToShade.bottom);
 ClosePoly();
 
 FillPoly(thePolygon, myPatternPtr);
 
 KillPoly(thePolygon);
}

CalcTheOwnedRsrcID(DRVR_ID, base_ID)
int DRVR_ID, base_ID;
{
int OwnedResourceNumber;

 OwnedResourceNumber = (0xC000 + (DRVR_ID << 5)) + base_ID;
 
 return(OwnedResourceNumber);
}

validWindow(ourWindow)
WindowPtr ourWindow;
{
WindowPtr nextWindow;
  
 if (ourWindow != NIL) {
      nextWindow = FrontWindow();
    }
    
 while (nextWindow != NIL) {
 if (ourWindow == nextWindow) {
 return(YES);
 }
     else {
      nextWindow = 
      (WindowPtr)(((WindowPeek)nextWindow)->nextWindow);
     }
 }

    return(NO);
}

/*
*Frame the Vital Signs box, and draw the three
*horizontal crossbars.
*/
DrawVitalSignsBox()
{
Rect VitalSignsBox;
 
 SetRect(&VitalSignsBox, 5, 3, 170, 155);
 FrameRect(&VitalSignsBox);
 
 MoveTo(5, 35);
 LineTo(169, 35);
 
 MoveTo(5, 73);
 LineTo(169, 73);
 
 MoveTo(5, 127);
 LineTo(169, 127);
}
 
/*
*Draw one of three PICT’s, in the DA’s window.
*/
DrawOurPicture(whichPicture)
int         whichPicture;
{
PicHandle OurPicture;
Rect      OurPicturesRect, toEraseBigHeart;
int         OurPicturesOwnedRsrcID;

 switch (whichPicture) {
 
 case MAC_PLUS:
 OurPicturesOwnedRsrcID =
         CalcTheOwnedRsrcID(ourDRVR_ID,
          MAC_PLUS_PICT_ID);
 /*First, make the Rect the right size.*/
 SetRect(&OurPicturesRect, 0, 0, 50, 57);
 
 /*Now offset it so it gets drawn where we want!*/
 OffsetRect(&OurPicturesRect, 124, 100);
 break;
 
 case BIG_HEART:
 OurPicturesOwnedRsrcID = 
                CalcTheOwnedRsrcID(ourDRVR_ID,
         BIG_HEART_PICT_ID);
 SetRect(&OurPicturesRect, 0, 0, 31, 23);
 OffsetRect(&OurPicturesRect, 133, 108);
 break;
 
 case SMALL_HEART:
 OurPicturesOwnedRsrcID =
                 CalcTheOwnedRsrcID(ourDRVR_ID,
          SMALL_HEART_PICT_ID);
 /*We have to erase the big heart first.*/
 SetRect(&toEraseBigHeart, 0, 0, 31, 23);
 OffsetRect(&toEraseBigHeart, 133, 108);
 EraseRect(&toEraseBigHeart);
 
 SetRect(&OurPicturesRect, 0, 0, 31, 15);
 OffsetRect(&OurPicturesRect, 133, 115);
 break; 
 
 }/*switch(whichPicture)*/
 
 OurPicture = GetPicture(OurPicturesOwnedRsrcID);
 
 if (OurPicture != NIL) {
 MoveHHi(OurPicture);
 HLock(OurPicture);
 DrawPicture(OurPicture, &OurPicturesRect);

 HUnlock(OurPicture);
 }
}

/*
*PascalStrCpy() copies a pascal string from p1 to p2.                
       
*/
PascalStrCpy(p1, p2)
register char *p1, *p2;
{
register int  len;
 
 len = *p2++ = *p1++;
 while (--len >= 0) {
 *p2++ = *p1++;
 }
}

resource  ‘DITL’ (-15999, preLoad)
{
  {
    {0, 0, 130, 289},
      Useritem {enabled}
  }
};

resource  ‘DLOG’ (-15999, preLoad)
{
    {88, 108, 218, 397},
    1,
    visible,
    noGoAway,
    0x0,
    -15999,
    “  “
};

resource  ‘MENU’ (-15999, preLoad)
{
    -15999,
    0,
    0xffffffff,
    enabled,
    “Vital Signs”,
    {
    “About Vital Signs\0xc9”, 0, 0, 0, 0;
    “Use animated heart”, 0, 0, 0, 0;
    “Use sound”, 0, 0, 0, 0;
    “Recalculate RAM usage”, 0, 0, 0, 0
    }
};

resource  ‘PICT’ (-15999)/*About... box*/
{
    2181,
    {0, 0, 130, 289},
    {17; 1; 160; 48; 57; 1; 0; 10; 0; 0; 0; 0; 0
    ; 130; 1; 33; 152; 0; 38; 0; 178; 0; 128; 1; 52
    ; 1; 168; 0; 178; 0; 135; 1; 52; 1; 168; 0; 0
    ; 0; 0; 0; 130; 1; 33; 0; 0; 2; 219; 0; 2
    ; 219; 0; 2; 219; 0; 2; 219; 0; 2; 219; 0; 2
    ; 219; 0; 2; 219; 0; 6; 253; 0; 0; 252; 224; 0
    ; 10; 254; 0; 4; 3; 255; 0; 0; 255; 227; 0; 11
    ; 254; 0; 5; 7; 255; 128; 3; 255; 128; 228; 0; 24
    ; 254; 0; 10; 15; 255; 192; 7; 255; 192; 0; 7; 240
    ; 126; 56; 254; 0; 4; 48; 0; 7; 216; 56; 241; 0
    ; 24; 254; 0; 10; 31; 255; 224; 15; 255; 224; 0; 1
    ; 192; 24; 56; 254; 0; 4; 112; 0; 28; 120; 56; 241
    ; 0; 22; 254; 0; 9; 63; 255; 240; 31; 255; 240; 0
    ; 0; 224; 24; 253; 0; 3; 240; 0; 56; 56; 240; 0
    ; 23; 254; 0; 17; 127; 255; 248; 63; 255; 248; 0; 0
    ; 224; 48; 0; 24; 0; 0; 112; 0; 56; 24; 240; 0
    ; 22; 254; 0; 16; 127; 255; 248; 63; 255; 252; 0; 0
    ; 112; 48; 0; 56; 0; 0; 112; 0; 56; 239; 0; 29
    ; 254; 0; 23; 255; 255; 252; 127; 255; 254; 0; 0; 112
    ; 48; 24; 127; 3; 224; 112; 0; 28; 0; 24; 15; 248
    ; 55; 129; 252; 246; 0; 30; 5; 0; 0; 1; 255; 255
    ; 254; 254; 255; 17; 0; 0; 56; 96; 56; 56; 14; 112
    ; 112; 0; 31; 0; 56; 29; 224; 127; 195; 156; 246; 0
    ; 27; 2; 0; 0; 1; 251; 255; 17; 0; 0; 56; 96
    ; 248; 56; 30; 112; 112; 0; 7; 192; 248; 56; 224; 249
    ; 195; 140; 246; 0; 35; 2; 0; 0; 3; 251; 255; 17
    ; 128; 0; 28; 192; 56; 56; 28; 112; 112; 0; 1; 240
    ; 56; 56; 224; 113; 195; 192; 254; 0; 4; 4; 0; 0
    ; 128; 112; 254; 0; 33; 2; 0; 0; 3; 251; 255; 17
    ; 128; 0; 28; 192; 56; 56; 1; 240; 112; 0; 0; 120
    ; 56; 56; 224; 113; 193; 240; 252; 0; 2; 1; 128; 136
    ; 254; 0; 35; 2; 0; 0; 3; 251; 255; 25; 128; 0
    ; 14; 192; 56; 56; 7; 112; 112; 0; 0; 60; 56; 56
    ; 224; 113; 192; 248; 0; 221; 150; 236; 229; 128; 128; 136
    ; 254; 0; 35; 2; 0; 0; 7; 251; 255; 25; 192; 0
    ; 15; 128; 56; 56; 14; 112; 112; 0; 48; 28; 56; 29
    ; 192; 113; 192; 60; 0; 138; 73; 37; 18; 64; 128; 136
    ; 254; 0; 35; 2; 0; 0; 7; 251; 255; 25; 192; 0
    ; 7; 128; 56; 56; 28; 112; 112; 0; 48; 28; 56; 31
    ; 128; 113; 192; 28; 0; 83; 200; 197; 18; 64; 128; 136
    ; 254; 0; 35; 2; 0; 0; 7; 251; 255; 25; 192; 0
    ; 7; 128; 56; 57; 156; 240; 112; 0; 56; 28; 56; 56
    ; 0; 113; 195; 28; 0; 82; 8; 37; 18; 64; 128; 136
    ; 254; 0; 35; 2; 0; 0; 15; 251; 255; 10; 224; 0
    ; 3; 0; 56; 63; 31; 252; 112; 0; 60; 254; 56; 11
    ; 0; 113; 195; 188; 0; 34; 73; 37; 18; 64; 128; 136
    ; 254; 0; 35; 2; 0; 0; 15; 251; 255; 25; 224; 0
    ; 3; 0; 254; 30; 15; 57; 252; 0; 55; 224; 254; 31
    ; 225; 255; 243; 248; 0; 33; 157; 206; 231; 97; 196; 112
    ; 254; 0; 15; 2; 0; 0; 15; 251; 255; 0; 224; 245
    ; 0; 1; 63; 240; 243; 0; 15; 2; 0; 0; 15; 251
    ; 255; 0; 224; 245; 0; 1; 96; 112; 243; 0; 15; 2
    ; 0; 0; 31; 251; 255; 0; 240; 245; 0; 1; 96; 112
    ; 243; 0; 15; 2; 0; 0; 15; 251; 255; 0; 224; 245
    ; 0; 1; 112; 96; 243; 0; 15; 2; 0; 0; 15; 251
    ; 255; 0; 224; 245; 0; 1; 127; 192; 243; 0; 15; 2
    ; 0; 0; 15; 251; 255; 0; 224; 245; 0; 1; 63; 128
    ; 243; 0; 10; 2; 0; 0; 15; 251; 255; 0; 224; 229
    ; 0; 10; 2; 0; 0; 15; 251; 255; 0; 224; 229; 0
    ; 10; 2; 0; 0; 7; 251; 255; 0; 192; 229; 0; 10
    ; 2; 0; 0; 7; 251; 255; 0; 192; 229; 0; 10; 2
    ; 0; 0; 3; 251; 255; 0; 128; 229; 0; 10; 2; 0
    ; 0; 3; 251; 255; 0; 128; 229; 0; 10; 2; 0; 0
    ; 3; 251; 255; 0; 128; 229; 0; 8; 2; 0; 0; 1
    ; 251; 255; 228; 0; 8; 2; 0; 0; 1; 251; 255; 228
    ; 0; 8; 254; 0; 252; 255; 0; 254; 228; 0; 10; 254
    ; 0; 0; 127; 253; 255; 0; 252; 228; 0; 10; 254; 0
    ; 0; 127; 253; 255; 0; 252; 228; 0; 10; 254; 0; 0
    ; 63; 253; 255; 0; 248; 228; 0; 10; 254; 0; 0; 31
    ; 253; 255; 0; 240; 228; 0; 10; 254; 0; 0; 15; 253
    ; 255; 0; 224; 228; 0; 10; 254; 0; 0; 7; 253; 255
    ; 0; 192; 228; 0; 8; 254; 0; 0; 1; 253; 255; 227
    ; 0; 8; 254; 0; 0; 1; 253; 255; 227; 0; 9; 253
    ; 0; 3; 127; 255; 255; 254; 227; 0; 9; 253; 0; 3
    ; 63; 255; 255; 252; 227; 0; 9; 253; 0; 3; 31; 255
    ; 255; 240; 227; 0; 9; 253; 0; 3; 15; 255; 255; 224
    ; 227; 0; 9; 253; 0; 3; 3; 255; 255; 128; 227; 0
    ; 8; 253; 0; 2; 1; 255; 255; 226; 0; 7; 252; 0
    ; 1; 255; 254; 226; 0; 7; 252; 0; 1; 63; 248; 226
    ; 0; 7; 252; 0; 1; 31; 240; 226; 0; 21; 252; 0
    ; 1; 7; 192; 254; 0; 3; 15; 240; 1; 248; 254; 0
    ; 3; 7; 192; 1; 198; 240; 0; 16; 247; 0; 3; 1
    ; 128; 1; 140; 254; 0; 3; 12; 96; 3; 6; 240; 0
    ; 27; 247; 0; 15; 1; 128; 1; 140; 120; 120; 252; 12
    ; 3; 199; 143; 25; 152; 241; 249; 224; 252; 0; 2; 14
    ; 127; 156; 253; 0; 27; 247; 0; 15; 1; 128; 1; 248
    ; 204; 204; 224; 12; 6; 99; 6; 25; 153; 153; 195; 48
    ; 252; 0; 2; 17; 128; 98; 253; 0; 27; 247; 0; 15
    ; 1; 128; 1; 140; 204; 124; 192; 7; 198; 99; 6; 15
    ; 240; 249; 131; 48; 252; 0; 2; 16; 0; 2; 253; 0
    ; 27; 247; 0; 15; 1; 128; 1; 140; 252; 204; 192; 0
    ; 102; 99; 6; 15; 241; 153; 131; 240; 252; 0; 2; 20
    ; 0; 10; 253; 0; 26; 247; 0; 14; 1; 128; 1; 140
    ; 192; 204; 192; 0; 102; 99; 6; 6; 97; 153; 131; 251
    ; 0; 2; 20; 0; 10; 253; 0; 27; 247; 0; 15; 1
    ; 128; 1; 140; 204; 204; 192; 12; 102; 99; 6; 6; 97
    ; 153; 131; 48; 252; 0; 2; 24; 0; 6; 253; 0; 27
    ; 247; 0; 15; 1; 131; 1; 248; 120; 124; 192; 7; 195
    ; 195; 3; 134; 96; 249; 129; 224; 252; 0; 2; 4; 51
    ; 8; 253; 0; 8; 226; 0; 2; 4; 51; 8; 253; 0
    ; 8; 226; 0; 2; 8; 0; 4; 253; 0; 8; 226; 0
    ; 2; 8; 0; 4; 253; 0; 8; 226; 0; 2; 8; 30
    ; 4; 253; 0; 8; 226; 0; 2; 8; 12; 4; 253; 0
    ; 8; 226; 0; 2; 4; 72; 136; 253; 0; 8; 226; 0
    ; 2; 28; 63; 14; 253; 0; 8; 226; 0; 2; 34; 0
    ; 17; 253; 0; 8; 226; 0; 2; 65; 255; 225; 253; 0
    ; 29; 249; 0; 0; 14; 254; 0; 13; 64; 32; 128; 65
    ; 195; 135; 2; 0; 15; 128; 32; 224; 1; 1; 252; 0
    ; 2; 64; 0; 225; 253; 0; 28; 249; 0; 0; 17; 253
    ; 0; 12; 32; 128; 194; 36; 72; 130; 0; 2; 0; 33
    ; 16; 1; 1; 252; 0; 2; 79; 193; 17; 253; 0; 31
    ; 249; 0; 25; 16; 49; 196; 150; 199; 57; 192; 66; 36
    ; 72; 131; 137; 2; 24; 225; 0; 1; 49; 199; 24; 199
    ; 0; 0; 72; 34; 17; 253; 0; 31; 249; 0; 25; 16
    ; 73; 36; 152; 73; 36; 128; 66; 35; 135; 2; 73; 2
    ; 37; 33; 0; 1; 73; 36; 165; 36; 128; 0; 72; 31
    ; 225; 253; 0; 31; 249; 0; 25; 16; 73; 36; 144; 73
    ; 36; 128; 65; 228; 72; 130; 73; 2; 61; 33; 0; 1
    ; 73; 36; 153; 36; 128; 0; 72; 0; 2; 253; 0; 31
    ; 249; 0; 25; 16; 73; 36; 144; 73; 36; 128; 64; 36
    ; 72; 130; 73; 2; 33; 33; 0; 17; 73; 36; 133; 36
    ; 128; 0; 39; 192; 4; 253; 0; 31; 249; 0; 25; 17
    ; 73; 36; 144; 73; 36; 128; 64; 68; 72; 130; 73; 2
    ; 37; 33; 16; 17; 73; 36; 165; 36; 128; 0; 16; 56
    ; 26; 253; 0; 31; 249; 0; 25; 14; 49; 195; 144; 71
    ; 36; 64; 65; 131; 135; 3; 135; 2; 24; 224; 226; 14
    ; 49; 36; 152; 196; 128; 0; 47; 199; 226; 253; 0; 19
    ; 248; 0; 3; 1; 0; 128; 1; 251; 0; 0; 1; 246
    ; 0; 2; 32; 0; 2; 253; 0; 19; 248; 0; 3; 1
    ; 3; 0; 6; 251; 0; 0; 6; 246; 0; 2; 32; 0
    ; 2; 253; 0; 8; 226; 0; 2; 32; 0; 2; 253; 0
    ; 8; 226; 0; 2; 32; 0; 2; 253; 0; 8; 226; 0
    ; 2; 31; 0; 124; 253; 0; 8; 226; 0; 2; 32; 128
    ; 130; 253; 0; 22; 245; 0; 6; 70; 96; 120; 128; 65
    ; 0; 30; 253; 0; 0; 8; 250; 0; 2; 42; 255; 170
    ; 253; 0; 22; 245; 0; 6; 66; 32; 68; 0; 65; 0
    ; 17; 253; 0; 0; 8; 250; 0; 2; 63; 128; 254; 253
    ; 0; 17; 245; 0; 11; 162; 32; 69; 142; 115; 152; 17
    ; 49; 140; 90; 38; 56; 243; 0; 17; 245; 0; 11; 162
    ; 32; 68; 146; 73; 36; 17; 74; 82; 98; 41; 72; 243
    ; 0; 18; 246; 0; 12; 1; 18; 32; 120; 146; 73; 24
    ; 30; 121; 158; 65; 79; 72; 243; 0; 18; 246; 0; 12
    ; 1; 242; 32; 80; 146; 73; 4; 20; 64; 80; 65; 72
    ; 72; 243; 0; 18; 246; 0; 12; 1; 18; 32; 72; 146
    ; 73; 36; 18; 74; 82; 64; 137; 72; 243; 0; 18; 246
    ; 0; 12; 1; 18; 32; 68; 142; 72; 152; 17; 49; 140
    ; 64; 134; 56; 243; 0; 6; 242; 0; 0; 2; 235; 0
    ; 6; 242; 0; 0; 12; 235; 0; 2; 219; 0; 2; 219
    ; 0; 2; 219; 0; 2; 219; 0; 2; 219; 0; 2; 219
    ; 0; 2; 219; 0; 2; 219; 0; 2; 219; 0; 2; 219
    ; 0; 35; 254; 0; 6; 3; 192; 0; 136; 0; 0; 28
    ; 254; 0; 19; 128; 65; 0; 131; 135; 6; 0; 249; 18
    ; 34; 68; 62; 0; 16; 0; 24; 0; 32; 0; 1; 252
    ; 0; 34; 254; 0; 6; 2; 32; 0; 128; 0; 0; 34
    ; 253; 0; 14; 65; 1; 132; 72; 136; 0; 33; 18; 50
    ; 72; 8; 0; 16; 0; 8; 254; 0; 0; 1; 252; 0
    ; 37; 254; 0; 31; 2; 38; 45; 216; 199; 24; 32; 99
    ; 137; 45; 142; 115; 128; 132; 72; 144; 0; 33; 18; 50
    ; 80; 8; 99; 28; 113; 136; 199; 99; 24; 1; 28; 96
    ; 254; 0; 37; 254; 0; 31; 2; 41; 48; 137; 36; 164
    ; 32; 146; 73; 48; 146; 73; 0; 132; 71; 30; 0; 33
    ; 242; 42; 96; 8; 148; 146; 74; 73; 41; 36; 164; 1
    ; 18; 144; 254; 0; 37; 254; 0; 31; 3; 201; 32; 137
    ; 36; 152; 32; 146; 73; 32; 146; 73; 0; 131; 200; 145
    ; 0; 33; 18; 42; 96; 8; 244; 18; 74; 73; 41; 39
    ; 152; 1; 18; 128; 254; 0; 37; 254; 0; 31; 2; 9
    ; 32; 137; 36; 132; 32; 146; 73; 32; 146; 73; 0; 128
    ; 72; 145; 0; 33; 18; 38; 80; 8; 132; 18; 74; 73
    ; 41; 36; 4; 1; 18; 128; 254; 0; 37; 254; 0; 31
    ; 2; 9; 32; 137; 36; 164; 34; 146; 73; 32; 146; 73
    ; 0; 128; 136; 145; 0; 33; 18; 38; 72; 8; 148; 146
    ; 74; 73; 41; 36; 164; 1; 18; 144; 254; 0; 37; 254
    ; 0; 31; 2; 6; 32; 72; 196; 152; 28; 99; 135; 32
    ; 142; 72; 128; 131; 7; 14; 16; 33; 18; 34; 68; 8
    ; 99; 18; 73; 136; 199; 35; 24; 65; 18; 98; 254; 0
    ; 20; 247; 0; 3; 2; 1; 0; 2; 252; 0; 0; 16
    ; 248; 0; 3; 1; 0; 0; 64; 252; 0; 20; 247; 0
    ; 3; 2; 6; 0; 12; 252; 0; 0; 32; 248; 0; 3
    ; 6; 0; 0; 128; 252; 0; 2; 219; 0; 2; 219; 0
    ; 2; 219; 0; 2; 219; 0; 2; 219; 0; 2; 219; 0
    ; 2; 219; 0; 2; 219; 0; 2; 219; 0; 255}
};

resource  ‘PICT’ (-15998, preLoad)/*MacPlus*/
{
    574,
    {31, 376, 88, 426},
    {17; 1; 160; 48; 57; 160; 0; 130; 160; 0; 142; 1; 0
    ; 10; 0; 0; 0; 0; 2; 208; 2; 64; 152; 0; 8
    ; 0; 31; 1; 120; 0; 88; 1; 176; 0; 31; 1; 120
    ; 0; 88; 1; 170; 0; 31; 1; 120; 0; 88; 1; 170
    ; 0; 0; 2; 249; 0; 6; 0; 63; 252; 255; 255; 0
    ; 7; 0; 64; 252; 0; 1; 128; 0; 7; 0; 64; 252
    ; 0; 1; 128; 0; 8; 0; 79; 253; 255; 2; 252; 128
    ; 0; 8; 0; 80; 253; 0; 2; 2; 128; 0; 8; 0
    ; 80; 253; 0; 2; 2; 128; 0; 9; 1; 80; 127; 254
    ; 255; 2; 194; 128; 0; 9; 1; 80; 128; 254; 0; 2
    ; 34; 128; 0; 8; 0; 81; 253; 0; 2; 18; 128; 0
    ; 8; 0; 81; 253; 0; 2; 18; 128; 0; 8; 0; 81
    ; 253; 0; 2; 18; 128; 0; 8; 0; 81; 253; 0; 2
    ; 18; 128; 0; 8; 0; 81; 253; 0; 2; 18; 128; 0
    ; 8; 0; 81; 253; 0; 2; 18; 128; 0; 8; 0; 81
    ; 253; 0; 2; 18; 128; 0; 8; 0; 81; 253; 0; 2
    ; 18; 128; 0; 8; 0; 81; 253; 0; 2; 18; 128; 0
    ; 8; 0; 81; 253; 0; 2; 18; 128; 0; 8; 0; 81
    ; 253; 0; 2; 18; 128; 0; 8; 0; 81; 253; 0; 2
    ; 18; 128; 0; 8; 0; 81; 253; 0; 2; 18; 128; 0
    ; 8; 0; 81; 253; 0; 2; 18; 128; 0; 8; 0; 81
    ; 253; 0; 2; 18; 128; 0; 8; 0; 81; 253; 0; 2
    ; 18; 128; 0; 8; 0; 81; 253; 0; 2; 18; 128; 0
    ; 8; 0; 81; 253; 0; 2; 18; 128; 0; 8; 0; 81
    ; 253; 0; 2; 18; 128; 0; 8; 0; 81; 253; 0; 2
    ; 18; 128; 0; 8; 0; 81; 253; 0; 2; 18; 128; 0
    ; 8; 0; 81; 253; 0; 2; 18; 128; 0; 8; 0; 81
    ; 253; 0; 2; 18; 128; 0; 8; 0; 81; 253; 0; 2
    ; 18; 128; 0; 9; 1; 80; 128; 254; 0; 2; 34; 128
    ; 0; 9; 1; 80; 127; 254; 255; 2; 194; 128; 0; 8
    ; 0; 80; 253; 0; 2; 2; 128; 0; 8; 0; 80; 253
    ; 0; 2; 2; 128; 0; 8; 0; 80; 253; 0; 2; 2
    ; 128; 0; 8; 0; 80; 253; 0; 2; 2; 128; 0; 8
    ; 0; 80; 253; 0; 2; 2; 128; 0; 8; 0; 80; 253
    ; 0; 2; 2; 128; 0; 8; 0; 80; 253; 0; 2; 2
    ; 128; 0; 8; 0; 80; 253; 0; 2; 2; 128; 0; 9
    ; 0; 80; 254; 0; 3; 15; 194; 128; 0; 9; 7; 80
    ; 0; 0; 255; 255; 226; 128; 0; 9; 7; 80; 0; 0
    ; 255; 255; 162; 128; 0; 9; 0; 80; 254; 0; 3; 8
    ; 34; 128; 0; 9; 7; 81; 192; 0; 0; 7; 194; 128
    ; 0; 9; 1; 81; 64; 254; 0; 2; 2; 128; 0; 9
    ; 1; 81; 192; 254; 0; 2; 2; 128; 0; 8; 0; 80
    ; 253; 0; 2; 2; 128; 0; 8; 0; 80; 253; 0; 2
    ; 2; 128; 0; 8; 0; 80; 253; 0; 2; 2; 128; 0
    ; 8; 0; 80; 253; 0; 2; 2; 128; 0; 7; 0; 95
    ; 252; 255; 1; 128; 0; 7; 0; 127; 252; 255; 1; 128
    ; 0; 2; 249; 0; 160; 0; 143; 160; 0; 131; 255}
};

resource  ‘PICT’ (-15997, preLoad)/*Large heart*/
{
    160,
    {259, 432, 282, 463},
    {17; 1; 160; 48; 57; 160; 0; 130; 160; 0; 142; 1; 0
    ; 10; 0; 0; 0; 0; 2; 208; 2; 64; 144; 0; 4
    ; 1; 3; 1; 176; 1; 26; 1; 208; 1; 3; 1; 176
    ; 1; 26; 1; 207; 1; 3; 1; 176; 1; 26; 1; 207
    ; 0; 0; 0; 0; 0; 0; 3; 224; 15; 128; 15; 240
    ; 31; 224; 31; 252; 127; 240; 63; 252; 127; 248; 63; 254
    ; 255; 248; 127; 255; 255; 252; 127; 255; 255; 252; 127; 255
    ; 255; 252; 127; 255; 255; 252; 63; 255; 255; 248; 63; 255
    ; 255; 248; 31; 255; 255; 240; 15; 255; 255; 224; 3; 255
    ; 255; 128; 1; 255; 255; 128; 0; 127; 254; 0; 0; 63
    ; 252; 0; 0; 15; 240; 0; 0; 7; 224; 0; 0; 3
    ; 192; 0; 0; 1; 128; 0; 0; 0; 0; 0; 160; 0
    ; 143; 160; 0; 131; 255}
};

resource  ‘PICT’ (-15996, preLoad)/*Small heart*/
{
    215,
    {244, 442, 269, 473},
    {17; 1; 160; 0; 130; 160; 0; 142; 1; 0; 10; 0; 0
    ; 0; 0; 2; 208; 2; 64; 144; 0; 6; 0; 244; 1
    ; 181; 1; 13; 1; 221; 0; 244; 1; 186; 1; 13; 1
    ; 217; 0; 244; 1; 186; 1; 13; 1; 217; 0; 0; 0
    ; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0
    ; 0; 0; 0; 0; 0; 0; 0; 224; 224; 0; 0; 0
    ; 1; 241; 240; 0; 0; 0; 1; 251; 240; 0; 0; 0
    ; 3; 255; 248; 0; 0; 0; 3; 255; 248; 0; 0; 0
    ; 3; 255; 248; 0; 0; 0; 3; 255; 248; 0; 0; 0
    ; 1; 255; 240; 0; 0; 0; 0; 255; 224; 0; 0; 0
    ; 0; 127; 192; 0; 0; 0; 0; 63; 128; 0; 0; 0
    ; 0; 31; 0; 0; 0; 0; 0; 14; 0; 0; 0; 0
    ; 0; 4; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0
    ; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0
    ; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0
    ; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0
    ; 0; 0; 0; 0; 0; 160; 0; 143; 160; 0; 131; 255
    }};

 
AAPL
$116.31
Apple Inc.
+1.64
MSFT
$48.70
Microsoft Corpora
+0.48
GOOG
$534.83
Google Inc.
-2.16

MacTech Search:
Community Search:

Software Updates via MacUpdate

Herald 5.0.1 - Notification plugin for M...
Note: Versions 2.1.3 (for OS X 10.7), 3.0.6 (for OS X 10.8), and 4.0.8 (for OS X 10.9) are no longer supported by the developer. Herald is a notification plugin for Mail.app, Apple's Mac OS X email... Read more
Firetask 3.7 - Innovative task managemen...
Firetask uniquely combines the advantages of classical priority-and-due-date-based task management with GTD. Stay focused and on top of your commitments - Firetask's "Today" view shows all relevant... Read more
TechTool Pro 7.0.6 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PhotoDesk 3.0.1 - Instagram client for p...
PhotoDesk lets you view, like, comment, and download Instagram pictures/videos! (NO Uploads! / Image Posting! Instagram forbids that! AND you *need* an *existing* Instagram account). But you can do... Read more
SuperDuper! 2.7.3 - Advanced disk clonin...
SuperDuper! is an advanced, yet easy to use disk copying program. It can, of course, make a straight copy, or "clone" -- useful when you want to move all your data from one machine to another, or do... Read more
MacJournal 6.1.5 - Create, maintain, and...
MacJournal is the world's most popular journaling software for the Mac. MacJournal 6 adds a calendar mode that show entries from any journal, geolocation, word count, and progress tracking, as well... Read more
Skim 1.4.10 - PDF Reader and note-taker...
Skim is a PDF reader and note-taker for OS X. It is designed to help you read and annotate scientific papers in PDF, but is also great for viewing any PDF file. Skim includes many features and has a... Read more
FontExplorer X Pro 4.2.2 - Font manageme...
FontExplorer X Pro is optimized for professional use; it's the solution that gives you the power you need to manage all your fonts. Now you can more easily manage, activate and organize your... Read more
SoftRAID 5.0.5 - High-quality RAID manag...
SoftRAID allows you to create and manage disk arrays to increase performance and reliability. SoftRAID's intuitive interface and powerful feature set makes this utility a must have for any Mac OS X... Read more
DEVONthink Pro 2.8.2 - Knowledge base, i...
Save 10% with our exclusive coupon code: MACUPDATE10 DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research... Read more

Latest Forum Discussions

See All

Weather or Not - Reports and Forecasts...
Weather or Not - Reports and Forecasts for your Calendar 1.0.0 Device: iOS iPhone Category: Weather Price: $2.99, Version: 1.0.0 (iTunes) Description: Weather or Not is a beautiful and intuitive way to check the weather and... | Read more »
Sago Mini Road Trip (Education)
Sago Mini Road Trip 1.0 Device: iOS Universal Category: Education Price: $2.99, Version: 1.0 (iTunes) Description: Go for a fun-filled drive with Jinja the cat. Pick a destination, select a vehicle and hit the road. What will Jinja... | Read more »
New Tower Defense Game, Kingdom Rush: Or...
New Tower Defense Game, Kingdom Rush: Origins, is Available Today Posted by Jessica Fisher on November 20th, 2014 [ permalink ] iPad Only App - Designed for the iPad | Read more »
Sunburn! (Games)
Sunburn! 1.0.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.0 (iTunes) Description: Your ship is gone. Your crew is scattered. One option remains. Gather your crew... and jump into the sun. Reunite your... | Read more »
Tapventures Review
Tapventures Review By Jennifer Allen on November 20th, 2014 Our Rating: :: ODDLY COMPELLINGUniversal App - Designed for iPhone and iPad Tapventures is an increasingly hands-off one-tap RPG, but expect it to hook you despite your... | Read more »
Who Wore it Best? The Hunger Games: Girl...
With The Hunger Games: Mockingjay Part 1 out this weekend, Who Wore it Best? pits two Hunger Games tie-ins, Girl on Fire and Panem Run, against each other in a brutal and pointless fight to the death. I wonder where we got that idea from? | Read more »
Ironkill Review
Ironkill Review By Jennifer Allen on November 20th, 2014 Our Rating: :: LACKLUSTER PUNCHINGUniversal App - Designed for iPhone and iPad Ironkill is a freemium focused fighting game that doesn’t offer particularly thrilling fights... | Read more »
Real-Time Multiplayer Match-3 RPG Crusad...
Real-Time Multiplayer Match-3 RPG Crusaders Quest Set to Launch Next Month Posted by Ellis Spice on November 20th, 2014 [ permalink ] | Read more »
Checkpoint Champion Review
Checkpoint Champion Review By Jennifer Allen on November 20th, 2014 Our Rating: :: SPEEDY DRIFTINGUniversal App - Designed for iPhone and iPad Checkpoint Champion is a drift-focused racing game that’s ideal for short but fun gaming... | Read more »
MediaFire iOS 8 Native Update Brings New...
MediaFire iOS 8 Native Update Brings New “Power Upload” Feature to iPad and iPhone Posted by Jessica Fisher on November 20th, 2014 [ | Read more »

Price Scanner via MacPrices.net

64GB iPod touch on sale for $249, save $50
Best Buy has the 64GB iPod touch on sale for $249 on their online store for a limited time. Their price is $50 off MSRP. Choose free shipping or free local store pickup (if available). Sale price for... Read more
15″ 2.2GHz Retina MacBook Pro on sale for $17...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale for $1799.99 for a limited time. Shipping is free, and B&H charges NY sales tax only. B&H will also include free copies of... Read more
New Logitech AnyAngle Case/Stand Brings Flexi...
Logitec has announced the newest addition to its suite of tablet products — the Logitech AnyAngle. A protective case with an any-angle stand for iPad Air 2 and all iPad mini models, AnyAngle is the... Read more
2013 15-inch 2.0GHz Retina MacBook Pro availa...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros available for $1499 including free shipping plus NY sales tax only. Their price is $500 off original MSRP. B&H will... Read more
16GB Retina iPad mini on sale today for $199,...
 Staples has 2nd generation 16GB Retina iPad minis on sale for $199 on their online store for a limited time. Their price is $100 off MSRP. Choose free shipping or free local store pickup (if... Read more
Developers Start Designing Apps for Apple Wat...
Apple has announced the availability of WatchKit, software that gives developers a set of tools to easily create experiences designed specifically for Apple Watch. Apple’s developer community can now... Read more
C Spire Launches iPad Air 2 and iPad Mini 3 o...
C Spire has announced that iPad Air 2 with Wi-Fi + Cellular and iPad mini 3 with Wi-Fi + Cellular are now available on its 4G LTE network. C Spire offers both new iPads with a range of data plans... Read more
Are You On Your Last PC? – The ‘Book Mystique
Will your current PC be your last? Quite possibly so if you define “personal computer” as a traditional desktop or laptop form factor machine according to some commentators. So then, the upshot that... Read more
Save up to $180 on MacBook Airs with Apple re...
The Apple Store has Apple Certified Refurbished 2014 MacBook Airs available for up to $180 off the cost of new models. An Apple one-year warranty is included with each MacBook, and shipping is free.... Read more
16GB iPad mini available for $219, save $30
Walmart has 16GB iPad minis (1st generation) available for $219 on their online store. Their price is $30 off MSRP. Choose free shipping or free store pickup (if available). Price for online orders... Read more

Jobs Board

*Apple* Solutions Consultant (ASC)- Retail S...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Store Leader Program - College Gradu...
Job Description: Job Summary As an Apple Store Leader Program agent, you can continue your education as you major in the art of leadership at the Apple Store. You'll Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.