Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
Sample.c
/* |
File: Sample.c |
Contains: Sample is an example application that demonstrates how to |
initialize the commonly used toolbox managers, operate |
successfully under MultiFinder, handle desk accessories, |
and create, grow, and zoom windows. |
It does not by any means demonstrate all the techniques |
you need for a large application. In particular, Sample |
does not cover exception handling, multiple windows/documents, |
sophisticated memory management, printing, or undo. All of |
these are vital parts of a normal full-sized application. |
This application is an example of the form of a Macintosh |
application; it is NOT a template. It is NOT intended to be |
used as a foundation for the next world-class, best-selling, |
600K application. A stick figure drawing of the human body may |
be a good example of the form for a painting, but that does not |
mean it should be used as the basis for the next Mona Lisa. |
We recommend that you review this program or TESample before |
beginning a new application. |
SetPort strategy: |
Toolbox routines do not change the current port. In spite of this, in this |
program we use a strategy of calling SetPort whenever we want to draw or |
make calls which depend on the current port. This makes us less vulnerable |
to bugs in other software which might alter the current port (such as the |
bug (feature?) in many desk accessories which change the port on OpenDeskAcc). |
Hopefully, this also makes the routines from this program more self-contained, |
since they don't depend on the current port setting. |
Written by: |
Copyright: Copyright © 1999 by Apple Computer, Inc., All Rights Reserved. |
You may incorporate this Apple sample source code into your program(s) without |
restriction. This Apple sample source code has been provided "AS IS" and the |
responsibility for its operation is yours. You are not permitted to redistribute |
this Apple sample source code as "Apple sample source code" after having made |
changes. If you're going to re-distribute the source, we require that you make |
it clear in the source that the code was descended from Apple sample source |
code, but that you've made changes. |
Change History (most recent first): |
7/9/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1 |
*/ |
#pragma segment Main |
#include <SegLoad.h> |
#include <ToolUtils.h> |
#include <DiskInit.h> |
#include <Processes.h> |
#include <Devices.h> |
#include <limits.h> |
#include "Test.h" |
#include "Sample.h" /* bring in all the #defines for Sample */ |
/* The "g" prefix is used to emphasize that a variable is global. */ |
/* GMac is used to hold the result of a SysEnvirons call. This makes |
it convenient for any routine to check the environment. */ |
SysEnvRec gMac; /* set up by Initialize */ |
/* GHasWaitNextEvent is set at startup, and tells whether the WaitNextEvent |
trap is available. If it is false, we know that we must call GetNextEvent. */ |
Boolean gHasWaitNextEvent; /* set up by Initialize */ |
/* GInBackground is maintained by our osEvent handling routines. Any part of |
the program can check it to find out if it is currently in the background. */ |
Boolean gInBackground; /* maintained by Initialize and DoEvent */ |
/* The following globals are the state of the window. If we supported more than |
one window, they would be attatched to each document, rather than globals. */ |
/* GStopped tells whether the stop light is currently on stop or go. */ |
Boolean gStopped; /* maintained by Initialize and SetLight */ |
/* GStopRect and gGoRect are the rectangles of the two stop lights in the window. */ |
Rect gStopRect; /* set up by Initialize */ |
Rect gGoRect; /* set up by Initialize */ |
/* Define HiWrd and LoWrd macros for efficiency. */ |
#define HiWrd(aLong) (((aLong) >> 16) & 0xFFFF) |
#define LoWrd(aLong) ((aLong) & 0xFFFF) |
/* Define TopLeft and BotRight macros for convenience. Notice the implicit |
dependency on the ordering of fields within a Rect */ |
#define TopLeft(aRect) (* (Point *) &(aRect).top) |
#define BotRight(aRect) (* (Point *) &(aRect).bottom) |
void main() |
{ |
/* 1.01 - call to ForceEnvirons removed */ |
/* If you have stack requirements that differ from the default, |
then you could use SetApplLimit to increase StackSpace at |
this point, before calling MaxApplZone. */ |
MaxApplZone(); /* expand the heap so code segments load at the top */ |
Initialize(); /* initialize the program */ |
UnloadSeg((Ptr) Initialize); /* note that Initialize must not be in Main! */ |
EventLoop(); /* call the main event loop */ |
} |
/* Get events forever, and handle them by calling DoEvent. |
Get the events by calling WaitNextEvent, if it's available, otherwise |
by calling GetNextEvent. Also call AdjustCursor each time through the loop. */ |
void EventLoop() |
{ |
RgnHandle cursorRgn; |
Boolean gotEvent; |
EventRecord event; |
Point mouse; |
cursorRgn = NewRgn(); /* weÕll pass WNE an empty region the 1st time thru */ |
do { |
/* use WNE if it is available */ |
if ( gHasWaitNextEvent ) { |
GetGlobalMouse(&mouse); |
AdjustCursor(mouse, cursorRgn); |
gotEvent = WaitNextEvent(everyEvent, &event, LONG_MAX, nil); |
} |
else { |
SystemTask(); |
gotEvent = GetNextEvent(everyEvent, &event); |
} |
if ( gotEvent ) { |
/* make sure we have the right cursor before handling the event */ |
AdjustCursor(event.where, cursorRgn); |
if (FrontWindow() && IsDialogEvent(&event)) { |
HandleDialogEvent(&event); |
} else { |
HandleNormalEvent(&event); |
} |
} |
/* If you are using modeless dialogs that have editText items, |
you will want to call IsDialogEvent to give the caret a chance |
to blink, even if WNE/GNE returned FALSE. However, check FrontWindow |
for a non-NIL value before calling IsDialogEvent. */ |
} while ( true ); /* loop forever; we quit via ExitToShell */ |
} /*EventLoop*/ |
/* Do the right thing for an event. Determine what kind of event it is, and call |
the appropriate routines. */ |
void HandleNormalEvent(EventRecord *event) |
{ |
short part, err; |
WindowPtr window; |
Boolean hit; |
char key; |
Point aPoint; |
switch ( event->what ) { |
case mouseDown: |
part = FindWindow(event->where, &window); |
switch ( part ) { |
case inMenuBar: /* process a mouse menu command (if any) */ |
AdjustMenus(); |
DoMenuCommand(MenuSelect(event->where)); |
break; |
case inSysWindow: /* let the system handle the mouseDown */ |
SystemClick(event, window); |
break; |
case inContent: |
if ( window != FrontWindow() ) { |
SelectWindow(window); |
/*HandleNormalEvent(event);*/ /* use this line for "do first click" */ |
} else |
DoContentClick(window); |
break; |
case inDrag: /* pass screenBits.bounds to get all gDevices */ |
DragWindow(window, event->where, &qd.screenBits.bounds); |
break; |
case inGrow: |
break; |
case inZoomIn: |
case inZoomOut: |
hit = TrackBox(window, event->where, part); |
if ( hit ) { |
SetPort(window); /* the window must be the current port... */ |
EraseRect(&window->portRect); /* because of a bug in ZoomWindow */ |
ZoomWindow(window, part, true); /* note that we invalidate and erase... */ |
InvalRect(&window->portRect); /* to make things look better on-screen */ |
} |
break; |
} |
break; |
case keyDown: |
case autoKey: /* check for menukey equivalents */ |
key = event->message & charCodeMask; |
if ( event->modifiers & cmdKey ) /* Command key down */ |
if ( event->what == keyDown ) { |
AdjustMenus(); /* enable/disable/check menu items properly */ |
DoMenuCommand(MenuKey(key)); |
} |
break; |
case activateEvt: |
DoActivate((WindowPtr) event->message, (event->modifiers & activeFlag) != 0); |
break; |
case updateEvt: |
DoUpdate((WindowPtr) event->message); |
break; |
/* 1.01 - It is not a bad idea to at least call DIBadMount in response |
to a diskEvt, so that the user can format a floppy. */ |
case diskEvt: |
if ( HiWord(event->message) != noErr ) { |
SetPt(&aPoint, kDILeft, kDITop); |
err = DIBadMount(aPoint, event->message); |
} |
break; |
case kOSEvent: |
/* 1.02 - must BitAND with 0x0FF to get only low byte */ |
switch ((event->message >> 24) & 0x0FF) { /* high byte of message */ |
case kSuspendResumeMessage: /* suspend/resume is also an activate/deactivate */ |
gInBackground = (event->message & kResumeMask) == 0; |
DoActivate(FrontWindow(), !gInBackground); |
break; |
} |
break; |
} |
} /*HandleNormalEvent*/ |
void HandleDialogEvent(EventRecord *event) |
{ |
OSErr err; |
char key; |
Point aPoint; |
short itemHit; |
DialogPtr dialog; |
switch ( event->what ) { |
case keyDown: |
case autoKey: /* check for menukey equivalents */ |
key = event->message & charCodeMask; |
if ( event->modifiers & cmdKey ) /* Command key down */ |
if ( event->what == keyDown ) { |
AdjustMenus(); /* enable/disable/check menu items properly */ |
DoMenuCommand(MenuKey(key)); |
} |
break; |
case diskEvt: |
if ( HiWord(event->message) != noErr ) { |
SetPt(&aPoint, kDILeft, kDITop); |
err = DIBadMount(aPoint, event->message); |
} |
break; |
default: |
if (DialogSelect(event, &dialog, &itemHit)) { |
if (itemHit == kReadySetGoButton) { |
LetTheGameBegin(dialog); |
} |
} |
} |
} |
/* Change the cursor's shape, depending on its position. This also calculates the region |
where the current cursor resides (for WaitNextEvent). If the mouse is ever outside of |
that region, an event would be generated, causing this routine to be called, |
allowing us to change the region to the region the mouse is currently in. If |
there is more to the event than just Òthe mouse movedÓ, we get called before the |
event is processed to make sure the cursor is the right one. In any (ahem) event, |
this is called again before we fall back into WNE. */ |
void AdjustCursor() |
{ |
WindowPtr window; |
//RgnHandle arrowRgn; |
//RgnHandle plusRgn; |
//Rect globalPortRect; |
window = FrontWindow(); /* we only adjust the cursor when we are in front */ |
if ( (! gInBackground) && (! IsDAWindow(window)) ) { |
} |
} /*AdjustCursor*/ |
/* Get the global coordinates of the mouse. When you call OSEventAvail |
it will return either a pending event or a null event. In either case, |
the where field of the event record will contain the current position |
of the mouse in global coordinates and the modifiers field will reflect |
the current state of the modifiers. Another way to get the global |
coordinates is to call GetMouse and LocalToGlobal, but that requires |
being sure that thePort is set to a valid port. */ |
void GetGlobalMouse(Point *mouse) |
{ |
EventRecord event; |
OSEventAvail(kNoEvents, &event); /* we aren't interested in any events */ |
*mouse = event.where; /* just the mouse position */ |
} /*GetGlobalMouse*/ |
/* This is called when an update event is received for a window. |
It calls DrawWindow to draw the contents of an application window. |
As an effeciency measure that does not have to be followed, it |
calls the drawing routine only if the visRgn is non-empty. This |
will handle situations where calculations for drawing or drawing |
itself is very time-consuming. */ |
void DoUpdate(WindowPtr window) |
{ |
if ( IsAppWindow(window) ) { |
BeginUpdate(window); /* this sets up the visRgn */ |
if ( ! EmptyRgn(window->visRgn) ) /* draw if updating needs to be done */ |
DrawWindow(window); |
EndUpdate(window); |
} |
} /*DoUpdate*/ |
/* This is called when a window is activated or deactivated. |
In Sample, the Window Manager's handling of activate and |
deactivate events is sufficient. Other applications may have |
TextEdit records, controls, lists, etc., to activate/deactivate. */ |
void DoActivate(WindowPtr window, Boolean becomingActive) |
{ |
if ( IsAppWindow(window) ) { |
if ( becomingActive ) |
/* do whatever you need to at activation */ ; |
else |
/* do whatever you need to at deactivation */ ; |
} |
} /*DoActivate*/ |
/* This is called when a mouse-down event occurs in the content of a window. |
Other applications might want to call FindControl, TEClick, etc., to |
further process the click. */ |
void DoContentClick() |
{ |
} /*DoContentClick*/ |
/* Draw the contents of the application window. We do some drawing in color, using |
Classic QuickDraw's color capabilities. This will be black and white on old |
machines, but color on color machines. At this point, the windowÕs visRgn |
is set to allow drawing only where it needs to be done. */ |
void DrawWindow(WindowPtr window) |
{ |
SetPort(window); |
} /*DrawWindow*/ |
/* Enable and disable menus based on the current state. |
The user can only select enabled menu items. We set up all the menu items |
before calling MenuSelect or MenuKey, since these are the only times that |
a menu item can be selected. Note that MenuSelect is also the only time |
the user will see menu items. This approach to deciding what enable/ |
disable state a menu item has the advantage of concentrating all |
the decision-making in one routine, as opposed to being spread throughout |
the application. Other application designs may take a different approach |
that is just as valid. */ |
void AdjustMenus() |
{ |
WindowPtr window; |
MenuHandle menu; |
window = FrontWindow(); |
menu = GetMenuHandle(mFile); |
if ( IsDAWindow(window) ) /* we can allow desk accessories to be closed from the menu */ |
EnableItem(menu, iClose); |
else |
DisableItem(menu, iClose); /* but not our traffic light window */ |
menu = GetMenuHandle(mEdit); |
if ( IsDAWindow(window) ) { /* a desk accessory might need the edit menuÉ */ |
EnableItem(menu, iUndo); |
EnableItem(menu, iCut); |
EnableItem(menu, iCopy); |
EnableItem(menu, iClear); |
EnableItem(menu, iPaste); |
} else { /* Ébut we donÕt use it */ |
DisableItem(menu, iUndo); |
DisableItem(menu, iCut); |
DisableItem(menu, iCopy); |
DisableItem(menu, iClear); |
DisableItem(menu, iPaste); |
} |
} /*AdjustMenus*/ |
/* This is called when an item is chosen from the menu bar (after calling |
MenuSelect or MenuKey). It performs the right operation for each command. |
It is good to have both the result of MenuSelect and MenuKey go to |
one routine like this to keep everything organized. */ |
void DoMenuCommand(long menuResult) |
{ |
short menuID; /* the resource ID of the selected menu */ |
short menuItem; /* the item number of the selected menu */ |
short itemHit; |
Str255 daName; |
short daRefNum; |
Boolean handledByDA; |
menuID = HiWord(menuResult); /* use macros for efficiency to... */ |
menuItem = LoWord(menuResult); /* get menu item number and menu number */ |
switch ( menuID ) { |
case mApple: |
switch ( menuItem ) { |
case iAbout: /* bring up alert for About */ |
itemHit = Alert(rAboutAlert, nil); |
break; |
default: /* all non-About items in this menu are DAs */ |
/* type Str255 is an array in MPW 3 */ |
GetMenuItemText(GetMenuHandle(mApple), menuItem, daName); |
daRefNum = OpenDeskAcc(daName); |
break; |
} |
break; |
case mFile: |
switch ( menuItem ) { |
case iClose: |
DoCloseWindow(FrontWindow()); |
break; |
case iQuit: |
Terminate(); |
break; |
} |
break; |
case mEdit: /* call SystemEdit for DA editing & MultiFinder */ |
handledByDA = SystemEdit(menuItem-1); /* since we donÕt do any Editing */ |
break; |
} |
HiliteMenu(0); /* unhighlight what MenuSelect (or MenuKey) hilited */ |
} /*DoMenuCommand*/ |
/* Close a window. This handles desk accessory and application windows. */ |
/* 1.01 - At this point, if there was a document associated with a |
window, you could do any document saving processing if it is 'dirty'. |
DoCloseWindow would return true if the window actually closed, i.e., |
the user didnÕt cancel from a save dialog. This result is handy when |
the user quits an application, but then cancels the save of a document |
associated with a window. */ |
Boolean DoCloseWindow(WindowPtr window) |
{ |
if ( IsDAWindow(window) ) |
CloseDeskAcc(((WindowPeek) window)->windowKind); |
else if ( IsAppWindow(window) ) |
CloseWindow(window); |
return true; |
} /*DoCloseWindow*/ |
/************************************************************************************** |
*** 1.01 DoCloseBehind(window) was removed *** |
1.01 - DoCloseBehind was a good idea for closing windows when quitting |
and not having to worry about updating the windows, but it suffered |
from a fatal flaw. If a desk accessory owned two windows, it would |
close both those windows when CloseDeskAcc was called. When DoCloseBehind |
got around to calling DoCloseWindow for that other window that was already |
closed, things would go very poorly. Another option would be to have a |
procedure, GetRearWindow, that would go through the window list and return |
the last window. Instead, we decided to present the standard approach |
of getting and closing FrontWindow until FrontWindow returns NIL. This |
has a potential benefit in that the window whose document needs to be saved |
may be visible since it is the front window, therefore decreasing the |
chance of user confusion. For aesthetic reasons, the windows in the |
application should be checked for updates periodically and have the |
updates serviced. |
**************************************************************************************/ |
/* Clean up the application and exit. We close all of the windows so that |
they can update their documents, if any. */ |
/* 1.01 - If we find out that a cancel has occurred, we won't exit to the |
shell, but will return instead. */ |
void Terminate() |
{ |
WindowPtr aWindow; |
Boolean closed; |
closed = true; |
do { |
aWindow = FrontWindow(); /* get the current front window */ |
if (aWindow != nil) |
closed = DoCloseWindow(aWindow); /* close this window */ |
} |
while (closed && (aWindow != nil)); |
if (closed) |
ExitToShell(); /* exit if no cancellation */ |
} /*Terminate*/ |
/* Check to see if a window belongs to the application. If the window pointer |
passed was NIL, then it could not be an application window. WindowKinds |
that are negative belong to the system and windowKinds less than userKind |
are reserved by Apple except for windowKinds equal to dialogKind, which |
mean it is a dialog. |
1.02 - In order to reduce the chance of accidentally treating some window |
as an AppWindow that shouldn't be, we'll only return true if the windowkind |
is userKind. If you add different kinds of windows to Sample you'll need |
to change how this all works. */ |
Boolean IsAppWindow(WindowPtr window) |
{ |
short windowKind; |
if ( window == nil ) |
return false; |
else { /* application windows have windowKinds = userKind (8) */ |
windowKind = ((WindowPeek) window)->windowKind; |
return ( windowKind == userKind || windowKind == dialogKind); |
} |
} /*IsAppWindow*/ |
/* Check to see if a window belongs to a desk accessory. */ |
Boolean IsDAWindow(WindowPtr window) |
{ |
if ( window == nil ) |
return false; |
else /* DA windows have negative windowKinds */ |
return ( ((WindowPeek) window)->windowKind < 0 ); |
} /*IsDAWindow*/ |
/* Display an alert that tells the user an error occurred, then exit the program. |
This routine is used as an ultimate bail-out for serious errors that prohibit |
the continuation of the application. Errors that do not require the termination |
of the application should be handled in a different manner. Error checking and |
reporting has a place even in the simplest application. The error number is used |
to index an 'STR#' resource so that a relevant message can be displayed. */ |
void AlertUser() |
{ |
short itemHit; |
SetCursor(&qd.arrow); |
itemHit = Alert(rUserAlert, nil); |
ExitToShell(); |
} /* AlertUser */ |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-07-22