September 94 - Pick Your Picker With Color Picker 2.0
Pick Your Picker With Color Picker 2.0
SHANNON HOLLAND
The limitations of the old Color Picker Package forced many developers to write their
own color pickers. The flexibility of Color Picker version 2.0 overcomes the old
limitations and provides many new features -- most notably, use with ColorSync
color. Now it's easy to design color pickers to suit your needs. This article describes
how to use the new Color Picker Manager and take advantage of its customization
features from within your application.
Apple designed the Color Picker Package as a way for applications to present a standard
user interface for color selection. The goal in developing Color Picker version 2.0 was
to remain compatible with the existing Color Picker Package while providing tighter
integration of color pickers with the application and allowing development of
customized color pickers (for example, to support other color spaces or specific
devices).
These goals were achieved by adding a Color Picker Manager, turning color pickers
into components, and separating the color picker components from the Color Picker
Manager. As components, color pickers are now accessed through the Component
Manager, which provides a layer between the application and the color picker
component. In other words, the application calls the Color Picker Manager, which then
calls the Component Manager, which calls the color picker component. In the old Color
Picker Package, the application called the color picker directly.
This separation of the color picker components from the Color Picker Manager allows
new color picker components to be dynamically added to the system by the user or an
application. Once a new color picker component has been registered to the Component
Manager, it's available for use by the Color Picker Manager.
The interface to the new Color Picker Manager is divided into high- and low-level
calls:
• The high-level calls are designed to be used with a minimum of fuss, but
provide access to nearly the whole feature set available to the application
through the Color Picker Manager. For compatibility with previous versions,
the old high-level call, GetColor, is still there. A new high-level call,
PickColor, replaces GetColor and offers a much broader feature set.
• The low-level calls are designed to allow maximum flexibility. They let
the application determine the type of dialog the color picker is placed in,
rather than using the modal dialog you get with high-level calls. The
application can also set the current color and maintain explicit control over
the event loop. Color pickers that are invoked through the low-level calls can
exist for the life of an application. This article discusses how to use these
calls and take advantage of the new Color Picker Manager. The code examples
are provided on this issue's CD. Color Picker 2.0 allows multiple color picker
components to exist on a system at one time (through the Component
Manager). Although the interface for these components is public, this article
doesn't discuss the creation of color picker components.
SPECIFYING COLORS
Unlike the old Color Picker Package, Color Picker 2.0 uses the more complete
ColorSync definition of a color, which contains both a color and a profile. The profile
defines the color space of the color (which includes the type of color -- CMYK, HSL,
RGB, and so on). You can also specify a destination profile, which describes the color
space of the device for which the color is being chosen (for example, a color printer
that will eventually print the document). Given knowledge of the destination profile,
color pickers that are ColorSync aware can help the user choose a color that's within
the destination device's gamut.
ColorSync is described in the forthcoming Inside Macintosh: Advanced Color
Imaging . See also "Print Hints: Syncing Up With ColorSync" in develop Issue 14.*
The ColorSync definition for a color, shown below, is used only with the new calls. The
old call, GetColor, still uses RGBColor for compatibility. These structures are
compatible with QuickDraw GX.
typedef struct CMProfile **CMProfileHandle;
RGBColor rgb;
unsigned short reserved[4];
CMProfileHandle profile;
CMColor color;
If you're specifying an RGB color with no particular profile, you can simply set the
CMProfileHandle field of PMColor to nil, which uses the system profile. To specify a
color that uses a profile, you need to provide the profile that describes that color.
USING THE HIGH-LEVEL CALLS
The high-level calls are designed to handle the most common uses for the Color Picker
Manager. The old GetColor call provides access to the new dialog and the color picker
component, but not to any of the new features that are accessible through the Color
Picker Manager (such as ColorSync colors).
The new PickColor call is designed to replace GetColor. It can be used very simply,
providing roughly the same feature set as GetColor, or it can be used to take advantage
of some of the more advanced features of Color Picker 2.0.
The new dialog for the high-level calls is much the same as the old one. A new button,
More Choices, reveals a list of all available color pickers (and changes to "Fewer
Choices"; see Figure 1). Clicking a color picker in the list makes it the current color
picker for the dialog. Both PickColor and GetColor display this dialog.
The biggest difference between PickColor and GetColor is that PickColor allows the
application to provide a pointer to an event filter procedure. If the application
supplies such a procedure, a movable modal dialog will be created rather than the old
modal dialog. You can do this from within PickColor because you're now able to pass
update events to windows within the same application layer as the color picker.
PickColor also uses the new ColorSync color definition, so you can specify a color in
any color space along with a destination profile. Likewise, a color can be returned in
any color space.
Figure 1. Color picker dialog for high-level calls
PICKCOLOR PARAMETER BLOCK
Listing 1 shows the parameter block that you pass through to PickColor. The first two
fields, theColor and dstProfile, are pretty obvious; they're simply the input (and
output) color and the profile for the final output device. If there's no output device,
you just set dstProfile to nil.
Listing 1. PickColor parameter block
typedef struct ColorPickerInfo {
PMColor theColor;
CMProfileHandle dstProfile;
long flags;
DialogPlacementSpec placeWhere;
Point dialogOrigin;
long pickerType;
UserEventProc eventProc;
ColorChangedProc colorProc;
long colorProcData;
Str255 prompt;
MenuItemInfo mInfo;
Boolean newColorChosen;
The flags field is a little more complicated. (It's also used in many of the low-level
calls.) With PickColor, there are three flags you need to worry about:
• CanModifyPalette. If you set this flag, you're telling the color picker
component that it's able to install a palette of its own that may modify (but not
animate) the current color table. If you don't want the colors in your document
to change as you make choices in the color picker dialog, don't set this flag.
• CanAnimatePalette. This flag is similar to CanModifyPalette, except that
it allows the color picker component to modify or animate the palette as much
as it wants to.
• AppIsColorSyncAware. This informs the Color Picker Manager that your
application understands ColorSync colors. This means that a color may be
returned to you in a different space than the one you passed in. For example,
you could pass an RGB color (with no profile) to the Color Picker Manager and
receive back a CMYK color (with its associated profile). If you don't set this
flag, the Color Picker Manager automatically converts any color it receives
back from the color picker component to RGB space.
The placeWhere field tells the Color Picker Manager where to position the color picker
dialog. The choices are kAtSpecifiedOrigin (at the point specified by the dialogOrigin
field), kDeepestColorScreen (centered on the deepest color screen), and
kCenterOnMainScreen (centered on the main screen).
The dialogOrigin field (in conjunction with kAtSpecifiedOrigin) is used when you
request that the color picker dialog be placed at a specific point. When PickColor
returns, this field contains the location of the color picker dialog at the time it was
closed.
You use the pickerType field to specify the component subtype of the color picker to
select initially. If you set this field to 0, the default system color picker will be used
(the last color picker chosen by the user). When PickColor returns, this field
contains the component subtype of the color picker that was open when the user closed
the dialog.
You should set the eventProc field to point to an event filter procedure that will handle
events meant for your application. If this procedure returns true, the Color Picker
Manager won't process the event further. If it returns false, the Color Picker Manager
will handle the event if it was meant for the color picker. If you set this field to nil, a
modal dialog will be created (rather than a movable modal dialog).
The colorProc field can contain a pointer to a procedure that will be called whenever
the color changes. This allows live updating of colors in application documents as the
user selects them. The colorProcData field contains a long integer that's passed to the
color-changed procedure and can be used for any private data.
The prompt field is a prompt string that the color picker displays to give the user
some indication as to what the new color is for (for example, a highlight color).
The mInfo field tells the Color Picker Manager what the Edit menu ID is and where the
various menu items are located within it.
The newColorChosen field is set on return from PickColor. If true, it means that the
user chose a color and clicked OK; otherwise, the user clicked Cancel.
IMPLEMENTING PICKCOLOR
Now let's look at an example of how all this would be used. Listings 2 and 3 show two
callbacks -- the event filter procedure (MyEventProc) and the color-changed
procedure (MyColorChangedProc). In the color-changed procedure we assume that
ColorSync is installed. This is because we'll be setting the AppIsColorSyncAware flag
when we call PickColor, so a non-RGB color might come back from the picker and, if
so, you need to call ColorSync to convert it to RGB.
Listing 2. Event filter procedure
WindowPtr myDocWindow;
pascal Boolean MyEventProc(EventRecord *event) {
Boolean handled = false; // Assume we don't handle the event.
switch (event->what) {
case updateEvt:
// Check to see if the update is for our window.
if ((WindowPtr) event->message == myDocWindow) {
DoTheUpdate(myDocWindow);
handled = true;
}
}
return handled;
}
Listing 3. Color-changed procedure
pascal void MyColorChangedProc(long userData, PMColorPtr newColor) {
GrafPtr port;
CWorld cWorld;
CMColor color;
CMError cwError;
GetPort(&port);
SetPort(myDocWindow);
// Now check to see if the color has a profile. If so, we need to
// convert it to RGB space.
// Create a color world and convert the color. This color
// world matches from the color's space to the system space
// (RGB).
cwError = CWNewColorWorld(&cWorld, newColor->profile, 0L);
if (cwError == noErr || cwError == CMProfilesIdentical) {
// We created the color world. Now match the color using
// a copy so that we don't munge the original.
color = newColor->color;
CWMatchColors(cWorld, &color, 1);
CWDisposeColorWorld(cWorld);
}
} else
color.rgb = newColor->color.rgb;
// Set the new color and paint the port with it.
myRGBColor = color.rgb;
RGBForeColor(&color.rgb);
PaintRect(&myDocWindow->portRect);
SetPort(port);
}
Once you have the two callback procedures, you can go ahead and call PickColor (see
Listing 4). Listing 4. Calling PickColor
ColorPickerInfo cpInfo;
PMColor savedColor;
// Set the input color to be an RGB color in system space.
cpInfo.theColor.color.rgb = myRGBColor;
cpInfo.theColor.profile = 0L;
cpInfo.dstProfile = 0L;
cpInfo.flags =
AppIsColorSyncAware | CanModifyPalette | CanAnimatePalette;
// Center the picker on the deepest color screen.
cpInfo.placeWhere = kDeepestColorScreen;
// Use the default picker.
cpInfo.pickerType = 0L;
// Install the callbacks.
cpInfo.eventProc = MyEventProc;
cpInfo.colorProc = MyColorChangedProc;
cpInfo.colorProcData = 0L;
strcpy(cpInfo.prompt,"\pChoose a new color");
// Tell the Color Picker Manager about the Edit menu.
cpInfo.mInfo.editMenuID = kMyEditMenuID;
cpInfo.mInfo.cutItem = kMyCutItem;
cpInfo.mInfo.copyItem = kMyCopyItem;
cpInfo.mInfo.pasteItem = kMyPasteItem;
cpInfo.mInfo.clearItem = kMyClearItem;
cpInfo.mInfo.undoItem = kMyUndoItem;
// Save the current color, in case the user cancels.
savedColor = cpInfo.theColor;
// And finally, pick that color!
if (PickColor(&cpInfo) == noErr && cpInfo.newColorChosen)
// Go use this new color. Remember it can be in any color space.