Tools Plus Pro
Volume Number: 15 (1999)
Issue Number: 10
Column Tag: Programming
Tools Plus Pro Libraries + Framework
by Thurman Gillespy III, M.D., Seattle, WA
Mac Programming Fast and Easy
Overview
The Tools Plus Pro 5 libraries and (optional) application framework greatly simplify the development of Macintosh applications and plug-ins. Tools Plus provides a high level API that simplifies and consolidates many Mac OS toolbox routines. Tools Plus can be programmed in C, C++, and Pascal, and covers the user interface and event processing components of the Mac Toolbox. Tools Plus replaces the classic Macintosh event loop with an event dispatcher. Tools Plus interface components include windows (including dialogs, floating windows, and a tool bar), cursors, menus, pop-up menus, buttons, scroll bars, sliders, panels, and list boxes. Tools Plus fully supports the Appearance Manager, but even without it, Tools Plus can still develop great-looking applications. Tools Plus supports most of the compilers in common use today.
Tools Plus is developed and distributed by Water's Edge Software, Ontario, Canada. A fully featured evaluation kit (9.4M) and a smaller demo kit (676K) are available online.
Water's Edge Software
<http://www.interlog.com/~wateredg/>
How I Use Tools Plus
I've been using Tools Plus since early 1996 for the development of a medical image display program, Dr Razz. The program uses many Tools Plus interface components, including the tool bar, floating windows, sliders, picture buttons, cursor tables, and edit fields. Two great ways to see what Tools Plus can do are to carefully study the Tools Plus Demo App (included in the evaluation and demo kits) to see all the user interface components in action, and also to look at my program to see Tools Plus used to develop a complete Macintosh application.
Dr Razz
<http://www.dr-razz.com/>
The Out of Box Experience
The Tools Plus distribution includes the Tools Plus 680x0 and PPC libraries for C/C++ and Pascal. Separate libraries for applications and plug-ins are included. The 680x0 libraries have both large and small code model versions. Also included is an application framework, actually a stub application outline. Tools Plus supports CodeWarrior (as far back as version 6), CodeWarrior Pro, Symantec C/C++, THINK C, and THINK Pascal, and separate libraries and project files are included for these systems.
Extras include a printed and electronic user manual (in both eDoc and PDF formats) and 11 different tutorial projects that illustrate a particular Tools Plus feature. A demo application (the Tools Plus Demo App) and source code are included that nicely illustrate the Tools Plus event model and user interface components.
Tools Plus also ships with SuperCDEFs, a nice collection of 3D user interface components that you can use to develop a great-looking user interface without the Appearance Manager. SuperCDEFs includes buttons, sliders, tabs, hierarchy triangles, and progress thermometers.
Figure 1.Some of the many SuperCDEFs components.
Also bundled are the Infinity Windoid WDEF, miscellaneous icons, animated cursors, fonts, and other resources.
Figure 2.A floating window created with the Infinity Windoid WDEF.
Tools Plus Professional retails for $249, and includes a one year subscription with three annual updates, as well as a license to include the libraries in your finished product. The subscription also includes e-mail access to the Tools Plus Developer Forum, Water's Edge News, and technical support. Support questions are usually promptly answered. Bug notices are automatically e-mailed to registered developers, which is a very nice service. Tools Plus Lite (680x0 libraries for CodeWarrior only) is available for a limited time for $99. Academic discounts are available. Tools Plus Pro and Tools Plus Lite are available from Water's Edge and DevDepot.
Some Assembly Required
So how do you develop an application or plug-in with Tools Plus? A great place to get started is to use the sample project and the application framework files appropriate for your development system. The application framework is composed of 8 files (and associated headers) that form a basic application shell, with lots of "insert your code here to handle..." comments. The framework is entirely optional, and you can use your own coding style as you see fit.
You can divide project development into the following steps.
- develop DLOG, MENU and other resources for user interface
- write your application specific initialization routines
- all InitToolsPlus with appropriate parameters
- write the application event handler
- write event handlers for each window type (optional, but highly recommended)
- write the event filter (optional, but the recommended method of performing menu updates)
- write routines to handle application and window events
- link the appropriate Tools Plus library/libraries for your development system
- compile and enjoy!
Batteries Not Included
Tools Plus is not a complete application/plug-in development environment, although the libraries handle most of the event processing and user interface aspects of an application or plug-in. There is no support for memory management (except for edit fields), file operations, open/save file dialogs, low-level debug support, or network operations. Support for balloon help is incomplete, and is completely absent for standard algorithms such as linked lists. There is no Tools Plus API support for print operations, but the application framework has useful print starter code. Tools Plus does not come with an interface editor like CodeWarrior Constructor, REALbasic or AppMaker - for serious work you will need Resorcerer from Mathemaesthetics. Tools Plus has basic list box support, but Water's Edge recommends purchasing StoneTable[TM] from StoneTablet Publishing for advanced list box operations. For advanced text handling and display you should consider the popular WASTE text-editing library from Marco Piovanelli, and for file operations you should consider the MoreFiles routines from Jim Luther.
- Resorcerer
- <http://www.mathemaesthetics.com/>
- StoneTable[TM]
- <http://www.teleport.com/~stack/>
- WASTE
- <http://www.merzwaren.com/waste/>
- MoreFiles
- <http://members.aol.com/jumplong/>
USING The Tools Plus System
The Tools Plus libraries handle events and manage the Tools Plus interface components. To get started, you initialize the libraries, then let them begin processing events.
Press Here To Start
Getting the Tools Plus libraries up and running is easy. Your application's main function may only contain 3 or 4 statements.
TPEventRecord Event;
void main(void)
{
// initialize Tools Plus
InitToolsPlus(&Event, MyEventHandler, MyEventFilter,...);
// initialize your data structures, etc.
InitMyApp();
// tell Tools Plus to begin processing Mac OS events
ProcessEvents();
// your clean up code, if any (might be within the event handler)
MyAppCleanUp();
}
InitToolsPlus gets the ball rolling, and should be called as soon as possible. It initializes the Tools Plus internal data structures, handles Macintosh startup chores for you, lets you set your event handling routines, and has a number of optional parameters.
The prototype is:
pascal Boolean InitToolsPlus (
TPEventRecord *EventPtr,
const EventHandlerProcPtr EventHandler,
const EventFilterProcPtr EventFilter,
short MoreHandles,
short MaxWindows,
short TEBufferSize,
long InitSpec);
InitToolsPlus takes a pointer to a TPEventRecord, the Tools Plus structure that contains the Tools Plus event information. Typically, this variable has global scope. The EventHandler and EventFilter parameters are discussed below. MoreHandles is the number of master handle blocks to allocate at startup. MaxWindows is the maximum number of windows that can be opened at the same time. Tools Plus preallocates window storage (about 300 bytes/window) and is limited to 250 windows at most (far more than any application could reasonably use). TEBufferSize specifies the size of text editing buffers (maintained by Tools Plus) used for cutting, copying, pasting, and storing copies of text for Edit menu Undo/Redo support, and should be between 255 and 32767 bytes.
Spec is the specification parameter that specifies various start up tasks and Tools Plus behavior. There are 19 different parameters available. In my application, I initialize ToolsPlus for 12 master handle blocks, 60 maximum windows, and an edit buffer of 4096 bytes. The specifications I use are initMacToolbox (initialize the Mac toolbox), initUseColor (use Color QuickDraw if available), initUseTEScrap (maintain a local TextEdit scrap), initDontUnloadDeskScrap (don't unload the desk scrap to disk) and initPureAppearanceManager (use Appearance Manager controls if available, resolve certain appearance ambiguities).
InitToolsPlus(&Event, EventHandler, EventFilter,
12, 60, 4096,
initMacToolbox + initUseColor +
initUseTEScrap + initDontUnloadDeskScrap +
initPureAppearanceManager);
InitToolsPlus also flushes the Mac OS event queue, seeds the random number generator and calls MaxApplZone. InitToolsPlus is typical of many Tools Plus routines: highly configurable, and able to handle of lot of the Mac OS "dirty work" for you.
After initializing Tools Plus, perform your application-specific initialization routines. Next, call ProcessEvents to tell Tools Plus to start processing Mac OS events into Tools Plus events. Tools Plus events are then dispatched to your EventHandler and WindowHandler routines (see below).
That's it! After three or four lines of code, your application is running and ready to process events.
The Tools Plus Event Model
After you call ProcessEvents, Tools Plus takes raw Mac OS events, translates them into very high-level Tools Plus events, and dispatches the events to your event handlers. Your event handlers parse the TPEventRecord, and then execute your application-specific code. The Tools Plus event model saves you hundreds of lines of code compared with handling the Mac OS events yourself.
The Tools Plus events are listed in Table 1.
doNothing |
No event (background processing done here) |
doChgWindow |
User clicked in an inactive window |
doRefresh |
A window has to be refreshed |
doGoAway |
The close box was clicked |
doButton |
Button was clicked |
doMenu |
Menu was selected |
doKeyDown |
A keyboard key was pressed |
doAutoKey |
A keyboard key is auto-repeating |
doKeyUp |
A keyboard key was released |
doClickToFocus |
Mouse clicked in an object that wants the keyboard focus (inactive field, etc.) |
doScrollBar |
Mouse clicked in a scroll bar |
doListBox |
Some sort of List Box activity |
doClick |
Mouse click/drag [1..3] |
doPopUpMenu |
Pop-up menu was selected |
doPictButton |
Picture button activity |
doClickControl |
Mouse clicked in a custom control |
doManualEvent |
Manually processed events |
doMoveWindow |
A window was dragged by user |
doGrowWindow |
A window was "grown" by user |
doClickDesk |
Mouse clicked in the desk top |
doZoomWindow |
Zoom box was clicked by user |
doSuspend |
Appl. suspended (in background) |
doResume |
Appl. resumed (now active appl.) |
doChgInField |
Editing field contents was changed |
doPreRefresh |
Refresh window before T+ objects |
doActivate |
Window was activated |
doDeactivate |
Window was deactivated |
doMoveCursor |
Cursor has entered a new Cursor Zone |
doKeyInControl |
Key stroke was applied to a control |
doChgMonitorSettings |
Monitor settings were changed |
doOpenApplication |
App launched with no open docs |
doOpenDocuments |
Your app should open 1 or more docs |
doPrintDocuments |
Your app should print 1 or more docs |
doQuitApplication |
Your app should quit |
Table 1. Tools Plus Events.
In many cases, the Tools Plus event represents the summation of multiple fields in a low-level Mac OS event, which greatly simplifies your code. For example, it might take half a dozen or so steps to process a Mac OS event to determine a mouse click occurred in the close box, but Tool Plus dispatches only one event: doGoAway.
Every application or plug-in has a main event handler.
pascal void EventHandler(Ptr myCustomData);
The address of this event handler is passed as a parameter to InitToolsPlus. Your event handler typically looks like this:
// application event handler
// this handler presumes all window events are handled by window event handlers
pascal void EventHandler(Ptr myCustomData)
{
switch (Event.what) {
case doNothing:
DoBackgroundProcessing();// do background processing here
break;
case doChgWindow:
ActivateWindow(Event.Window);
break;
case doMenu:
HandleDoMenu();
break;
case doManualEvent:
break;
case doSuspend:
HandleSuspendApp();
break;
case doResume:
HandleResumeApp();
break;
case doMoveCursor:
HandleMoveCursor();
break;
case doChgMonitorSettings:
HandleChangeMonitor();
break;
case doKeyDown:
case doAutoKey:
case doKeyUp:
// most applications don't handle keyboard events when no windows are open
break;
// Apple Events
case doOpenApplication:
OpenApplication();
break;
case doOpenDocuments:
OpenDocuments();
break;
case doPrintDocuments:
PrintDocuments();
break;
case doQuitApplication:
QuitApp();
break;
}
}
After branching to the code that handles the specific Tools Plus event, you often parse the TPEventRecord further for additional information. For example, handling a File/Open menu command might look like this:
// handle menu event
void HandleDoMenu(void)
{
switch (Event.Menu) {
case iFile:
switch (Event.Menu.Item) {
case iNew:
HandleNewDocument();
break;
case iOpen:
HandleOpenDocument();
break;
case iClose:
HandleCloseDocument();
break;
case iPreferences:
HandlePreferences();
break;
case iQuit:
HandleQuitApp();
break;
break;
case iEdit:
switch (Event.Menu.Item) {
case iUndo:
HandleUndo();
break;
case iCut:
HandleCut();
break;
case iCopy:
HandleCopy();
break;
case iPaste:
HandlePaste();
break;
case iClear:
HandleClear();
break;
}
break;
}
}
Window Event Handlers
An important feature of Tools Plus is the option to have window-related events sent to a window event handler. I highly recommend you utilize window handlers since they allow you to nicely modularize your window-related code. Typically, different types of windows have separate window event handlers, but windows of the same type will share the same handler. For example, a tool palette floating window would have a separate window handler, while all document windows might share the same handler.
To associate a window event handler with a window, you create a UniversalProcPtr that points to the handler, then call SetWindowEventHandler to make the association. The example below is derived from my Preferences dialog. If there aren't too many dialogs in the program, I prefer to create the dialogs at program startup and keep them hidden until needed. The window event handler won't be called until the window is made visible and begins to receive user interactions.
EventHandlerUPP prefsDlgEventHandlerUPP;
pascal void SetWindowEventHandler (short Window,
EventHandlerUPP EventHandler);
pascal EventHandlerUPP NewEventHandlerProc (
EventHandlerProcPtr userRoutine);
// initialize the Preferences window at app start up, but don’t make visible
void InitPrefsWindow(void)
{
// create dialog from DLOG resource
LoadDialog(kPrefsWindowID, kPrefsWindowResID);
Init_Prefs_Dialog(); // set button, check boxes, etc per user prefs
prefsDlgEventHandlerUPP =
NewEventHandlerProc(PrefsDlgEventHandler);
SetWindowEventHandler(kPrefsWindowID,
prefsDlgEventHandlerUPP);
// delete UPP here if not needed anymore
} /* InitPrefsWindow */
//event handler for Preferences dialog
static pascal void PrefsDlgEventHandler(Ptr myData)
{
extern TPEventRecord Event;
switch (Event.What) {
case doButton:
switch (Event.Button.Num) {
case iSaveButton:
// record user preference settings
Save_Dialog_Preferences();
// no break after case!
case iCancelButton:
WindowDisplay(kPrefsWindow, wHide);
break;
}
case iUseIC:
// toggle the ‘Use InternetConfig’ checkbox
break;
case iUseTempMem:
// toggle the ‘Use temporary memory’ checkbox
break;
break;
case doPopUpMenu:
HandlePrefDialogPopUpMenu();
break;
}
}
In the example above, when the user selects the Preferences... menu item, the Preferences dialog is made visible with the WindowDisplay function, and the handler begins to receive events. When the Save or Cancel buttons are selected, the window is hidden with the same Tools Plus call.
WindowDisplay(kPrefsWindow, wShow); // display the dialog
WindowDisplay(kPrefsWindow, wHide); // hide dialog, don't destroy
Mac OS Events
There are three occasions when you might have to handle raw Mac OS events.
First, Mac OS events not handled by Tools Plus are reported as doManualEvents. For example, Open and Close File dialog update events are sent to the application event filter proc, and are reported as a doManualEvent. The raw Mac OS event is recorded in the Event field of the TPEventRecord.
// code snippet from an application event handler
// note how the Event.message field is recast to a DialogPtr
case doManualEvent:
if (Event.Event.what==updateEvt && OpenFileDlgIsOpen())
DoOpenFileDialogUpdate((DialogPtr)Event.Event.message);
break;
Second, you can preprocess raw Mac OS events before they are handled by Tools Plus in an event filter that is an optional parameter for InitToolsPlus. A common use for this filter is keyboard shortcuts. Here is an example where the Command-Option-Shift-D key combination calls the debugger. The filter should return true (0) if the event has been handled and does not need further processing by Tools Plus, or false (1) if the event should be handled by Tools Plus.
// preprocess Mac OS events before handled by Tools Plus
// call Debugger() if Command-Option-Shift-D keys pressed
pascal Boolean EventFilter(EventRecord *evt)
{
switch (evt->what) {
case keyDown://
if ( (evt->modifiers & (cmdKey | optionKey | shiftKey))
&& IsKeyDown(0x02) ) // 0x02 == 'D' key
Debugger();
return (0);
}
break;
}
return (1);
}
Finally, you have to process raw Mac OS events in the event filter proc of a CustomGetFile or CustomPutFile dialog.
Apple Events
New to version 5.0 is support for Apple Events. The four required events are now reported as Tools Plus events: doOpenApplication, doOpenDocuments, doPrintDocuments and doQuitApplication. During InitToolsPlus, Tools Plus calls AEInstallEventHandler to install default handlers for the four required Apple Events. If you install your own handler for one or more of these events, your handlers override the Tools Plus defaults. If you install handlers for any other Apple Events, Tools Plus calls the handler for you. If there are no installed handlers for an Apple Event, Tools Plus reports the Apple Event as a doManualEvent.
To determine which files have been sent with the Open or Print Document Apple Events, the CountNumberOfFiles and GetIndexFileFSS routines are provided. Of course, production code will handle any errors when attempting to open a file.
// code snippet from an application event handler
// handle the ‘odoc’ Apple Event
FSSpec myFSS;
case doOpenDocuments:
for (i = 1; i <= CountNumberOfFiles(); i++)
if (GetIndexFileFSS(i, &myFSS))
MyOpenFileProc(myFSS);
break;
I found a minor bug with the new Apple Event support. If an Apple Event is handled, the event is still reported as a doManualEvent. Water's Edge has officially reported the bug, and promises a fix in the next release.
Tools Plus Interface Components API
Tools Plus interface components are created with and referenced by an integer reference number. You can use predefined values, or get the next available reference number.
enum {prefDialogID = 1 };
// create a new window with predefined reference number
WindowOpen(prefDialogID, ...);
WindowDisplay(prefDialogID, wShow);
// create a new window with first free window number
windID = GetFreeWindowNum();
WindowOpen(windID, ...);
WindowDisplay(windID, wShow);
If an invalid reference is passed to a Tools Plus routine, Tools Plus will either safely perform a null operation, or will alert the user that an invalid operation has occurred (Figure 3). With the Mac OS API, using an invalid reference like a menu or window handle usually results in a system crash, or worse.
Figure 3.Tools Plus error message if an invalid parameter is passed to a Tools Plus routine.
Tools Plus also gives you complete control over the appearance of the user interface components, including color, font, visibility, and enabled and disabled status.
Most Tools Plus elements are created by one of four methods:
- boundaries of the control are passed as separate parameters
- boundaries are passed in a Rect structure
- boundaries are derived from a DLOG resource with the same resource ID as the control reference number
- all element parameters are contained in a MBAR, MENU, WIND, DLOG or CNTL resource
The general form of these four methods is as follows:
NewXXX
NewXXXRect
NewDialogXXX
LoadXXX
As an example, here are the four methods of creating a new button.
// create a button with separate parameters for button coordinates
pascal void NewButton(short Button, short left, short top,
short right, short bottom, const Str255 Title, long Spec,
Boolean Enabled, Boolean Selected);
// get button coordinates from Rect structure
pascal void NewButtonRect(short Button, const Rect *Bounds,
const Str255 Title, long Spec, Boolean Enabled,
Boolean Selected);
// get button coordinates from DLOG item with same resource number
// as Button parameter
pascal void NewDialogButton(short Button, const Str255 Title,
long Spec, Boolean Enabled, Boolean Selected);
// get button parameters from CNTL resource
pascal void LoadButton(short Button, short ResID);
If the control is displayed in a window, the reference numbers are local to every window. For example, you can have two check boxes with the same number if they are in different windows.
SuperCDEFs
SuperCDEFs are an excellent collection of high quality, 3D user interface components (Figure 1). There is a neat trick to get a great 3D look with or without the Appearance Manager. Choose the SuperCDEFs appearance that you prefer (there are multiple choices), and then paste the appropriate library into your resource file. Next, change the SuperCDEFs' resource ID to 0. Then, SuperCDEFs will override the default System CDEFs, and you will have a great-looking interface with no additional effort.
Zen and the Art of (Tools Plus) Resource Editing
As noted above, the Tools Plus user interfaces can be designed with resource templates. For example, to create a simple Preferences dialog, design the DLOG resource and dialog control items (DITL resources) in Resorcerer or ResEdit.
Figure 4.DLOG resource in Resorcerer.
Figure 5.DLOG and DITL resources in Resorcerer. Item numbers are shown.
Then the dialog can be created with one line of code, and the check boxes and button are drawn automatically.
// using DLOG resource to create a ‘Preferences’ dialog
enum { PrefWindowID = 1};
enum { PrefWindowResID = 128};
enum {
iOKButton = 1,
iCancelButton,
iPrefShowIcon,
iPrefUseIC,
iPrefUseTempMem
};
LoadDialog(PrefWindowID, PrefWindowResID);
The buttons and check boxes are referenced by their resource ID number. And, of course, you will have to write the code that loads the user preferences and sets the dialog buttons based on the preference values.
An alternate method of designing your interface with resources is to use CNTL resources. As we'll see in the next section, CNTL resources allow you to have a great-looking interface with or without the Appearance Manager. An example of using CNTL resources is illustrated in the section on pop-up menus below.
If you are designing your interface with resources, you should strongly consider purchasing Resorcerer. Using Resorcerer to design your program interface isn't as elegant as a dedicated interface editor, but it's reasonably close.
Keeping Up Appearances
To develop an interface that looks similar with and without the Appearance Manager, you must use CNTL resources. Each CNTL resource points to a CDEF resource. For each Appearance Manager control you want available without the Appearance Manager, you must link your application with a third-party CDEF or WDEF resource that replicates that control. The SuperCDEFs and Infinity Windoid WDEF resources included with Tools Plus support Appearance Manager equivalents for buttons, sliders, tabs, hierarchy triangles, progress thermometers, and a floating window (Figures. 1 and 2). You must number these third-party resources 128 or higher so they don't conflict with the System resources. Then, have the CNTL resources point to the third-party resources rather than the System resources. Finally, link your project with the Appearance Manager library (AppearanceLib). PPC projects should "weak link" the library to avoid a runtime error if the Appearance Manager is not present.
At runtime, call InitToolsPlus with the initAppearanceManagerSavvy or initPureAppearanceManager specifications. In your startup code, use the UsingAppearanceManager function to determine of the Appearance Manager services are available. If the manager is present, you can substitute the procIDs of the control resources so the Appearance Manager controls are used instead of the 3rd party resources.
if (UsingAppearanceManager()) {
ReplaceWindowProcID(myToolPaletteProcID, 1985);
ReplaceControlProcID(myCheckBoxProcID, checkBoxProc);
ReplaceControlProcID(myRadioButtonProcID, radioButProc);
}
At this point I must confess that my application looks so good with SuperCDEFs and the Infinity Windoid WDEF that I've not converted my application to fully support the Appearance Manager. Using CNTL resources and substituting procIDs is definitely more work than simply using User Item resources and the NewDialogXXX form of Tools Plus component creation. However, if you want your application to support themes and other advanced Appearance Manager features, then CNTL resources are the way to go.
The Tools Plus User Interface Components
Here are some of the user interface components supported by Tools Plus. Be sure to look at the demo app to get a complete look at the Tools Plus system.
Windows
Tools Plus supports all standard window and dialog types, and they can be created from resource templates. I find it convenient to create non-document windows like tool palettes and dialogs during program startup, then simply hide and unhide the windows as needed.
You can get the Mac OS window pointer to the Tools Plus window with the WindowPointer function. I use window pointers to set the windowKind field of the WindowRecord, and to store document related data in the refCon field.
WindowRef w;
w = WindowPointer(windID);
SetWindowKind(w, kMyDocWindID);
SetWRefCon(w, (long)myDocDataH);
When you are finished with and close a window, Tools Plus releases the window reference for future use, but does not actually free the Tools Plus window data structure.
Dialogs
In Tools Plus, dialogs are simply windows with user interface components. Dialogs can be created as either modal or non-modal. I prefer to create my dialogs in Resorcerer with DLOG/DITL resources, then create the dialog with the LoadDialog function.
Tools Plus also supports dynamic alerts, simple dialogs with only 1 to 3 pushbuttons and static text. The AlertBox function creates the alert and automatically resizes the alert dialog to properly fit the alert text.
The Tool Bar
Tools Plus supports a non-movable tool bar that sits directly beneath the menu bar, much like the CodeWarrior IDE tool bar. However, unlike the CodeWarrior, the tool bar width is always the full width of the screen. Tools Plus automatically hides the tool bar when your application is in the background or if an Open or Close File dialog is opened. Tools Plus also deactivates (dims) the tool bar if a modal dialog is opened.
Figure 6.Tool Bar from the Tools Plus Demo App.
Floating Windows(!)
Hardly a month passes on the comp.sys.mac.programmer newsgroups without someone asking how to support floating windows (Figures. 2 and 7). Tools Plus provides superb, easy-to-implement support for floating windows. Floating windows automatically float above document windows, and are hidden when your application is switched to the background. Floating window behavior is set with the wPalette spec when creating the window. The floating palette appearance is easily achieved using the included Infinity Windoid WDEF resource.
Figure 7.Floating window from the Tools Plus Demo App
Window Levels
The presence of standard windows, floating palettes, the tool bar and modal windows adds a lot complexity to window management. Tools Plus maintains a window level hierarchy that determines which windows are active, and which remain frontmost (Table 2).
Topmost Layer (closest to user) |
Modal Windows |
Modal windows open at the front of this layer |
|
Multiple modal windows can be open simultaneously |
|
Frontmost modal window is only accessible window overriding all others |
Tool Bar |
Always active (only one tool bar can be open) |
|
Inaccessible if a modal window is open |
|
Automatically hidden when your application is suspended |
Floating Palettes |
Floating palettes open at the front of this layer |
|
Always active |
|
Multiple floating palettes can be open simultaneously |
|
Inaccessible if a modal window is open |
|
Automatically hidden when your application is suspended |
Standard Windows |
Standard (modeless, non-tool bar non-floating palette) windows open at the front of this layer |
|
Only the frontmost window in this layer is active |
|
Multiple standard windows can be open simultaneously |
|
When running under System 5 or 6's Finder, desk accessory windows may appear in this layer, providing a tool bar or floating palette is not open |
|
Inaccessible if a modal window is open |
Table 2. The Tools Plus window layer model.
Menus
Tools Plus provides full support for both pull-down and hierarchical menus. Although there are a variety of routines for creating menus, the easiest method of creating your application menus is to create each MENU resource in your resource editor, tie all of them together with an MBAR resource, and call the LoadMenuBar function in your initialization code. Manipulating menus using Tools Plus is similar to using the Mac OS API, except you use a menu number instead of a MenuHandle.
EnableMenu(kFileMenu, iCloseItem, enabled);
MenuMark(kOptionsMenu, iColor, CheckChar);
RenameItem(kEnhanceMenu, iToolBarItem, "\pShow Tool Bar");
RenameItem(kEnhanceMenu, iToolBarItem, "\pHide Tool Bar");
Tools Plus automatically handles the Edit menu when an editing field is active, including Cut, Copy and Paste operations to and from the Clipboard, and Undo/Redo support for the last edit operation.
Water's Edge recommends that you update the status of your menu items in the event filter when the user clicks the mouse in the menu bar for maximum performance. That way, user actions that result in menu items being enabled, disabled, added, or deleted - such as menu item check marks appearing or disappearing. - won't be performed until just before the user actually sees the menu.
// filter Mac OS events before processing by Tools Plus
// update all menu items when the user clicks in the menu bar
pascal Boolean EventFilter(EventRecord *evt)
{
WindowRef w; // just a place holder for FindWindow
switch (evt->what) {
case mouseDown:
if (FindWindow(evt->where, &w) == inMenuBar)
MyUpdateMenuProc();
break;
}
return (1); // Tools Plus should continue to process this event
}
However, changes to the menu bar should be performed more promptly. For example, when all document windows are closed, the Edit menu is dimmed in many applications. Fortunately, Tools Plus updates the menu bar after an event handler routine finishes whenever the menu bar has been changed with the AppleMenu, Menu, RemoveMenu, or EnableMenu routines.
My major annoyance with Tools Plus menus is the inability to show multiple-key menu item shortcuts in a menu. This shortcoming could be readily fixed by the Mercutio MDEF, but, unfortunately, Tools Plus is not compatible with Mercutio. Water's Edge is investigating the issue, but have not committed to a Mercutio-compatibility release date.
Mercutio MDEF
<http://www.digitalalchemy.com/mercutio/>
Pop-Up Menus
Pop-up menus are a great user interface feature that allow a selection from multiple choices in a compact region of a window or dialog. In addition to Mac OS standard pop-up menus, Tools Plus pop-up menus have additional options, including displaying the selected item's icon within the pop-up box, suppressing the pop-up menu's "down arrow", and a "pop down" option where the menu's list opens below the menu body.
Figure 8.Pop-up menus from the Tools Plus Demo App.
I create my pop-up menus with the LoadDialogPopUp function.
LoadDialogPopUp(short MenuNumber, long Spec, short ResID);
MenuNumber is the menu resource ID, which must be 1600 or higher. Tools Plus uses the coordinates of the resource identified by ResID - in Resorcerer, I use a User Item - to draw the pop-up. Spec is the pop-up specification that defines the pop-up appearance and behavior. Options I use include popup3DBody (3D look available in all system versions), popupUseWFont (use window font) and popupIconTitle (use the menu item icon in the pop-up box). Unfortunately, the pop-up menu title is drawn to the left of the user item rectangle, so getting the pop-ups lined up correctly in your window takes a few edit-compile-run cycles.
Water's Edge recommends using CNTL resources as the best method for creating pop-up menus. The manual is a bit sparse on the details, but here are the steps (using Resorcerer):
- Create and place a new Control item (Item menu) in the window where the pop-up box will be drawn. The menu title will be drawn to the left of the box.
- Select the item, then choose Get Info from the Item menu. A Control Item dialog will open. Enter the CNTL resource number in the Res. ID edit field. A new CNTL resource will be created if one does not exist.
- Click the Edit button, and edit the (new) CNTL resource.
- Change the CDEF Res ID field to 63, which will generate a ProcID of 1008.
- Change the ‘MENU’ ID field to the number of MENU resource used by the pop-up (must be 16000 or higher).
- Enter the name of the pop-up menu in the Title field.
- Set the Style code and Title width fields to 0.
I struggled to get CNTL resources and pop-up menus to work correctly until I discovered two crucial tips:
- you must set the Conform 'CNTL' bounds to Control item bounds dialog editor preference (Dialog/Preferences...) in order to synchronize resizing the Control item with the CNTL resource.
- Style code and Title width fields must be set to 0 (Resorcerer may choose an inappropriate default). Frankly, CNTL resources are significantly more complicated than using User Item resources with LoadDialogPopUp.
Cursors
Cursor shapes are easily changed with the CursorShape routine. There are defined constants for the standard cursor shapes: arrowCursor, iBeamCursor, crossCursor, plusCursor, watchCursor. Tools Plus automatically changes the cursor shape when the cursor is over an edit field.
pascal void CursorShape(short Shape);
The watch cursor has special status. When you change the cursor shape to the watch, Tools Plus is alerted that "a lengthy process in underway." If the watch cursor is active, the cursor shape will not be changed automatically by Tools Plus. Most events are filtered and not sent to the application event handler except for Command-period, to allow the user to cancel a lengthy operation.
Cursor Tables and Zones
A powerful feature of Tools Plus cursor management is the ability to define regions where the cursor automatically changes shape. Tools Plus uses cursor tables, which contain one or more cursor zones. When the cursor enters a cursor zone, it changes to the cursor shape defined for that zone (Figure 9). The zones are defined in local window coordinates. Each cursor table has a default cursor that is used if the cursor does not fall within any of the cursor zones. A cursor table is associated with a window by the UseCursorTable routine.
Figure 9.Cursor Zone Example. When the cursor moves inside the cursor zone, the cursor shape changes from an arrow to a plus shape.
I use cursor tables to support cursor shapes for different tools. At program initialization cursor tables are defined, and linked to the cursor resource associated with each tool. However, I don't create a cursor zone for any of the tables. When the tool is selected, UseCursorTable associates the proper table with the image window, and the cursor shape automatically changes to the proper tool when the cursor moves within the window (Figure 10).
// program start up
// establish cursor tables for each tool
// cursor tables
enum {
SelectionTable = 1,
GrabberTable,
MagnifyTable,
PencilTable,
PaintBrushTable
};
// resource IDs for cursor shapes
enum {
GrabberResID = 128,
MagnifyResID,
PencilResID,
BrushResID
};
// set up the cursor tables, associate table with default cursor resource
NewCursorTable(SelectionTable, arrowCursor);
NewCursorTable(GrabberTable, GrabberResID );
NewCursorTable(MagnifyTable, MagnifyResID);
NewCursorTable(PencilTable, PencilResID);
NewCursorTable(PaintBrushTable, BrushResID);
// application event handler
// when a tool is selected, associate the proper cursor table with the image window
// in this example, the Magnify tool is selected
UseCursorTable(FrontImageWindow(), MagnifyTable);
Figure 10.Cursor table example. The magnify tool is selected. Cursor shape is arrow when the cursor is outside of window, and changes to magnify glass when moved over window.
Every Picture (Button) Tells a Story
Some of my favorite Tools Plus features are picture buttons - click-sensitive icons and PICT resources. This versatile control permits a wide range of button types, including pushbuttons, click-sensitive icons, multistage buttons, radio-type buttons and polarized buttons (button value increases or decreases depending on which part of the button is clicked). Picture buttons are an easy method of creating great-looking tool palettes (Figures 6, 7 and 10).
Picture buttons are a bit more complex to set up because of the many different button types and behavior options available. I create my picture buttons with the NewDialogPictButton routine.
pascal void NewDialogPictButton (
short ButtonID, // Picture button number
short BaseID, // Base image's resource ID
long Spec, // Appearance and Behavior spec
Boolean EnabledFlag, // "Button is enabled" flag
Boolean SelectedFlag, // "Button is selected" flag
short minimum, // Minimum button value
short value, // Initial button value
short maximum); // Maximum button value
The button's coordinates are obtained from the dialog item whose number matches the button number, ButtonID. BaseID is the resource number of the icon or PICT resource of the button, and Spec is the behavior and appearance specification. There are 30 different specification parameters for customizing the appearance and behavior of the button.
Tools Plus can support more than one icon or PICT resource for a picture button. For example, the active, inactive, and dimmed states of a button may have a different image, and multistage buttons will of course have a different image for each stage. Tools Plus handles multiple resources for picture buttons by using a resource numbering sequence system to determine which resource is associated with each stage and appearance of the button.
The picture button type I find most useful is a 3D button created with SICN icon resources, which I use for a tool palette on a tool bar window (Figure 10). Tools Plus automatically creates a color, 3D button from the black & white SICN icon. The options I prefer for my tool palette include picbutLockSelect (lock button in selected state until another button selected) and pcitbutSlectLightenSICN3D (lighten and "push in" button when selected).
Field(s) of Dreams
Tools Plus text editing fields are powerful. Tools Plus automatically handles many routine edit field tasks, including field activation/deactivation and low memory protection. Fields can store up to 32k of text each. To save memory or improve performance, you can limit the field text length to a much smaller number, or can have a text handle that automatically resizes as text is entered. Either Pascal or C strings can be used.
When you enter or change the text in an edit field, you are working on a copy of the field text. The changes are not saved to the edit field string until the edit field is deactivated by clicking or tabbing to another field, or closing the dialog with the field. You can optionally be notified when this occurs to perform field validation. Fields can be placed in any window that you create. Tool Plus ensures that only one field - the active field - is active and selected at a time.
Another great feature of Tools Plus is its field filters. Filters allow you to limit what characters can be entered into a field.
pascal short NewFieldFilter(const Str255 Chars,
long FilterSpec);
NewFieldFilter returns the field reference number. Chars specifies the characters to be included in the field, and Spec allows various text filter options. Filters and length-limited fields are a great help when designing a data entry form.
A problematic aspect of edit field management is validating field contents. You can have Tools Plus notify you when the active edit field is about to be deactivated. If you don't need to validate edit field contents, then you should call InitToolsPlus with the initAutoFocus specification, and Tools Plus will automatically update field contents.
If you don't use the initAutoFocus option, there are two occasions when Tools Plus notifies you when the user is about to deselect an active edit field. First, when the user clicks in an inactive edit field, Tools Plus sends your event handler a doClickToFocus event. The GetEditString function returns a handle to the temporary copy of the active edit field string. If the field string is validated, you save the changes with the SaveFieldString function, and allow the new edit field to be selected with the ClickToFocus function.
// example of field validation
// example is for a form with a name and e-mail edit field
pascal void GetEditString(Str255 EditString);
pascal void ClickToFocus(void);
pascal void SaveFieldString(void);
// window event handler
case doClickToFocus:
if (MyValidateFieldProc()) {
SaveFieldString();
ClickToFocus();
}
break;
short MyValidateFieldProc(void)
{
Str255 string;
short fieldNum = ActiveFieldNumber();
GetEditString(&string);
switch (fieldNum) {
case nameField:
if (!IsNameFieldValid(string)) {
Alert("Invalid name field, please reenter.");
return false;
}
break;
case e-mailField:
if (!IsValidEmailAddress(string)) {
Alert("Invalid e-mail address, please reenter.");
return false;
}
break;
}
return true;
}
Second, if the user pressed the Tab key to move to the next field (or pressed Shift-Tab to move to the previous field), you use the HaveTabInFocus function to determine if the key press occurred in an active edit field. If the field contents are acceptable, you save the field changes with SaveFieldString (as before), and use TabToFocus to allow the user to tab into the next or previous field.
pascal void HaveTabInFocus(void);
// window event handler
case doKeyDown:
case doAutoKey:
if (HaveTabInFocus()) {
if (MyValidateFieldProc()) {
SaveFieldString();
TabToFocus();
}
}
break;
Scroll Bars and Sliders
Tools Plus provides excellent support for managing scroll bars and sliders, a problematic aspect of the pre-Appearance Manager Mac OS API. You can use the default system scroll bar, or you can use third-party CDEFs like those provided in SuperCDEFs. SuperCDEFs has a nice selection of sliders (Fig. 1). Tools Plus also supports "live action" scroll bars, and scroll bars that automatically resize when a window is resized.
When a scroll bar or slider is moved or clicked by the user, a doScrollBar event is reported. The event record also reports which part of the scroll bar or slider was activated. Your event handler will continue to get doScrollBar events as long as the user keeps moving the scroll bar thumb with the mouse.
A better method of handling scroll bar and slider events is to use an action routine which Tools Plus calls for every user interaction with the scroll bar. The action routine is linked to the scroll bar with the SetScrollBarAction routine. Within the routine, you use the GetScrollBarActionInfo routine to determine which scroll bar is active, the parent window of the scroll bar, the part moved or clicked by the user, and whether the mouse is still in the original scroll bar part. Action routines are another method of developing nicely modularized code in Tools Plus.
// prototypes
// Create a UPP to a scroll bar's action routine
pascal ScrollBarActionUPP NewScrollBarActionProc (ScrollBarActionProcPtr userRoutine);
// Set an action routine for a scroll bar
pascal void SetScrollBarAction (short ScrollBar, ScrollBarActionUPP ActionProc);
// Get info about the scroll bar currently calling an action routine
pascal void GetScrollBarActionInfo (short *Window,
short *ScrollBar,
short *Part,
Boolean *InPart);
// create UPP, attach the action proc to the scroll bar
ScrollBarActionUPP MyActionProcUPP;
MyActionProcUPP =
NewScrollBarActionProc(MyActionProc);
SetScrollBarAction(kMyScrollBar,
MyActionProcUPP);
// typical structure of scroll bar action proc
pascal void MyActionProc(void)
{
short scrollBar, part, curVal, windID;
Boolean inPart;
static short oldVal = 0;
GetScrollBarActionInfo(&windID, &scrollBar,
&part, &inPart);
if (!inPart)
return;
// get the current value of the scroll bar attached to the action proc
curVal = GetScrollBarVal(scrollBar);
switch (part) {
case (inThumb):
if (curVal == oldVal)
return;
// handle in thumb action here
break;
case (inPageUp):
case (inUpButton):
// handle page up action here
break;
case (inPageDown):
case (inDownButton):
// handle page down action here
break;
}
oldVal = curVal;
}
Miscellaneous Stuff
Documentation
The Tools Plus documentation is excellent. There are overview chapters on setting up and using the libraries, and specific sections for each type of interface element. Both printed and electronic versions (eDoc and PDF) are supplied. There is a nice touch to the electronic documentation I've not seen before - the pages are numbered consecutively from the title page, so that when you select Move to Page... (eDoc) or Go to Page... (PDF), the Table of Contents page numbers and the electronic page numbers are synchronized. I think a few more code examples would be helpful, but there is plenty of code to study in the demo app and in the tutorials.
Whither Carbon?
Water's Edge states that Tools Plus is already highly Carbon compliant, and full Carbon compatibility should not be a problem.
Summary
Living with Tools Plus
I've enjoyed working with Tools Plus and Water's Edge Software. The quality of the libraries, the documentation and the technical support is excellent. Tools Plus greatly simplifies the task of developing Mac OS applications and plug-ins, and lets you concentrate on coding your application. I'm impressed with how much of the "dirty work" of Mac OS programming the Tools Plus system handles for you.
I've submitted about half a dozen bugs that have been acknowledged, fixed, and logged in the changes file. Most of the bugs I've encountered have been minor and easy to work around. I find it helpful to knock off a quick mini-application to send to Water's Edge that isolates a particular problem, and with Tools Plus, the application is easy to create!
My two major complaints with Tools Plus are the lack of full support for balloon help (scheduled for Sept. 99 release) and the inability to have multiple-key menu item shortcuts (under investigation, but no release date announced). Some features I'd like to see include Open/Save File dialog support, preference file support, and some examples of how to make your application scriptable.
Recommendations
I strongly recommend the Tools Plus system. But is Tools Plus for you? Here are some points you should consider.
- What language do you program in? If you are better at programming in C or Pascal than C++, then I'd lean towards Tools Plus. If you are good in BASIC, I'd take a peak at REALbasic. If C++ is what bakes your cookies, then you might be more comfortable with a complete application framework like PowerPlant.
- Do you have strong beliefs about your coding structure and style? A code generator or application framework will impose its programming worldview upon you, whereas Tools Plus allows you to program in the style that suits you best.
- What about PowerPlant? PowerPlant is a more complete application development system, but it is significantly more complex to learn than Tools Plus. You will be up and running much faster with Tools Plus, but down the road you will have some programming chores to do on your own that PowerPlant would have handled for you.
- Are you a Windows programmer new to the Mac? Welcome! The Tools Plus event model resembles the Win32 messaging system, so I think your Windows programming skills will serve you well if you choose the Tools Plus system.
Whatever your choice, good luck on your programming projects.
Thurman Gillespy III (tg3@u.washington.edu) is an Associate Professor of Radiology at the University of Washington, Seattle, Washington, and practices at both the University of Washington Medical Center and the Puget Sound Veterans Medical Center in Seattle. His research interests include medical image processing and image display. When not programming or occupied with the day job, Thurman is the personal chauffeur for his two teenage daughters.