Important: The information in this document is obsolete and should not be used for new development.
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.
- return information about the items in its panel
- handle user actions and other events in its panel
- get and set the values of its items
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.
The component resource binds together all the relevant resources contained in a component; its structure is defined by the
- 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.
ComponentResource
data type.
TYPE ComponentResource = RECORD cd: ComponentDescription; component: ResourceSpec; componentName: ResourceSpec; componentInfo: ResourceSpec; componentIcon: ResourceSpec; END;Thecd
field contains a component description record that specifies the component type, subtype, manufacturer, and flags. Thecomponent
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 thecomponentType
field of the component description record referenced through thecd
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. TheResourceSpec
data type has this structure:
TYPE ResourceSpec = RECORD resourceType: ResType; resourceID: Integer; END;ThecomponentName
field of theResourceSpec
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 theComponentResource
structure is a component description record, which includes additional information about the component. A component description record is defined by theComponentDescription
data structure.
TYPE ComponentDescription = RECORD componentType: LongInt; componentSubType: LongInt; componentManufacturer: LongInt; componentFlags: LongInt; componentFlagsMask: LongInt; END;For control panel extensions, thecomponentType
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, thecomponentSubType
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.
You can assign any value you like to the
- Note
- Apple reserves for its own uses all types and subtypes composed solely of lowercase letters.
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}ThechannelFlagDontOpenResFile
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 thecomponentType
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;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:
- Note
- For complete details on required component request codes, see the chapter "Component Manager" in Inside Macintosh: More Macintosh Toolbox.
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 theparams
field of the component parameters record. Theparams
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 theCallComponentFunction
orCallComponentFunctionWithStorage
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 thewhat
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
andCloseComponentResFile
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. TheOpenComponentResFile
routine requires theComponentInstance
parameter supplied to your routine. You should not call the Resource Manager routinesOpenResFile
orCloseResFile
.
The following sections illustrate how to write control panel extension functions that respond to panel-specific request codes.
- 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.
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:
In response to a get-item list request, set your component's function result to
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.
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:
In response to an install request, set your component's function result to
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.
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;TheMyPanelInstall
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:
In response to a remove request, dispose of any additional dialog data you created (for example, if you created a list, call
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.
LDispose
), but do not dispose of your component's global storage. Also, set your component's function result tonoErr
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 thekPanelItemSelect
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 thehandled
parameter in response to an event-select request. Typically, your component only returnsFALSE
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:
You must set your component's function result to
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.
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 thekPanelEventSelect
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 theModalDialog
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:
When your extension receives an event-select request, it indicates (through the fifth entry in
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) orFALSE
(did not handle the event).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
- maps the Return or Enter key to the default button, performs the action corresponding to the default button, and returns
TRUE
and the item number of the default button through entries inparams
- maps the Esc (Escape) key or Command-period combination to the Cancel button (if any), performs the action corresponding to the Cancel button, and returns
TRUE
and the item number through entries inparams
- updates the panel if needed (typically updating only those items that need updating apart from the standard updating performed by the Dialog Manager, such as user-defined panel items or lists) and returns
TRUE
and the item number of the default button through entries inparams
- activates certain panel items (such as lists) as necessary and returns
TRUE
- maps keyboard equivalents (if any) to corresponding item numbers, performs the corresponding action for that item number, and returns
TRUE
- tracks movement of the cursor as needed (typically tracking the cursor only in those items, such as user-defined items or lists, that the Dialog Manager doesn't handle) and returns
TRUE
FALSE
(in the fifth entry ofparams
) and allow the owning control panel to handle the event. However, note that if your extension returnsFALSE
, 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 thekPanelValidateInputSelect
,kPanelGetSettingsSelect
, orkPanelSetSettingsSelect
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
, orkPanelSetSettingsSelect
request code.