Advanced Search
Apple Developer Connection
Member Login Log In | Not a Member? Contact ADC

Previous Book Contents Book Index Next

Inside Macintosh: Operating System Utilities /
Chapter 5 - Control Panel Extensions


Writing a Control Panel Extension

A control panel extension is a component that works with a control panel to manage a panel--a certain part of an existing control panel's display area. Because a control panel extension is a component, it must be able to respond to standard request codes sent by the Component Manager. In addition, a control panel extension must

This section describes how to write a control panel extension. You need to read this section if you want to create a new panel for an existing control panel.

Creating a Component Resource for a Control Panel Extension

A control panel extension is stored as a component resource. It contains a number of resources, including icons, strings, pictures, and the standard component resource (a resource of type 'thng') required of any Component Manager component. In addition, a control panel extension must contain code to handle required request codes passed to it by the Component Manager as well as panel-specific request codes. A control panel extension also usually contains an item list resource ('DITL') that defines the items for the panel.

Note
For complete details on components and their structure, see the chapter "Component Manager" in Inside Macintosh: More Macintosh Toolbox. This section provides specific information about control panel extensions.
The component resource binds together all the relevant resources contained in a component; its structure is defined by the ComponentResource data type.

TYPE ComponentResource =
   RECORD
      cd:               ComponentDescription;
      component:        ResourceSpec;
      componentName:    ResourceSpec;
      componentInfo:    ResourceSpec;
      componentIcon:    ResourceSpec;
   END;
The cd field contains a component description record that specifies the component type, subtype, manufacturer, and flags. The component field specifies the resource type and resource ID of the component's executable code. By convention, this resource should be of the same type as the componentType field of the component description record referenced through the cd field. (You can, however, specify some other resource type if you wish.) The resource ID can be any integer greater than or equal to 128. See the next section, "Dispatching to Control Panel Extension-Defined Routines," for further information about this code resource. The ResourceSpec data type has this structure:

TYPE ResourceSpec =
   RECORD
      resourceType:     ResType;
      resourceID:       Integer;
   END;
The componentName field of the ResourceSpec data type specifies the resource type and resource ID of the resource that contains the component's name. Usually the name is contained in a resource of type 'STR '. This string should be as short as possible.

The componentInfo field specifies the resource type and resource ID of the resource that contains a description of the component. Usually the description is contained in a resource of type 'STR '. This information is not currently used by control panels, but some development tools may use it.

The componentIcon field specifies the resource type and resource ID of the resource that contains an icon for the component. Usually the icon is contained in a resource of type 'ICON'. This icon is not currently used by control panels, but some development tools may use it.

As previously described, the cd field of the ComponentResource structure is a component description record, which includes additional information about the component. A component description record is defined by the ComponentDescription data structure.

TYPE ComponentDescription =
   RECORD
      componentType:          LongInt;
      componentSubType:       LongInt;
      componentManufacturer:  LongInt;
      componentFlags:         LongInt;
      componentFlagsMask:     LongInt;
   END;
For control panel extensions, the componentType field must be set to a value associated with an existing control panel. Currently, you can specify one of two available component types for control panel extensions:

CONST
   SoundPanelType             = 'sndP';   {sound panel}
   VideoPanelType             = 'vidP';   {video panel}
In addition, the componentSubType field must be set to a value that indicates the type of control panel services your panel provides. For example, the Apple-supplied control panel extensions for the Sound control panel have these subtypes:

CONST
   kAlertSoundsPanel          = 'alrt';   {alert sounds panel}
   kInputsPanel               = 'mics';   {input devices panel}
   kOutputsPanel              = 'spek';   {output devices panel}
   kVolumesSubType            = 'vols';   {volumes panel}
If you add panels to the Sound control panel, you should assign some other subtype.

Note
Apple reserves for its own uses all types and subtypes composed solely of lowercase letters.
You can assign any value you like to the componentManufacturer field; typically, you put the signature of your control panel extension in this field.

The componentFlags field of the component description for a control panel extension contains bit flags that encode information about the extension. Currently, you can use this field to specify whether the control panel should open your extension's resource file.

CONST
   channelFlagDontOpenResFile = 2;     {do not open resource file}
The channelFlagDontOpenResFile bit indicates to the owning control panel whether or not to open the component's resource file. When bit 2 is cleared (set to 0), the control panel opens the component's resource file for you. In general, this is the most convenient way to gain access to your extension's resources. However, if the component is linked with an application and does not have its own resource file, you might not want the control panel to try to open the resource file. In that case, set this bit to 1.

You should set the componentFlagsMask field to 0.

Your control panel extension is contained in a resource file. The creator of the file can be any type you wish, but the type of the file must be 'thng'. If the extension contains a 'BNDL' resource, then the file's bundle bit must be set. Control panel extensions should be located in the Control Panels folder (or Extensions folder if the component needs automatic registration).

Listing 5-1 shows the Rez listing of a component resource that describes a control panel extension.

Listing 5-1 A component resource for a control panel extension

resource 'thng' (kExamplePanelID, kExampleName, purgeable) {
   kExamplePanelComponentType,   /*component type*/
   kExamplePanelSubType,         /*component subtype*/
   kExampleManufacturer,         /*component manufacturer*/
   cmpWantsRegisterMessage,      /*control flags*/
   0,                            /*control flags mask*/
                                 /*code res type, res ID*/
   kExamplePanelCodeType, kExamplePanelCodeID,
   'STR ', kExamplePanelNameID,  /*name res type, res ID*/
   'STR ', kExamplePanelInfoID,  /*info res type, res ID*/
   'ICON', kExamplePanelIconID   /*icon res type, res ID*/
};

Dispatching to Control Panel Extension-Defined Routines

As explained in the previous section, the code stored in the control panel extension component should be contained in a resource whose resource type matches the type stored in the componentType field of the component description record. The Component Manager expects that the entry point in this resource is a function having this format:

FUNCTION MyPanelDispatch (VAR params: ComponentParameters;
                          storage: Handle): ComponentResult;
Whenever the Component Manager receives a request for your control panel extension, it calls your component's entry point and passes any parameters, along with information about the current connection, in a component parameters record. The Component Manager also passes a handle to the global storage (if any) associated with that instance of your component.

When your component receives a request, it should examine the parameters to determine the nature of the request, perform the appropriate processing, set an error code if necessary, and return an appropriate function result to the Component Manager.

The component parameters record is defined by a data structure of type ComponentParameters. The what field of this record contains a value that specifies the type of request. Your component's entry point should interpret the request code and possibly dispatch to some other subroutine. Your extension must be able to handle the required request codes, defined by these constants:

CONST
   kComponentOpenSelect          = -1;
   kComponentCloseSelect         = -2;
   kComponentCanDoSelect         = -3;
   kComponentVersionSelect       = -4;
Note
For complete details on required component request codes, see the chapter "Component Manager" in Inside Macintosh: More Macintosh Toolbox.
In addition, your extension must be able to respond to panel-specific request codes. Currently, you need to be able to handle these request codes:

CONST
   kPanelGetDitlSelect        = 0;  {get panel's item list}
   kPanelGetTitleSelect       = 1;  {get panel's name}
   kPanelInstallSelect        = 2;  {restore item settings}
   kPanelEventSelect          = 3;  {handle event in panel}
   kPanelItemSelect           = 4;  {handle click in a panel item}
   kPanelRemoveSelect         = 5;  {panel is about to be removed}
   kPanelValidateInputSelect  = 6;  {validate panel settings}
   kPanelGetSettingsSelect    = 7;  {get panel settings}
   kPanelSetSettingsSelect    = 8;  {set panel settings}
You should respond to these request codes by performing the requested action. To service the request, your component may need to access additional information provided in the params field of the component parameters record. The params field is an array that contains the parameters specified by the control panel that called your component. You can directly extract the parameters from this array, or you can use the CallComponentFunction or CallComponentFunctionWithStorage function to extract the parameters from this array and pass these parameters to a subroutine of your component.

Listing 5-2 illustrates how to define the entry-point routine for a control panel extension.

Listing 5-2 Handling Component Manager request codes

FUNCTION MyPanelDispatch (VAR params: ComponentParameters; storage: Handle) 
                         : ComponentResult;
CONST
   kPanelVersion = 1;
   kExamplePanelDITLID = 128;
   kDefaultButton = 1;
   kExampleOtherButton = 2;
   kExampleBeepButton = 3;
   kExampleRadioButton1 = 4;
   kExampleRadioButton1 = 5;
TYPE
   PanelGlobalsRec =          {global storage for this component instance}
      RECORD
         itemOffset:    Integer;
         mySelf:        ComponentInstance;
      END;
   PanelGlobalsPtr = ^PanelGlobalsRec;
   PanelGlobalsHandle = ^PanelGlobalsPtr;
VAR
   myGlobals:     PanelGlobalsHandle;
   selector:      Integer;
BEGIN
   CASE params.what OF
      kComponentOpenSelect:                  {component is opening}
         BEGIN
            myGlobals := 
               PanelGlobalsHandle(NewHandleClear(SizeOf(PanelGlobalsRec)));
            IF myGlobals <> NIL THEN
            BEGIN
               myGlobals^^.mySelf := ComponentInstance(params.params[0]);
               SetComponentInstanceStorage(myGlobals^^.mySelf,
                                          Handle(myGlobals));
               MyPanelDispatch := noErr;
            END
            ELSE
               MyPanelDispatch := MemError;
         END;

      kComponentCloseSelect:                 {component is closing; clean up}
         BEGIN
            IF storage <> NIL THEN
               DisposeHandle(storage);
            MyPanelDispatch := noErr;
         END;

      kComponentCanDoSelect:                 {indicate whether component }
                                             { supports this request code}
         BEGIN
            selector := Integer((Ptr(params.params)^));
            IF (((kComponentVersionSelect <= selector) 
                  AND (selector <= kComponentOpenSelect))
               OR ((kPanelGetDitlSelect <= selector) 
                  AND (selector <= kPanelSetSettingsSelect))) THEN
               MyPanelDispatch := 1 {valid request}
            ELSE
               MyPanelDispatch := 0;{invalid request}
         END;
      
      kComponentVersionSelect:{return version number}
         MyPanelDispatch := kPanelVersion;
            
      kPanelGetDitlSelect:    {get panel's item list}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                               ComponentFunction(@MyPanelGetDITL));

      kPanelInstallSelect:    {restore items' settings if necessary}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                               ComponentFunction(@MyPanelInstall));

      kPanelEventSelect:      {handle event in panel}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                               ComponentFunction(@MyPanelEvent));

      kPanelItemSelect:       {handle hit in one of panel's items}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                               ComponentFunction(@MyPanelItem));

      kPanelRemoveSelect:  {panel is about to be removed, respond as needed}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                               ComponentFunction(@MyPanelRemove));
      kPanelValidateInputSelect:{validate panel settings}
         MyPanelDispatch := 
                     CallComponentFunctionWithStoMyPanelValidateInputrage
                                 (storage, params, 
                                  ComponentFunction(@MyPanelValidateInput));

      kPanelGetTitleSelect:   {get panel's name}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                               ComponentFunction(@MyPanelGetTitle));

      kPanelGetSettingsSelect:   {get panel settings}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                              ComponentFunction(@MyPanelGetSettings));

      kPanelSetSettingsSelect:{set panel settings}
         MyPanelDispatch := CallComponentFunctionWithStorage
                              (storage, params, 
                              ComponentFunction(@MyPanelSetSettings));

      OTHERWISE               {unrecognized request code}
         MyPanelDispatch := badComponentSelector;
   END; {of CASE}
END;
The MyPanelDispatch function defined in Listing 5-2 simply inspects the what field of the component parameters record to determine which request code to handle. For panel-specific request codes, it dispatches to the appropriate function in the control panel extension. See the following sections for more details on handling panel-specific request codes.

Your extension can be dynamically loaded or unloaded at any time. When the owning control panel first discovers the extension, it loads it into a subheap of some existing heap. In all likelihood, your extension is loaded into either the system heap or temporary memory. In some cases, however, your extension might be loaded into an application's heap. Your extension is guaranteed 32 KB of available heap space. You should do all allocation in that heap using normal Memory Manager routines.

If you need to access resources that are stored in your control panel extension, you can use the OpenComponentResFile and CloseComponentResFile functions (which are provided by the Component Manager), or you can allow the control panel to open your resource fork for you automatically by setting the appropriate component flag. The OpenComponentResFile routine requires the ComponentInstance parameter supplied to your routine. You should not call the Resource Manager routines OpenResFile or CloseResFile.

WARNING
Do not leave any resource files open when your control panel extension is closed. Their maps will be left in the subheap when the subheap is freed, causing the Resource Manager to crash.
The following sections illustrate how to write control panel extension functions that respond to panel-specific request codes.

Installing and Removing Panel Items

After opening your control panel extension, the control panel calls your control panel extension with a get-item list request followed by an install request. When your component receives a get-item list request, it should return the item list that defines the items in its panel. When your component receives an install request, it should set the default values of any items in the panel or set up any user items in the panel. For example, your component can restore previous settings as set by the user or create lists at this time. When your component receives a remove request, it should perform any processing that is necessary before the panel is removed from the display area of the control panel.

A control panel that uses your control panel extension calls your component with the get-item list request (followed by an install request) before displaying the panel to the user. If your component returns a result code of noErr in response to both of these request codes, the control panel displays your panel to the user.

The relevant fields in the component parameters record when your component receives a get-item list request are:

what
This field is set to kPanelGetDitlSelect.
params
The first entry in this array contains a handle to a block of memory. Your component should resize the handle as necessary and then use this memory to return an item list of the items supported by your control panel extension.
In response to a get-item list request, set your component's function result to noErr if your component successfully placed the item list in memory; otherwise, set it to a nonzero value.

Listing 5-3 shows an example of a control panel extension-defined routine that handles the get-item list request.

Listing 5-3 Responding to the get-item list request

FUNCTION MyPanelGetDITL(globals: PanelGlobalsHandle; 
                        ditlHandle: Handle): ComponentResult;
BEGIN
   MyPanelGetDITL := resNotFound; {set default return value}
   ditlHandle := Get1Resource('DITL', kExamplePanelDITLID);
   IF (ditlHandle <> NIL)
   BEGIN
      DetachResource(ditlHandle);
      MyPanelGetDITL := noErr;
   END;
END;
The relevant fields in the component parameters record when your component receives an install request are:

what
This field is set to kPanelInstallSelect.
params
The first entry in this array contains the dialog pointer of the owning control panel. The dialog box can be a color dialog box on systems that support color windows. The second entry contains the item offset to your panel's first item.
In response to an install request, set your component's function result to noErr if your component successfully handled the request; otherwise, set it to a nonzero value.

Listing 5-4 shows an example of a control panel extension-defined routine that handles the install request.

Listing 5-4 Responding to the install request

FUNCTION MyPanelInstall(globals: PanelGlobalsHandle; 
                        cpDialogPtr: DialogPtr; 
                        itemOffset: Integer): ComponentResult;
BEGIN
   {restore previous settings of panel items as set by user}
   MyPanelInstall := MyRestoreSettings(globals, itemOffset,
                                       cpDialogPtr);
END;
The MyPanelInstall function shown in Listing 5-4 calls one of its own routines (MyRestoreSettings) to set the panel's items to the last settings chosen by the user. In response to the install request, you can also create other elements needed by your panel, such as lists.

The relevant fields in the component parameters record when your component receives a remove request are:

what
This field is set to kPanelRemoveSelect.
params
The first entry in this array contains the dialog pointer of the owning control panel. The dialog box can be a color dialog box on systems that support color windows. The second entry contains the item offset to your panel's first item.
In response to a remove request, dispose of any additional dialog data you created (for example, if you created a list, call LDispose), but do not dispose of your component's global storage. Also, set your component's function result to noErr if your component successfully handled the request; otherwise, set it to a nonzero value.

Handling Panel Items

Your control panel extension typically receives an item-select request (indicated by the kPanelItemSelect request code) when the user clicks in one of your panel's items. When your component receives an item-select request, it should perform the appropriate action for the selected item.

Note that when a click in one of your panel's items occurs, the owning control panel first sends your component an event-select request, giving your component a chance to filter the event, if necessary. A control panel sends your component an item-select request only if your component returns FALSE in the handled parameter in response to an event-select request. Typically, your component only returns FALSE in response to an event-select request if the event is a mouse event. The event-select request is discussed in detail in the next section, "Handling Events in a Panel" beginning on page 5-17.

The relevant fields in the component parameters record when your component receives an item-select request are:

what
This field is set to kPanelItemSelect.
params
The first entry in this array contains the dialog pointer of the owning control panel. The dialog box can be a color dialog box on systems that support color windows. The second entry contains the item number of the item selected by the user. Note that to map the item number to an item in your panel, you must offset the item number by the number of items in the owning control panel.
You must set your component's function result to noErr in response to an item-select request; otherwise, the owning control panel closes the panel.

Listing 5-5 shows an example of a control panel extension-defined routine that handles an item-select request.

Listing 5-5 Responding to an item-select request

FUNCTION MyPanelItemSelect(globals: PanelGlobalsHandle; 
                           cpDialogPtr: DialogPtr;
                           itemHit: Integer): ComponentResult;
BEGIN
   MyPanelItemSelect := noErr; {set return value}
   {adjust item number to take into account control panel's items}
   itemHit := itemHit - (globals^^).itemsOffset;
   CASE itemHit OF
      kExampleBeepButton:  {user clicked beep button}
         SysBeep(40);
      kExampleOtherButton: {user clicked this button}
         MyPanelItemSelect := MyDoButton(cpDialogPtr, itemHit);
      kExampleRadioButton1:{user clicked this radio button}
         MyPanelItemSelect := MySetRadioButton(cpDialogPtr,
                                              itemHit);
      kExampleRadioButton2:{user clicked this radio button}
         MyPanelItemSelect := MySetRadioButton(cpDialogPtr,
                                              itemHit);
      kDefaultButton:      {user clicked the default button}
         MyPanelItemSelect := 
                        MyDoDefaultButtonAction(cpDialogPtr,
                                                itemHit);
   END; {of CASE}
END;

Handling Events in a Panel

A control panel sends an event-select request (indicated by the kPanelEventSelect request code) to your extension whenever an event occurs in your panel. The event-select request is intended to provide your extension with the ability to respond just like an event filter function specified in calls to the ModalDialog procedure or other Dialog Manager routines. A control panel sends your extension the event-select request to give it an opportunity to intercept events in its panel and handle events before, or instead of the owning control panel. For example, you can change a keystroke into a click on an item, use idle time during null events, or track the movement of the cursor through mouse events.

The relevant fields in the component parameters record when your component receives an event-select request are:

what
This field is set to kPanelEventSelect.
params
The first entry in this array contains the dialog pointer of the owning control panel. The second entry contains the item offset to your panel's first item. Note that to map the item number to an item in your panel, you must offset the item number by the number of items in the owning control panel. The third entry contains an event record describing the event. If your extension handles the event, it should return in the fourth entry the item number of the associated panel item. On exit, your extension should indicate in the fifth entry whether it has handled the event by returning TRUE (handled the event) or FALSE (did not handle the event).
When your extension receives an event-select request, it indicates (through the fifth entry in params) whether it handled the event or not. Typically, your extension responds to an event-select request in this manner:

In general, for all other events, your extension should return FALSE (in the fifth entry of params) and allow the owning control panel to handle the event. However, note that if your extension returns FALSE, the owning control panel calls your extension with the item-select request code. See the previous section, "Handling Panel Items" on page 5-16 for information on handling clicks in your panel's items.

Listing 5-6 shows an example of a control panel extension-defined routine that handles the event-select request.

Listing 5-6 Responding to an event-select request

FUNCTION MyPanelEvent (globals: Handle; dialog: DialogPtr; 
                       itemOffset: Integer; 
                       theEvent: eventRecord; 
                       VAR itemHit: Integer; 
                       VAR handled: Boolean): ComponentResult;
VAR
   itemType:      Integer;
   itemHandle:    Handle;
   itemRect:      Rect;
   finalTicks:    LongInt;
BEGIN
   MyPanelEvent := noErr;
   CASE theEvent.what OF
      keyDown, autoKey:
      BEGIN
         CASE ((char)(theEvent->message & charCodeMask))
            kEnterKey, kReturnKey:
            BEGIN    {respond as if user clicked Default button}
               itemHit := kDefaultButton + itemOffset;

               GetDialogItem(dialog, itemHit, itemType,
                             itemHandle, itemRect);
               HiliteControl(ControlHandle(itemHandle),inButton);
               Delay(kVisualDelay, finalTicks);
               HiliteControl(ControlHandle(itemHandle),0);
               MyPanelEvent := 
                        MyDoDefaultButtonAction(dialog, itemHit);
            END;
         OTHERWISE
      {let control panel/Dialog Mgr handle other keyboard events}
            handled := FALSE;
      END; {of CASE keyDown, autoKey}
      updateEvt:
         DoUpdatePanel(globals, dialog);
      OTHERWISE
      {let owning control panel & Dialog Mgr handle other events}
         handled := FALSE;
   END; {of CASE}
END;

Handling Title Requests

A control panel may send your control panel extension a title request to determine the name it should display for the panel in the control panel's pop-up menu. Note that a control panel usually uses the name of your component as the name to display.

The relevant fields in the component parameters record when your component receives a title request are:

what
This field is set to kPanelGetTitleSelect.
params
The first entry in this array contains a value that identifies a specific instance of your component. In the second entry of this array, your component should return the name you want displayed in the pop-up menu associated with your panel.
Note
Current versions of the Sound and Video control panels do not send the kPanelGetTitleSelect request code.

Managing Control Panel Settings

A control panel may send the kPanelValidateInputSelect, kPanelGetSettingsSelect, or kPanelSetSettingsSelect request codes to your extension to request it to validate the settings of its items, or return or set the current settings of its items. If a control panel sends this request code, your extension should respond appropriately.

Note
Current versions of the Sound and Video control panels do not send the kPanelValidateInputSelect, kPanelGetSettingsSelect, or kPanelSetSettingsSelect request code.

Previous Book Contents Book Index Next

© Apple Computer, Inc.
6 JUL 1996

Get information on Apple products.
Visit the Apple Store online or at retail locations.
1-800-MY-APPLE

Copyright © 2004 Apple Computer, Inc.
All rights reserved. | Terms of use | Privacy Notice