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.
MacCalendar.c
/* |
File: MacCalendar |
Contains: Control strip module for displaying a calendar. |
Written by: Martin Minow |
Copyright: © 1994-1997 by Apple Computer, Inc., all rights reserved. |
Change History (most recent first): |
You may incorporate this sample code into your applications without |
restriction, though the sample code has been provided "AS IS" and the |
responsibility for its operation is 100% yours. However, what you are |
not permitted to do is to redistribute the source as "DSC Sample 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 Code, but that you've made changes. |
Based on the Status Bar Sample.c by Steve Christensen. |
File Type sdev |
File Creator SCAL -- registered with DTS |
Resource Type sdev |
Resource ID 0 |
Resource Attributes purgeable |
Other options (MetroWerks, Think C 7.0): |
Set Require Prototypes, Check Pointer Types, All other warnings. |
Do not set trigraph recognition. |
Enable Apple extensions. |
MetroWerks link/project options: |
Link single segment |
Set Project type "Code Segment", Standard Header. |
*/ |
///////////////////////////////////////////////////////////////////////// |
// Pick up resource constants common to both control strip |
// module and application. |
#include "MacCalendarCommon.h" |
///////////////////////////////////////////////////////////////////////// |
// Pick up resource constants common to both C and Rez for |
// the control strip module. |
#include "MacCalendar.h" |
///////////////////////////////////////////////////////////////////////// |
// Pick up prototype for the core drawing code. |
#include "DrawCalendar.h" |
///////////////////////////////////////////////////////////////////////// |
// Pick up system types. |
#include <Fonts.h> |
#include <Memory.h> |
#include <Menus.h> |
#include <Quickdraw.h> |
#include <Resources.h> |
#include <ToolUtils.h> |
#include <Types.h> |
#include <Windows.h> |
#include <Icons.h> |
#include <ControlStrip.h> |
#include <Gestalt.h> |
///////////////////////////////////////////////////////////////////////// |
// Metrowerks uses A4 to reference globals. The A4-setup code was copied from the |
// WDEF.c sample included in the Metrowerks DR3 distribution. MPW and Think C |
// use PC-relative addressing in a single-segment code module. |
#ifdef __MWERKS__ |
#include <A4Stuff.h> // also included in <MacHeaders> |
#include <SetupA4.h> // required to handle callback functions |
#endif |
///////////////////////////////////////////////////////////////////////// |
// Lots of global constants. |
/* |
* Define the patterns as C-strings so they can be addressed as constants |
* within the program. |
*/ |
#define kWhitePattern ((ConstPatternParam) "\000\000\000\000\000\000\000\000") |
#define kBlackPattern ((ConstPatternParam) "\377\377\377\377\377\377\377\377") |
enum { |
kIconWidth = 16 // Width of the icon on the control strip itself. |
}; |
// Name under which our preferences are saved. |
#define kCalendarPrefName "\pMacCalendar Preferences" |
// Indices into the STRN_Info STR# resource. |
enum { |
kStringHelp = 1, |
kStringFontName, |
kStringFontSize, |
kStringFirstDayOfWeek, |
kStringDayNames |
}; |
/* |
* This record defines the information we need to draw the calendar. It is initialized |
* when we are called with the sdevInitModule message, and passed to and from the |
* Status Bar manager. |
*/ |
typedef struct GlobalRecord { |
Handle iconSuite; /* Status bar icon */ |
Handle textStrings; /* Balloon help string etc. */ |
PicHandle rightArrowPicture; /* Popup arrow */ |
SavedSettingsHandle settings; /* Preference settings */ |
UInt32 lastSavedModCount; /* The modCount setting when we last saved. */ |
} GlobalRecord, *GlobalPtr, **GlobalHandle; |
///////////////////////////////////////////////////////////////////////// |
// Save current status for restarts. |
static OSErr |
CtlStripSavePreferences( |
GlobalPtr globalPtr |
) |
{ |
OSErr status; |
status = SBSavePreferences(kCalendarPrefName, (Handle) globalPtr->settings); |
if (status == noErr) { |
globalPtr->lastSavedModCount = (**globalPtr->settings).modCount; |
} |
return(status); |
} |
///////////////////////////////////////////////////////////////////////// |
// Termination |
static void |
CtlStripCleanUp( |
GlobalHandle globalHandle |
) |
{ |
register GlobalPtr globalPtr; |
if (globalHandle != nil) { |
HLock((Handle) globalHandle); |
globalPtr = *globalHandle; |
if (globalPtr->iconSuite != NULL) |
DisposeIconSuite(globalPtr->iconSuite, TRUE); |
if (globalPtr->textStrings != NULL) |
DisposeHandle(globalPtr->textStrings); |
if (globalPtr->rightArrowPicture != NULL) |
DisposeHandle((Handle) globalPtr->rightArrowPicture); |
if (globalPtr->settings != NULL) |
DisposeHandle((Handle) globalPtr->settings); |
(void) ReplaceGestaltValue(kControlStripCreator, 0); |
DisposeHandle((Handle) globalHandle); |
} |
} |
///////////////////////////////////////////////////////////////////////// |
// Initialization |
static long |
CtlStripInitialize(void) |
{ |
register GlobalHandle globalHandle; |
register GlobalPtr globalPtr; |
long result; |
Str255 work; |
SavedSettingsHandle prefsHandle; |
long tempLong; |
long gestaltResponse; |
globalHandle = nil; |
// We're using GestaltValue, so we have to make sure we have |
// System 7.5. |
result = Gestalt(gestaltSystemVersion, &gestaltResponse); |
if (result == noErr) { |
if (gestaltResponse < 0x0750) { |
result = unimpErr; |
} |
} |
// Register ourselves with Gestalt. This will trigger |
// an error if we've been installed twice, which will cause the |
// second instance to fail sooner rather than fail later. |
if (result == noErr) { |
result = NewGestaltValue(kControlStripCreator, 0); |
} |
if (result == noErr) { |
globalHandle = (GlobalHandle) NewHandleSysClear(sizeof (GlobalRecord)); |
result = noErr; |
if (globalHandle == NULL) |
result = MemError(); |
else { |
HLock((Handle) globalHandle); |
globalPtr = *globalHandle; |
/* |
* Load and detach the icon suite |
*/ |
result = SBGetDetachIconSuite(&(globalPtr->iconSuite), ICON_StatusBar, svAllSmallData); |
} |
} |
if (result == noErr) { |
globalPtr->textStrings = GetResource('STR#', STRN_Info); |
result = ResError(); |
} |
if (result == noErr) { |
DetachResource(globalPtr->textStrings); |
globalPtr->rightArrowPicture = GetPicture(PICT_RightArrow); |
if (globalPtr->rightArrowPicture == NULL) |
result = ResError(); |
} |
if (result == noErr) { |
DetachResource((Handle) globalPtr->rightArrowPicture); |
/* |
* Get the saved preferences, if any, and configure the drawing |
* environment. Note that the sample status bar doesn't dispose |
* of the prefsHandle but I maintain that this is a bug. While |
* the control strip documentation doesn't mention whether the |
* handle returned by SBLoadPreferences is a resource or a memory |
* handle, my testing indicates it's a memory handle. |
*/ |
prefsHandle = nil; |
result = SBLoadPreferences(kCalendarPrefName, (Handle *) &prefsHandle); |
if (result == noErr |
&& prefsHandle != nil |
&& GetHandleSize((Handle) prefsHandle) == sizeof (SavedSettings) |
&& (**prefsHandle).signature == kControlStripCreator |
&& (**prefsHandle).prefVersion == kPrefVersion) { |
/* |
* Use the saved preference resource |
*/ |
globalPtr->settings = prefsHandle; |
(**(globalPtr->settings)).modCount = 0; |
globalPtr->lastSavedModCount = 0; |
} |
else { |
/* |
* Hmm, we don't have any preferences. Build a new preference resource. |
*/ |
if (prefsHandle != nil) { |
DisposeHandle( (Handle) prefsHandle); |
} |
prefsHandle = (SavedSettingsHandle) NewHandleSysClear(sizeof(SavedSettings)); |
globalPtr->settings = prefsHandle; |
result = MemError(); |
if (result == noErr) { |
(**(globalPtr->settings)).signature = kControlStripCreator; |
(**(globalPtr->settings)).prefVersion = kPrefVersion; |
(**(globalPtr->settings)).modCount = 0; |
globalPtr->lastSavedModCount = 1; |
SBGetDetachedIndString(work, globalPtr->textStrings, kStringDayNames); |
pstrcpy((**(globalPtr->settings)).dayNameString, work); |
SBGetDetachedIndString(work, globalPtr->textStrings, kStringFontName); |
pstrcpy((**(globalPtr->settings)).fontName, work); |
SBGetDetachedIndString(work, globalPtr->textStrings, kStringFontSize); |
StringToNum(work, &tempLong); |
(**(globalPtr->settings)).fontSize = tempLong; |
SBGetDetachedIndString(work, globalPtr->textStrings, kStringFirstDayOfWeek); |
StringToNum(work, &tempLong); |
(**(globalPtr->settings)).firstDayOfWeek = tempLong; |
} |
} |
} |
// Now that we successfully started up, publish our globals using |
// Gestalt. |
if (result == noErr) { |
result = ReplaceGestaltValue(kControlStripCreator, (long) globalPtr->settings); |
} |
/* |
* We've finished all initialization. If there is an error, exit through |
* CtlStripCleanUp to dispose of handles and other junk. If initialization |
* are successful, unlock the handle and return the handle cast to a long. |
*/ |
if (result != noErr) { |
CtlStripCleanUp(globalHandle); |
} |
else { |
HUnlock((Handle) globalHandle); |
result = (long) globalHandle; |
} |
return (result); |
} |
///////////////////////////////////////////////////////////////////////// |
// Draw the icon in the status bar. |
static long |
CtlStripDrawStatusIcon( |
GlobalPtr globalPtr, |
const Rect *statusRect |
) |
{ |
Rect viewRect; |
short arrowHeight; |
viewRect = *statusRect; |
viewRect.right = viewRect.left + kIconWidth; |
(void) PlotIconSuite(&viewRect, atNone, ttNone, globalPtr->iconSuite); |
/* |
* Draw an right-arrow to show that we have a popup menu. Well, we don't |
* actually have a popup menu, but we do pop up a calendar when clicked on. |
*/ |
arrowHeight = height(PicFrame(rightArrowPicture)); |
viewRect.left = viewRect.right; |
viewRect.right += width(PicFrame(rightArrowPicture)); |
viewRect.top += ((height(viewRect) - arrowHeight) >> 1); |
viewRect.bottom = viewRect.top + arrowHeight; |
DrawPicture(globalPtr->rightArrowPicture, &viewRect); |
return (0); |
} |
///////////////////////////////////////////////////////////////////////// |
// Position the calendar with respect to the status bar. If there is enough room |
// above, put it above, else put it below. Left and right operate similarly. |
enum { |
kCtlStripFrame = 4 /* The frame above/below the icon itself */ |
}; |
static void |
GetDisplayRect( |
const Rect *statusRect, |
Point displaySize, |
Rect *windowRect |
) |
{ |
Rect sBarRect; |
sBarRect = *statusRect; |
LocalToGlobal((Point *) &sBarRect.top); |
LocalToGlobal((Point *) &sBarRect.bottom); |
if (sBarRect.top - kCtlStripFrame - displaySize.v - (GetMBarHeight() + 2) > 0) { |
/* |
* The calendar is displayed above the status bar. |
*/ |
windowRect->bottom = sBarRect.top - kCtlStripFrame - 1; |
windowRect->top = windowRect->bottom - displaySize.v; |
} |
else { |
/* |
* The calendar is displayed below the status bar. |
*/ |
windowRect->top = sBarRect.bottom + kCtlStripFrame + 1; |
windowRect->bottom = windowRect->top + displaySize.v; |
} |
if (sBarRect.right - displaySize.h > 0) { |
/* |
* The calendar is displayed to the left of the calendar icon. |
*/ |
windowRect->right = sBarRect.right; |
windowRect->left = windowRect->right - displaySize.h; |
} |
else { |
/* |
* The calendar is displayed to the right of the calendar icon. |
*/ |
windowRect->left = sBarRect.left; |
windowRect->right = windowRect->left + displaySize.h; |
} |
} |
///////////////////////////////////////////////////////////////////////// |
// Build the triangular "next month" and "previous month" buttons. |
enum { |
kButtonSeparation = 4 /* 1.0d3, was 2 */ |
}; |
static void |
MakeTriangularButtons( |
const Rect *monthRect, |
PolyHandle *leftButton, |
PolyHandle *rightButton, |
Rect *leftButtonRect, |
Rect *rightButtonRect |
) |
{ |
FontInfo fontInfo; |
short buttonSize; |
short halfSize; |
Rect bothButtonRect; |
GetFontInfo(&fontInfo); |
buttonSize = (fontInfo.ascent & ~1); /* Round down to even value */ |
halfSize = buttonSize / 2; |
bothButtonRect = *monthRect; |
bothButtonRect.bottom -= (1 + fontInfo.leading); |
bothButtonRect.top = bothButtonRect.bottom - buttonSize; |
bothButtonRect.left = |
(width(*monthRect) >> 1) - buttonSize - kButtonSeparation; |
bothButtonRect.right = |
(width(*monthRect) >> 1) + buttonSize + kButtonSeparation; |
*leftButtonRect = bothButtonRect; |
/* 1.0d4 + */ |
leftButtonRect->right = leftButtonRect->left + halfSize; |
*rightButtonRect = bothButtonRect; |
rightButtonRect->left = rightButtonRect->right - halfSize; |
*leftButton = OpenPoly(); |
MoveTo(halfSize, 0); |
LineTo(halfSize, buttonSize); |
LineTo(0, halfSize); |
LineTo(halfSize, 0); |
ClosePoly(); |
OffsetPoly(*leftButton, leftButtonRect->left, leftButtonRect->top); |
*rightButton = OpenPoly(); |
MoveTo(0, 0); |
LineTo(halfSize, halfSize); |
LineTo(0, buttonSize); |
LineTo(0, 0); |
ClosePoly(); |
OffsetPoly(*rightButton, rightButtonRect->left, rightButtonRect->top); |
/* 1.0d4 - */ |
} |
///////////////////////////////////////////////////////////////////////// |
// Now that we've done setup, do the actual mouse tracking. If the mouse hits the |
// right button, advance the month and draw it. If it hits the left button, draw |
// the previous month. |
static void |
DrawCalendarAndTrackMouse( |
GlobalPtr globalPtr, |
WindowPtr windowPtr, |
const Rect *monthRect |
) |
{ |
Rect leftButtonRect; |
Rect rightButtonRect; |
PolyHandle leftButton; |
PolyHandle rightButton; |
unsigned long nowSeconds; |
DateTimeRec now; |
Point mousePt; |
short thisYear; |
short thisMonth; |
typedef enum { |
inNoButton = 0, |
inLeftButton = 1, |
inRightButton = 2 |
} WhichButton; |
WhichButton inButton; |
WhichButton wasInButton; |
unsigned long nextMonthTick; |
Boolean redrawButtons; |
FrameRect(monthRect); |
MakeTriangularButtons( |
monthRect, |
&leftButton, |
&rightButton, |
&leftButtonRect, |
&rightButtonRect |
); |
GetDateTime(&nowSeconds); |
SecondsToDate(nowSeconds, &now); |
inButton = wasInButton = inNoButton; |
thisYear = thisMonth = 0; |
InitCursor(); |
while (WaitMouseUp()) { |
if (thisYear != now.year || thisMonth != now.month) { |
/* |
* Draw the new month. Also make sure the buttons are drawn. |
*/ |
redrawButtons = TRUE; |
EraseRect(&windowPtr->portRect); |
FrameRect(monthRect); |
switch (wasInButton) { |
case inLeftButton: |
FillPoly(leftButton, kWhitePattern); |
break; |
case inRightButton: |
FillPoly(rightButton, kWhitePattern); |
break; |
} |
FramePoly(leftButton); |
FramePoly(rightButton); |
DrawCalendar( |
globalPtr->settings, |
now.year, |
now.month, |
&windowPtr->portRect |
); |
thisYear = now.year; |
thisMonth = now.month; |
} /* If drawing new month */ |
/* |
* Get the mouse and track it while it is in one of our buttons |
*/ |
GetMouse(&mousePt); |
if (PtInRect(mousePt, &leftButtonRect)) |
inButton = inLeftButton; |
else if (PtInRect(mousePt, &rightButtonRect)) |
inButton = inRightButton; |
else { |
inButton = inNoButton; |
} |
if (redrawButtons || inButton != wasInButton) { |
switch (wasInButton) { |
case inLeftButton: FillPoly(leftButton, kWhitePattern); break; |
case inRightButton: FillPoly(rightButton, kWhitePattern); break; |
} |
switch (inButton) { |
case inLeftButton: FillPoly(leftButton, kBlackPattern); break; |
case inRightButton: FillPoly(rightButton, kBlackPattern); break; |
} |
FramePoly(leftButton); |
FramePoly(rightButton); |
if (inButton != wasInButton && inButton != inNoButton) |
nextMonthTick = 0; /* Force new month drawing */ |
redrawButtons = FALSE; |
wasInButton = inButton; |
} /* If button click change */ |
if (inButton != inNoButton && TickCount() > nextMonthTick) { |
/* |
* The user has clicked in a button, or has held the mouse |
* down in a button for one second. Draw the appropriate month. |
*/ |
nextMonthTick = TickCount() + 60; |
switch (inButton) { |
case inLeftButton: |
if (--now.month <= 0) { /* Previous month or year */ |
now.month = 12; |
--now.year; |
} |
break; |
case inRightButton: |
if (++now.month > 12) { /* Next month or year */ |
now.month = 1; |
++now.year; |
} |
break; |
} /* Which button was clicked */ |
} /* Moving to a new month */ |
} /* Loop while mouse down */ |
KillPoly(leftButton); |
KillPoly(rightButton); |
} |
///////////////////////////////////////////////////////////////////////// |
// Click in the status bar icon |
static long |
CtlStripMouseClick( |
GlobalPtr globalPtr, |
const Rect *statusRect |
) |
{ |
Rect windowRect; |
WindowPtr windowPtr; |
GrafPtr savePort; |
Point displaySize; |
Rect monthRect; |
displaySize = GetCalendarDisplaySize(globalPtr->settings); |
displaySize.h += 2; |
displaySize.v += 2; |
GetDisplayRect(statusRect, displaySize, &windowRect); |
windowPtr = NewWindow( |
NULL, |
&windowRect, |
"\p", |
TRUE, |
plainDBox, |
(WindowPtr) -1L, |
FALSE, /* No go-away box */ |
0 /* No refCon */ |
); |
if (windowPtr != NULL) { |
GetPort(&savePort); |
SetPort(windowPtr); |
GetCalendarMonthRect( |
globalPtr->settings, |
&windowPtr->portRect, |
&monthRect |
); |
if (StillDown()) { |
/* |
* Design the two triangular buttons that will be displayed on the |
* bottom line of the calendar and create the polygons. Then draw |
* the calendar and track the mouse while it's held down. |
*/ |
DrawCalendarAndTrackMouse( |
globalPtr, |
windowPtr, |
&monthRect |
); |
} |
SetPort(savePort); |
DisposeWindow(windowPtr); |
} |
return (0); |
} |
///////////////////////////////////////////////////////////////////////// |
// Periodically we check the globals that we publish via Gestalt. If they |
// have been modified, we return sdevNeedToSave so that the Control Strip |
// will call our CtlStripSavePreferences routine when an appropriate time |
// to save comes around (ie the hard disk spins up). |
static long |
CtlStripPeriodicTickle( |
GlobalPtr globalPtr, |
const Rect *statusRect |
) |
{ |
#pragma unused(statusRect) |
OSErr result; |
result = 0; |
if (globalPtr->lastSavedModCount != (**globalPtr->settings).modCount) { |
result = (1 << sdevNeedToSave); |
} |
return (result); |
} |
///////////////////////////////////////////////////////////////////////// |
// This "main" program is called by the Control Strip manager. Note that, |
// unlike the rest of this program, we must define a prototype for main |
// because we can't just declare it "static". |
pascal long main( |
unsigned long message, |
GlobalHandle globalHandle, |
const Rect *statusRect, |
GrafPtr statusPort |
); |
pascal long |
main( |
unsigned long message, |
GlobalHandle globalHandle, |
const Rect *statusRect, |
GrafPtr statusPort |
) |
{ |
#pragma unused(statusPort) |
short savedState; |
register GlobalPtr globalPtr; |
long result; |
Str255 helpString; |
#ifdef __MWERKS__ |
long oldA4 = SetCurrentA4(); |
#endif |
if (globalHandle != nil) { |
/* |
* We have already allocated the global record. Save its lock state, lock |
* the handle, and get the global pointer (so we can write (*globalPtr).something) |
*/ |
savedState = HGetState((Handle) globalHandle); |
HLock((Handle) globalHandle); |
globalPtr = *globalHandle; |
} |
result = 0; /* Unknown message result */ |
switch (message) { |
case sdevInitModule: /* Initialize the module */ |
/* |
* Initialization always sets globalHandle to NULL to avoid the HSetState |
* at the exit routine. If CtlStripInitialize succeeds, it sets the result |
* to the global parameter. |
*/ |
globalHandle = NULL; |
result = CtlStripInitialize(); /* Do the initialization and */ |
break; /* Return global or error code */ |
case sdevCloseModule: /* Clean up before closing */ |
CtlStripCleanUp(globalHandle); |
globalHandle = NULL; |
break; |
case sdevFeatures: /* Return feature bits */ |
result = ( (1<<sdevWantMouseClicks) /* We handle mouse down */ |
| (1<<sdevDontAutoTrack) /* We track the mouse, too */ |
| (1<<sdevHasCustomHelp) /* Custom help string */ |
); |
break; |
case sdevGetDisplayWidth: /* Return display width */ |
result = kIconWidth + width(PicFrame(rightArrowPicture)); |
break; |
case sdevPeriodicTickle: /* Nothing else is happening */ |
result = CtlStripPeriodicTickle(globalPtr, statusRect); |
break; |
case sdevDrawStatus: /* Draw the status bar info */ |
result = CtlStripDrawStatusIcon(globalPtr, statusRect); |
break; |
case sdevMouseClick: /* Status bar click */ |
result = CtlStripMouseClick(globalPtr, statusRect); |
break; |
case sdevSaveSettings: /* Save changed settings */ |
result = CtlStripSavePreferences(globalPtr); |
break; |
case sdevShowBalloonHelp: /* Display custom balloon help */ |
/* |
* We don't really have a custom help string, but this shows how to do it. |
*/ |
SBGetDetachedIndString(helpString, globalPtr->textStrings, kStringHelp); |
SBShowHelpString(statusRect, helpString); |
break; |
default: |
// Ignore unknown messages. |
break; |
} |
if (globalHandle != NULL) /* If we have globals allocated */ |
HSetState((Handle) globalHandle, savedState); /* Restore lock state */ |
#ifdef __MWERKS__ |
SetA4(oldA4); |
#endif |
return (result); |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-01-14