TweetFollow Us on Twitter

Control Strip
Volume Number:10
Issue Number:12
Column Tag:New Apple Technology
Related Info: Help Manager

Writing Control Strip Modules

Extend the strip with your own items

By Mike Blackwell, mkb@cs.cmu.edu

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

About the Author

Mike Blackwell - Mike organizes international symposiums, travels around, and figures out how to program things before Apple gets around to documenting them. He’s also been known to do the occasional piece of contract work. Given that chose to go traveling without sending us a bio to fill this space, he’s in no position to dispute our claim that he spends most of his time working for a huge unnamed charitable foundation, handing out large grants to starving software entrepreneurs.

When Apple introduced the PowerBook 500 and Duo 280 series computers, they also released the latest version of System 7. Along with the usual new goodies that accompany a system release this included the Control Strip extension. The Control Strip is a long thin window which floats above all other applications on the desktop, always easily available to the user. When retracted, the Control Strip tucks out of the way in a corner of the desktop. When extended, it presents a row of separate chiclet tiles, each displaying some aspect of system status, such as remaining battery life or the state of file sharing. Some of the tiles allow the user to click on them to quickly change the state of the system through a popup menu or dialog box.

Each of the status tiles in the Control Strip is controlled by a chunk of code called a Control Strip Module (specifically, a resource of type ‘sdev’), which is generally bundled into a corresponding module file. The user can control which modules are available simply by adding or removing the module files from the Control Strip Modules folder in the system folder. At boot time, the Control Strip extension loads and initializes all of the modules found in this folder.

Control strip modules are fairly easy to write. Since the Control Strip extension itself deals with all of the low level interface to the system, the module programmer is freed from the usual minutiae of Macintosh programming such as toolbox initialization, event loops, grafports, and the likes. However, there are still some tricky areas: resource loading, global memory, loading and saving user preferences, and balloon help, for example. In this article we will use MPW to develop a simple module which displays a rudimentary clock, to learn just what makes a Control Strip module tick.

Before we get started, there are a few caveats to keep in mind:

• Space in the Control Strip is a very limited resource, and should not be needlessly cluttered with modules that might be better implemented as applications. The Control Strip should be reserved for information that the user may need convenient access to at any time.

• Since Control Strip modules are always “running,” they should minimize their impact on system performance by consuming as little memory and as few processor cycles as possible.

• Finally, the current release of the Control Strip extension only runs on the PowerBook machines.

If you would like to play with Control Strip on a desktop Mac, you have two options. If you have access to the Control Strip extension (on the System 7.5 installation disk, for example), Rob Mah has produced a nice little patcher program which will modify Control Strip to run on all platforms (by deleting ‘sdev’ resource number -4064 from the extension, which Control Strip uses as a “PowerBook-only” flag). Also, Sigurdur Asgeirsson has written a shareware Control Strip work-alike called Desktop Strip, which will run on all Macs and use most Control Strip modules (some nice ones are provided). These programs are available at finer archive sites everywhere, try:


/* 1 */
ftp://mac.archive.umich.edu/
    mac/system.extensions/cdev/controlstrippatcher2.0.sit.hqx
ftp://mac.archive.umich.edu/
    mac/system.extensions/cdev/desktopstrip1.0.sit.hqx

They are also available on this issue’s code disk and online sites (see p. 2).

Structure of a Module

All code for the module is contained in a code resource of type ‘sdev’ in the module file. Typically a module file has only one ‘sdev’ resource corresponding to one module. If a module file has more than one ‘sdev’ resource, then each is loaded as a separate module, though this gives the user less flexibility in mixing and matching modules. One possible use for multiple modules in one file would be to have different versions of a similar module, only one of which will load depending on the user’s hardware configuration. As we will see shortly, during the initialization process a module can decide for itself if it should stay loaded.

Besides the ‘sdev’ resource, a module file will typically contain a couple of string and picture resources, plus the usual version, icon, and bundle resources to help the user distinguish the file from a generic document. Unless otherwise required, the programmer should number all resources in the range 256 - 32767 to avoid conflicts with other system resources.

In our example module, SimpleCSClock.r is a Rez file which contains all of the resources for the module except for the ‘sdev’ resource, which will be produced by the C compiler. It starts with a version and signature resource, with information to be displayed by the Finder’s Get Info command. The signature (‘cs!C’ in this case) is also used as the file’s creator ID and as a Preferences key, and should be unique. If you are planning to distribute a module widely, its signature should be registered with Apple to prevent conflicts with other applications.

Next is a ‘PICT’ resource which is a picture of a small right-pointing arrow. This will be drawn in the right-hand side of our tile, to indicate to the user that the module provides a popup menu. A ‘MENU’ resource defines that menu, followed by a couple of strings and string lists which will be described later. Finally, a ‘BNDL’, ‘FREF’, and icon family define the icons for the file that the user will see from the Finder.

The header file SimpleCSClock.h defines the various resource and item numbers in a common location for both the Rez and C programs.

Module Initialization and Global Memory

The Control Strip communicates to the module through a single entry point at the beginning of the module’s ‘sdev’ resource. The prototype for this interface is


/* 2 */
pascal long ControlStripModule(long message, long params,
  Rect *statusRect, GrafPtr statusPort);

message signifies which action the Control Strip wishes the module to perform, from which the module can then dispatch the appropriate routines. These actions include module initialization, display management, queries for module features or display size, periodic “tickles,” and a couple of others.

parms is generally used to store a handle to a parameter or global variables needed by the control strip. The statusRect and statusPort variables are used when drawing the control strip tile.

In addition to this message interface, the Control Strip extension also provides the module with a handful of utility toolbox routines which simplify many actions common to most modules (the utility routine names all begin with the letters “SB” - until just before its release, Control Strip was called Status Bar).

Control Strip sends sdevInitModule as the very first message a module receives. This happens as the control strip extension loads modules from the modules folder at boot time. sdevInitModule asks the module to determine if it can run on this particular platform (generally using various calls to Gestalt to query for necessary features), and then initialize its internal state. If the initialization returns a value less than zero to the Control Strip (because it cannot run on this platform or something failed during initialization), then the module is unloaded and not installed in the Control Strip.

In our example, the Initialize() routine is dispatched on receipt of the initialize message (note that params will always be 0 for this message). After checking the machine features, a chunk of memory and a handle to it is allocated to hold all of the global variables that the module will need to maintain its state. These variables are defined in the Globals structure at the beginning of the program. If the initialization proceeds successfully, then the handle to the globals is returned to the Control Strip. On all subsequent message calls to the module the Control Strip will pass this handle in params, so the globals will always be accessible.

The module file will not be open after the first initialize message has been processed, so any resources that the module needs must be loaded from the file during initialization. This is accomplished with toolbox variants of GetResource. Once each resource is loaded in to memory, its handle is stashed in the globals structure, and the resource is detached so it will always be available. One reason why the module file is not left open after initialization is to conserve power on PowerBooks. If the module were to make GetResource calls on a regular basis it would keep the hard disk turned on a lot.

Once the resources are loaded, the Initialize() routine loads and sets any previously saved user’s preferences. This is accomplished using two of the Control Strip’s utility routines. First, SBGetDetachedIndString() is called to get the string item corresponding to the name of our preferences from the recently detached ‘STR#’ resource helpStrings. This name is then passed to SBLoadPreferences() to read the module’s preferences in to the SavedSettings structure. The first field of the preferences structure is checked against 4-byte module signature to make sure these preferences really belong to this module. Finally, the user’s preferences are copied into the globals structure (in this case, there is only one preference: whether to display the time in 12- or 24-hour format).

Maintaining The Display

Once the module has been initialized and loaded, it can go about its business of maintaining its status display in its tile. This is primarily accomplished through two messages from the Control Strip: sdevPeriodicTickle and sdevDrawStatus. For these two messages (and all others as well), the Control Strip passes the handle to our globals structure in params, a pointer to the drawing rectangle in statusRect, and a pointer to the GrafPort in statusPort (which typically you won’t need to access directly).

Note that each time a message is processed after initialization, the first thing the message handler does is save the state of the globals structure handle, and then lock the structure down so it won’t move while the message is being handled. At the completion of the message, the handle’s state is restored. We don’t simply use HLock()/HUnlock(), because modules can be reentrant - it is possible for the Control Strip to call the module’s message handler before a previous message has completed. If this happens and we inadvertently unlock the handle underneath another message’s feet, all hell could break loose. For this same reason, it’s best not to store variables like parameter blocks in the globals structure, since two messages could be trying to modify them at the same time. Allocate these variables on the stack as needed (by declaring them within the scope of the handler routine), or if they must be global, add an in-use flag.

The module performs most of its work in the sdevPeriodicTickle message. This message is called periodically, but there is no guarantee about how often or when it will be called. The sdevPeriodicTickle message gives the module a chance to check whether its status display is out of date, and if so to update it. Since our clock module updates rather slowly (once every minute), there’s no need to check if the time has changed every single time the tickle message is called - once every couple of seconds is adequate and saves wasting cycles needlessly. To accomplish this, we use the nextTick global variable to signal when we need to check if the display is out of date. If the system clock (returned by TickCount()) is less than nextTick, then we return from the message right away without any further processing.

Otherwise, we call UpdateTime(), which checks to see if the time has changed since it was last displayed, and stores the time to be displayed in the globals structure. If the time has changed, the tile rectangle is erased and DrawDisplay() is called to update the display. DrawDisplay(), in turn, converts the current time in to a string, selects the proper font, sets the drawing point within the statusRect, and calls DrawString() to draw the time of day in the module’s tile. DrawDisplay() then draws the small right-pointing arrow picture just to the right of the time string, which indicates that a popup menu is available. Finally, nextTick is set for two seconds from the current time.

After drawing all of the information in the statusRect, the tickle message handler compares the width of what it has just drawn (returned by DrawDisplay()) to the width of what it had drawn the previous time through (and saved in the width field of the globals structure). If the width has changed then the width field is updated, and sdevResizeDisplay bit is set to be returned to the message call. This bit signals to the Control Strip to shrink or grow the module’s tile appropriately so all of the data is visible in the tile.

When the Control Strip is informed that the module’s tile width has changed, it will next send the module an sdevGetDisplayWidth message request. Since the module is maintaining that width anyway, there’s no need to recompute it, and the module simply returns the width field from the globals structure.

The other message which maintains the module’s displays is sdevDrawStatus. This message is sent by the Control Strip when the display needs to be redrawn, such as for a window update or display hilite, and requests the module to draw its display in its tile. In our case, we do this by calling the DrawTime() routine. Since the module will always be displaying the same information it has already displayed, the width will not have changed and the return from DrawDisplay() can be ignored.

User Interaction

Not all modules require input from the user - they may simply display status information - but our clock module allows the user to select the format of the time presentation. The user can select either a 12- or 24-hour display format by clicking on the module’s tile and making a menu selection from the popup menu that appears. The currently selected option is flagged with a bullet (Figure 1).

Figure 1

The module informed the Control Strip that it wanted to process mouse clicks in its tile by setting the sdevWantMouseClicks and sdevDontAutoTrack bits in its reply to an sdevFeatures message. Whenever the Control Strip detects a click in the module’s tile, it does two things: first, it highlights the tile by darkening its background and shifting the display slightly for a 3-D effect, and then it sends the module an sdevMouseClick message. If the sdevDontAutoTrack feature bit had not been set, then the module would not receive the sdevMouseClick message until after the mouse was clicked and then released in the tile, causing the tile to act as a button instead of a menu title.

Our module responds to the sdevMouseClick message by calling its HandleMouseClick() routine. This routine first checks the current display mode, and marks the appropriate selection in the menu with a bullet character (conveniently pre-defined as sdevMenuItemMark). It then displays the popup menu by calling the Control Strip utility routine SBTrackPopupMenu(). Once a menu item has been selected, the appropriate display mode is flagged in the global structure. If the display mode was changed, then the reply to the sdevMouseClick message has the appropriate bits set to inform the Control Strip that the new user preference needs to be saved and that the balloon help message has changed. DrawDisplay() is called with a flag to inhibit actual drawing to compute the width of the new display. Similar to the tickle message, if the width has changed then the global width is updated and the sdevResizeDisplay reply bit is set to inform Control Strip.

User Preferences

The Control Strip provides the module with a very nice mechanism to save its state between shutdown and restart. This state is stored as a ‘pref’ resource in the Control Strip Preferences file in the system Preferences folder.

At some convenient point after a module has requested to save its settings (by setting the sdevNeedToSave bit in the reply to an sdevMouseClick message, for example) it will receive an sdevSaveSettings message. Control Strip typically waits until some other process has powered up the hard disk, or during shutdown time, to send this message. When our module receives this message, it dispatches its SavePreferences() routine. This allocates memory for the SavedSettings structure, which for our module has only two fields: an identifying signature, and the boolean display mode we want to save. The signature is just a safety feature to make sure the module doesn’t get confused later by inadvertently trying to load some other module’s incorrectly stored resource, and we’ll set it to our file creator type. The display mode is just copied from the current mode in the global structure.

Once the settings structure is set up and filled in, the module needs to pick a name for the resource. For convenience, we already predefined this name as an item in the help strings ‘STR#’ resource, so the module calls SBGetDetachedIndString() to retrieve the name. Then the module calls SBSavePreferences() to save the settings structure as a ‘pref’ resource in the preferences file.

On startup these steps are basically performed in reverse to load and set the previously saved settings, as described in the Module Initialization section.

Balloon Help

The Control Strip allows each module to provide simple balloon help to the user. If balloon help is turned on and the module has set the sdevHasCustomHelp feature bit, then the Control Strip will send the module an sdevShowBalloonHelp message when the mouse is sitting in the module’s tile. The module can then call SBShowHelpString() with a descriptive string to be displayed to the user (Figure 2). There is no easy way to provide more detailed help for things like menu items.

Figure 2

If the module changes its mode while handling a tickle or mouse click message, it can set the sdevHelpStateChanged bit in its reply, telling the Control Strip to issue a new sdevShowBalloonHelp message to cause the help string to change.

To give the user more information about the module file, our module has a Finder help string resource (a ‘STR ’ with an ID of -16397). If the user double clicks on the module’s icon from the Finder, the Finder will display a dialog box containing this string, telling the user what the module is and how to use it. We’ve also set a Help Manager resource pointing to this same string. The idea is that balloon help from the Finder will display the string, but this does not currently work because the Finder doesn’t know about ‘sdev’ files so it always displays a generic message. But it only costs a few bytes, and maybe one day the Finder will do the right thing.

Additional Resources

Hopefully, this article has given you enough information to go out and write your own Control Strip modules. The full documentation for the Control Strip API can be found, oddly enough, in Chapter 5 of Apple’s PowerBook 520/520c/540/540c Developer Notes. You can find a copy on any recent Develop Bookmark CD (a very worthwhile addition to your programming library to complement your MacTech magazines). It will also soon be released as a tech note.

The Control Strip header file ControlStrip.h can be found on Apple’s ETO CD #15 and later. If you don’t have access to the “official” version, Rob Mah provides a home grown version along with his patcher. Note that there is a slight inconsistency between Apple’s and Mah’s definitions of the return bits for tickle and feature messages. Apple defines the bit number (for example, sdevHasCustomHelp = 2) while Mah defines the bit position (sdevHasCustomHelp = (1 << 2)). The code in this article uses Apple’s conventions.

Many thanks to Steve Christensen at Apple, Control Strip’s author, for reviewing this article.

SimpleCSClock.h


/* 3 */
#define kSignature 'cs!C'

#define kArrowPictID 256

#define kConfigMenuID256
#define k12HourCmd 1
#define k24HourCmd 2

#define kHelpStringsID    256
#define kPrefNameStr 1
#define kHelp12StringStr  2
#define kHelp24StringStr  3

SimpleCSClock.c


/* 4 */
/* SimpleCSClock - A simple control strip clock module         
 By Mike Blackwell, mkb@cs.cmu.edu 
*/

#include <GestaltEqu.h>
#include <Fonts.h>
#include <Memory.h>
#include <Packages.h>
#include <Resources.h>
#include <Strings.h>
#include <SysEqu.h>
#include <ToolUtils.h>
#include "ControlStrip.h"
#include "SimpleCSClock.h"


// How often to check if the display needs to be updated (in ticks)
#define INTERVAL (2 * 60) // 2 seconds

// Display font information
#define DISPLAY_FONT monaco
#define DISPLAY_FONT_SIZE 9
#define DISPLAY_FONT_FACE 0
#define DISPLAY_MARGIN    3 // Blank space to left and right of text

typedef struct Globals {
 PicHandlearrowPicture;   // Picture to show we have a popup menu
 short  arrowWidth, 
 arrowHeight;    // Size of arrow
 short  fontHeight;// Ascender height of display font
 Handle helpStrings; // Balloon help strings for each state
 MenuHandle configMenu;   // Menu to select display options
 int    width;   // Width of display
 Booleanshow12Hour;// True for 12 hour display
 int    displayTime; // Time that is currently being displayed
 //  (in minutes since midnight)
 unsigned long nextTick;  // When to next update the display
} Globals, *GlobalPtr, **GlobalHandle;


typedef struct SavedSettings {
 OSType signature; // Signature to verify that prefs are for
 //  this module
 Booleanshow12Hour;// True for 12 hour display
} SavedSettings;


/* Prototypes 9/
long    Initialize(void);
void    CleanUp(GlobalHandle globHand);
long    HandleMouseClick(GlobalPtr globPtr, Rect *statusRect);
short   SavePreferences(GlobalPtr globPtr);
Boolean UpdateTime(GlobalPtr globPtr);
intDrawDisplay(GlobalPtr globPtr, Rect *statusRect, 
  Boolean drawit);
Boolean CheckFeatures(void);


pascal long main(long message, 
 long params, 
 Rect *statusRect, 
 GrafPtr statusPort)
{
#pragma unused(statusPort)
 char   savedState;
 GlobalPtrglobPtr;
 long   result;
 int    current_width;
 Str255 helpString;

 if (params > 0) { // If we have globals allocated,
 savedState = HGetState((Handle)params);                       
 //  save the handle state,
 HLock((Handle)params);   //  lock the handle to the globals,
 globPtr = *(GlobalHandle)params;  //  and point to globals directly
 }

 result = 0;// Return zero for unknown messages

 switch (message) {

 case sdevInitModule:// Initialize the module
 result = Initialize();
 break;

 case sdevCloseModule:    // Clean up before being closed
 CleanUp((GlobalHandle)params);
 params = 0L;    // Handle is gone now
 break;

 case sdevFeatures:// Return feature bits
 result = (1 << sdevWantMouseClicks) | \
  (1 << sdevDontAutoTrack)| \
  (1 << sdevHasCustomHelp);
 break;

 case sdevGetDisplayWidth:// Return display width
 result = globPtr->width;
 break;

 case sdevPeriodicTickle: // Periodic tickle when nothing 
 // else is happening
 // Time to update display yet?
 if (TickCount() >= globPtr->nextTick) {                       
 if (UpdateTime(globPtr)) { // Check if time has changed
 EraseRect(statusRect);   // Yep, erase the old
 current_width = DrawDisplay( globPtr, 
   statusRect, 
 true);// And draw the new
 // If the display width changed, let the control strip know
 if (globPtr->width != current_width) {
 globPtr->width = current_width;
 result = (1 << sdevResizeDisplay);
 }
 }
 globPtr->nextTick = TickCount() + INTERVAL;
 }
 break;

 case sdevDrawStatus:// Update the display
 (void)DrawDisplay(globPtr, statusRect, true);
 break;

 case sdevMouseClick:// User clicked on the module's 
 // display area in the status bar
 result = HandleMouseClick(globPtr, statusRect);
 break;

 case sdevSaveSettings:   // Save changed settings
 result = SavePreferences(globPtr);
 break;

 case sdevShowBalloonHelp:// Display custom balloon help
 SBGetDetachedIndString(&helpString, 
 globPtr->helpStrings,
 globPtr->show12Hour ? kHelp12StringStr : kHelp24StringStr);
 SBShowHelpString(statusRect, &helpString);
 break;
 }

 if ((long)params > 0)    // If we have globals allocated,
 HSetState((Handle)params, savedState);//  restore the locked/unlocked 
state

 return(result);
}


Initialize
long Initialize(void)
{
 long   result;
 GlobalPtrglobPtr;
 GlobalHandle  globHand;
 FontInfo fontInfo;
 Str255 prefsResourceName;
 SavedSettings **preferences;

 result = -1;    // Assume failure

 if (!CheckFeatures()) 
 return(result);


 if (! (globHand = 
 (GlobalHandle)NewHandleClear(sizeof(Globals))))
 goto done; // Allocate the globals


 HLock((Handle)globHand); // Lock the globals while using them
 globPtr = *globHand;//  and get a pointer to them

//  Load and detach the ‘up arrow’ picture

 if (! (globPtr->arrowPicture = GetPicture(kArrowPictID))) 
 goto done;


 DetachResource((Handle)globPtr->arrowPicture);

// Compute size of arrow picture

 globPtr->arrowHeight = 
 (**globPtr->arrowPicture).picFrame.bottom
 - (**globPtr->arrowPicture).picFrame.top;
 globPtr->arrowWidth = 
 (**globPtr->arrowPicture).picFrame.right 
 - (**globPtr->arrowPicture).picFrame.left;

// Compute size of display font

 TextFont(DISPLAY_FONT);
 TextSize(DISPLAY_FONT_SIZE);
 TextFace(DISPLAY_FONT_FACE);
 GetFontInfo(&fontInfo);
 globPtr->fontHeight = fontInfo.ascent;

//  Load and detach the configuration menu

 if (! (globPtr->configMenu = GetMenu(kConfigMenuID))) 
 goto done;


 DetachResource((Handle)globPtr->configMenu);

//  Load and detach the help strings

 if (! (globPtr->helpStrings = 
 Get1Resource('STR#', kHelpStringsID))) 
 goto done;


 DetachResource(globPtr->helpStrings);

//  Get the module's saved preferences, if any, and configure the module

 SBGetDetachedIndString(&prefsResourceName, 
  globPtr->helpStrings, kPrefNameStr);
 if (! SBLoadPreferences(&prefsResourceName, 
   (Handle *)&preferences) 
 &&
 ((**preferences).signature == kSignature)) {
 globPtr->show12Hour = (**preferences).show12Hour;
 }

 globPtr->nextTick = 0;   // Do first update right away
 globPtr->width = 0;
 globPtr->displayTime = 0;// Haven't displayed a time yet

 HUnlock((Handle)globHand); // Unlock the globals

 result = (long)globHand; // Return the handle to the 
 // globals as the result

done:
 return(result); // Return either a handle or 
 // an error code
}


CleanUp
void CleanUp(GlobalHandle globHand)
{
 GlobalPtrglobPtr;

 if ((long)globHand <= 0) return;

 HLock((Handle)globHand);
 globPtr = *globHand;

 if (globPtr->arrowPicture) 
 DisposeHandle((Handle)globPtr->arrowPicture);

 if (globPtr->configMenu) 
 DisposeMenu(globPtr->configMenu);

 if (globPtr->helpStrings) 
 DisposeHandle(globPtr->helpStrings);

 DisposeHandle((Handle)globHand);
}



HandleMouseClick

long HandleMouseClick(GlobalPtr globPtr, Rect *statusRect)
{
 short  menuItem;
 long   result;
 int    new_width;
 Booleanmode_changed;

 // Check off the appropriate items in the popup menu

 if (globPtr->show12Hour) {
 SetItemMark(globPtr->configMenu, k12HourCmd, 
  sdevMenuItemMark);
 SetItemMark(globPtr->configMenu, k24HourCmd, noMark);
 } else {
 SetItemMark(globPtr->configMenu, k24HourCmd, 
  sdevMenuItemMark);
 SetItemMark(globPtr->configMenu, k12HourCmd, noMark);
 }

 result = 0;

 // Display the popup menu

 menuItem = SBTrackPopupMenu(statusRect, 
  globPtr->configMenu);

 // Handle the menu selection

 mode_changed = false;

 switch (menuItem) {
 case k12HourCmd:
 if (!globPtr->show12Hour) {
 globPtr->show12Hour = true;
 mode_changed = true;
 }
 break;

 case k24HourCmd:
 if (globPtr->show12Hour) {
 globPtr->show12Hour = false;
 mode_changed = true;
 }
 break;
 }

 // If the mode changed, calculate new display width 
 // and let CS know what happened

 if (mode_changed) {
 result = (1 << sdevNeedToSave) 
 | (1 << sdevHelpStateChange);
 new_width = DrawDisplay(globPtr, statusRect, false);
 if (globPtr->width != new_width) {
 globPtr->width = new_width;
 result |= (1 << sdevResizeDisplay);
 }
 }

 return(result);
}


SavePreferences
short SavePreferences(GlobalPtr globPtr)
{
 short  result;
 SavedSettings **preferences;
 Str255 prefsResourceName;

 preferences = (SavedSettings**)NewHandle(                     
 sizeof(SavedSettings));

 if (! (result = MemError())) {  // Allocate a block to hold the settings

 // Include a signature to verify it’s ours
 (**preferences).signature = kSignature; 

 (**preferences).show12Hour = globPtr->show12Hour;

 // Get the name of the preferences resource
 SBGetDetachedIndString(prefsResourceName, 
 globPtr->helpStrings, kPrefNameStr);

 // Save the settings in the Control Strip's preferences file
 result = SBSavePreferences(prefsResourceName, 
 (Handle)preferences);

 DisposeHandle((Handle)preferences); // Get rid of the block
 }

 return(result);
}



UpdateTime

// Compute the current time in seconds since midnight. If it's changed 
since we
// last displayed the time, return true.

Boolean UpdateTime(GlobalPtr globPtr)
{
 unsigned long now;
 int    dispTime;

 GetDateTime(&now);// Get seconds since epoch

// Compute minutes since midnight
 dispTime = (now % (60 * 60 * 24)) / 60;     

// Has the time changed yet?
 if (dispTime != globPtr->displayTime) {                       
 globPtr->displayTime = dispTime;  // Yes
 return(true);   // Need to update display
 } else {
 return(false);
 }
}


NumStr
// Stuff the ascii string representing the number in to the destination 
string.
// Number range is 0 - 99. If pad is true, pad with a zero if necessary.
// Return pointer to end of string.

char *NumStr(char *dest, int num, Boolean pad)
{
 if (num < 0) 
 num = 0;
 if (num > 99) 
 num = 99;
 if (num >= 10) {
 *dest++ = (num / 10) + '0';
 num %= 10;
 } else if (pad) {
 *dest++ = '0';
 }
 *dest++ = num + '0';
 *dest = '\0';
 return(dest);
}



DrawDisplay
// Draw the time specified in displayTime. If drawit is false, don't 
actually
// do any drawing. Returns the width of the display

int DrawDisplay( GlobalPtr globPtr, Rect *statusRect, 
 Boolean drawit)
{
 int    result;
 char   buf[10], 
 *bptr;
 int    hours, minutes;
 Booleanafternoon;
 int    width, offset;
 Rect   arrowRect;

 result = 0;

 hours = globPtr->displayTime / 60;
 if (globPtr->show12Hour) {
 afternoon = (hours >= 12);
 if (afternoon) 
 hours -= 12;
 if (hours == 0) 
 hours = 12;
 }
 minutes = globPtr->displayTime % 60;

 bptr = buf;

 if (globPtr->show12Hour) {
 bptr = NumStr(bptr, hours, false);
 *bptr++ = ':';
 bptr = NumStr(bptr, minutes, true);
 *bptr++ = (afternoon) ? 'P' : 'A';
 *bptr++ = 'M';
 *bptr = '\0';
 } else {
 bptr = NumStr(bptr, hours, true);
 *bptr++ = ':';
 bptr = NumStr(bptr, minutes, true);
 *bptr = '\0';
 }
 c2pstr(buf);

 // Draw the time string a little away from the right edge and centered 
vertically
 // Compute top/bottom margin. -1 is tweak to make it look just right...
 TextFont(DISPLAY_FONT);
 TextSize(DISPLAY_FONT_SIZE);
 TextFace(DISPLAY_FONT_FACE);
 if (drawit) {
 offset = (statusRect->bottom - statusRect->top 
 - globPtr->fontHeight) / 2 - 1;
 MoveTo(statusRect->left + DISPLAY_MARGIN, 
 statusRect->top + offset + globPtr->fontHeight);
 DrawString(buf);
 }

 width = DISPLAY_MARGIN + StringWidth(buf) - 1;
 
 // Draw the right arrow to show that the module has a popup menu
 if (drawit) {
 arrowRect.left = statusRect->left + width;
 arrowRect.right = arrowRect.left + globPtr->arrowWidth;
 arrowRect.top = statusRect->top +
 (statusRect->bottom - statusRect->top 
 - globPtr->arrowHeight) / 2;
 arrowRect.bottom = arrowRect.top + globPtr->arrowHeight;
 DrawPicture(globPtr->arrowPicture, &arrowRect);
 }

 width += globPtr->arrowWidth;

 return(width);
}


CheckFeatures
// Check if this machine is capable of running this control strip (probably
// by using Gestalt). This simple example will run on any Mac, so just 
return
// true.

Boolean CheckFeatures(void)
{
 return(true);
}








  
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Apple iMovie 10.1.6 - Edit personal vide...
With an all-new design, Apple iMovie lets you enjoy your videos like never before. Browse your clips more easily, instantly share your favorite moments, and create beautiful HD movies and Hollywood-... Read more
TechTool Pro 9.5.1 - Hard drive and syst...
TechTool Pro has long been one of the foremost utilities for keeping your Mac running smoothly and efficiently. With the release of version 9, it has become more proficient than ever. TechTool... Read more
Jamf Pro 9.99.0 - Powerful sysadmin/ente...
Jamf Pro (formerly Casper Suite) is the EMM tool that delights IT pros and the users they support by delivering on the promise of unified endpoint management for Apple devices. At Jamf, connecting... Read more
VueScan 9.5.78 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Adobe Lightroom 6.10.1 - Import, develop...
Adobe Lightroom is available as part of Adobe Creative Cloud for as little as $9.99/month bundled with Photoshop CC as part of the photography package. Lightroom 6 is also available for purchase as a... Read more
iPhoto Library Manager 4.2.7 - Manage mu...
iPhoto Library Manager allows you to organize your photos among multiple iPhoto libraries, rather than having to store all of your photos in one giant library. You can browse the photos in all your... Read more
Smultron 9.4 - Easy-to-use, powerful tex...
Smultron 9 is an elegant and powerful text editor that is easy to use. Use it to create or edit any text document. Everything from a web page, a note or a script to any single piece of text or code.... Read more
TextSoap 8.4 - Automate tedious text doc...
TextSoap can automatically remove unwanted characters, fix up messed up carriage returns, and do pretty much anything else that we can think of to text. Save time and effort. Be more productive. Stop... Read more
Merlin Project 4.2.3 - $349.00
Merlin Project is the leading professional project management software for OS X. If you plan complex projects on your Mac, you won’t get far with a simple list of tasks. Good planning raises... Read more
QuarkXPress 13.0.0.0 - Desktop publishin...
QuarkXPress 2017 is the new version that raises the bar for design and productivity. With non-destructive graphics and image editing directly within your layout, you no longer have to choose between... Read more

Latest Forum Discussions

See All

Goat Simulator PAYDAY (Games)
Goat Simulator PAYDAY 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: ** IMPORTANT - SUPPORTED DEVICES **iPhone 4S, iPad 2, iPod Touch 5 or better Goat Simulator: Payday is the most... | Read more »
Zombie Gunship Survival Beginner's...
The much anticipated Zombie Gunship Survival is here. In this latest entry in the Zombie Gunship franchise, you're tasked with supporting ground troops and protecting your base from the zombie horde. There's a lot of rich base building fun, and... | Read more »
Mordheim: Warband Skirmish (Games)
Mordheim: Warband Skirmish 1.2.2 Device: iOS Universal Category: Games Price: $3.99, Version: 1.2.2 (iTunes) Description: Explore the ruins of the City of Mordheim, clash with other scavenging warbands and collect Wyrdstone -... | Read more »
Mordheim: Warband Skirmish brings tablet...
Legendary Games has just launched Mordheim: Warband Skirmish, a new turn-based action game for iOS and Android. | Read more »
Magikarp Jump splashes onto Android worl...
If you're tired ofPokémon GObut still want something to satisfy your mobilePokémon fix,Magikarp Jumpmay just do the trick. It's out now on Android devices the world over. While it looks like a simple arcade jumper, there's quite a bit more to it... | Read more »
Purrfectly charming open-world RPG Cat Q...
Cat Quest, an expansive open-world RPG from former Koei-Tecmo developers, got a new gameplay trailer today. The video showcases the combat and exploration features of this feline-themed RPG. Cat puns abound as you travel across a large map in a... | Read more »
Jaipur: A Card Game of Duels (Games)
Jaipur: A Card Game of Duels 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** WARNING: iPad 2, iPad Mini 1 & iPhone 4S are NOT compatible. ** *** Special Launch Price for a limited... | Read more »
Subdivision Infinity (Games)
Subdivision Infinity 1.03 Device: iOS Universal Category: Games Price: $2.99, Version: 1.03 (iTunes) Description: Launch sale! 40% Off! Subdivision Infinity is an immersive and pulse pounding sci-fi 3D space shooter. https://www.... | Read more »
Clash of Clans' gets a huge new upd...
Clash of Clans just got a massive new update, and that's not hyperbole. The update easily tacks on a whole new game's worth of content to the hit base building game. In the update, that mysterious boat on the edge of the map has been repaired and... | Read more »
Thimbleweed Park officially headed to iO...
Welp, it's official. Thimbleweed Park will be getting a mobile version. After lots of wondering and speculation, the developers confirmed it today. Thimbleweed Park will be available on both iOS and Android sometime in the near future. There's no... | Read more »

Price Scanner via MacPrices.net

Huawei Unveils New ‘Business-Styled’ MateBook...
Huawei has introduced a trio of new MateBook laptops, expanding its mobile portfolio and building on its success in delivering attractive and powerful high-end devices. The company claims the HUAWEI... Read more
Deal! Gold 12-inch 1.2GHz Retina MacBook for...
Amazon has the 2016 Gold 12″ 1.2GHz Retina MacBook (MLHF2LL/A) on sale for $350 off MSRP for a limited time. Shipping is free: - 12″ 1.2GHz Gold Retina MacBook: $1249.99 $350 off MSRP We expect this... Read more
13-inch 2.0GHz MacBook Pros on sale for $100...
B&H has the non-Touch Bar 13″ 2.0GHz MacBook Pros in stock today and on sale for $100 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 13″ 2.0GHz MacBook Pro Space... Read more
15-inch 2.2GHz Retina MacBook Pro, Apple refu...
Apple has Certified Refurbished 2015 15″ 2.2GHz Retina MacBook Pros available for $1699. That’s $300 off MSRP, and it’s the lowest price available for a 15″ MacBook Pro. An Apple one-year warranty is... Read more
Apple refurbished 9-inch and 12-inch iPad Pro...
Apple has Certified Refurbished 9″ and 12″ Apple iPad Pros available for up to $160 off the cost of new iPads. An Apple one-year warranty is included with each model, and shipping is free: - 32GB 9″... Read more
Apple Certified Refurbished iMacs available f...
Apple has Certified Refurbished 2015 21″ & 27″ iMacs available for up to $350 off MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: - 21″ 3.... Read more
Sale! 15-inch 2.6GHz Silver Touch Bar MacBook...
DataVision has the 15″ 2.6GHz Silver Touch Bar MacBook Pro (MLW72LL/A) on sale for $2199 including free shipping. Their price is $200 off MSRP, and it’s the lowest price available for this model (... Read more
A Kaby Lake Processor Upgrade For The MacBook...
Now they tell me! Well, actually Apple hasn’t said anything official on the subject, but last week Bloomberg News’s Mark Gurman and Alex Webb cited unnamed “people familiar with the matter”... Read more
Kodak’s Camera-First Smartphone EKTRA Launche...
The Eastman Kodak Company and Bullitt Group have announced the availability of a U.S. GSM version of the KODAK EKTRA Smartphone. The U.S. launch coincides with a software update addressing requests... Read more
Apple Launches App Development Curriculum for...
Apple today launched a new app development curriculum designed for students who want to pursue careers in the fast-growing app economy. The curriculum is available as a free download today from Apple... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Systems Engineer - California Polyte...
Cal Poly, San Luis Obispo Apple Systems Engineer Department: ITS - Customer & Tech Support (134900) College/Division: Academic Affairs Salary Range: Position Read more
Best Buy *Apple* Computing Master - Best Bu...
**508718BR** **Job Title:** Best Buy Apple Computing Master **Location Number:** 001526-Odessa-Store **Job Description:** **What does a Best Buy Apple Computing Read more
Data Engineer - *Apple* Media Products - Ap...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.