version 2004.1 (Modified)
A plug-in area is an area (in a form or in its own window) that belongs to the plug-in.
Once created, 4th Dimension send it all the necessary messages and events to deal with the user: mouse clicks, keystrokes, menu selection, drag and drop, idle, etc
To create an external area, you use the PluginWizard, selecting the necessary options. An area can exist only within a form (its name starts with a "%"), or it can run within a form and in its own window (its name starts with an underscore, it then is listed in the "Plug-ins" menu of the User mode).
Here are the subjects that will be discussed in this section:
- brief look at the skeleton of the code of an external area
- Area events
- Area Reference
- GrafPort and DeviceContext
- Using properties and Advanced properties
- build a sample area that demonstrates the basic way to code an external area.
Overview of external areas
An external area receives events that are passed to it by 4th Dimension. Its main routine usually consist of a dispatch of the events the plug-in wants to respond.
When 4th Dimension wants to interact with the area, it calls the PluginMain routine with the appropriate selector value (as usual, it is the index of the routine in the STR# resource of the plug-in PluginWizard takes care of that). 4D passes the plug-in the standard pointer PA_PluginParameters, which contains different kind of data depending of the event that has to be managed.
Those events are listed in an enum of type AE_AreaEvent that is defined in the "PublicTypes.h" header file of this API.
typedef enum { eAE_Idle = 0, eAE_MouseDown = 1, eAE_MouseUp = 2, eAE_KeyDown = 3, eAE_KeyUp = 4, . . . . . . eAE_DesignInit = 605 } PA_AreaEvent;
For each event, the plug-in can do about whatever it wants. Some events are critical for the plug-in, such as initialization/de-initialization of private data.
Basically, there are 3 kinds of events.
- Design environment events: customizing the area (especially with the Advanced Properties Dialog), draw it in the form editor.
- Initialization/De-initialization events at Runtime: the plug-in allocates memory, get some default values from the Advanced Properties.
- Runtime events: mouse events, key strokes, drag and drop
So, here is a skeleton of the code of a plug-in area:
void PluginMain( long selector, PA_PluginParameters params ) { // Dispatch selector switch ( selector ) { case kInitPlugin : // Enter some initialization code for the plug-in break;
case kDeinitPlugin : // enter some de-initialization code here if needed break; case 1 : // enter code for first routine here break;
case 2 : // enter code for second routine here break; . . . . . . case 11 : // in this sample, the 11th selector is an external area // Call the dispatcher DoMyPluginArea( params ); break;
} }
void DoMyPluginArea ( PA_PluginParameters params ) { /* . . . local variables . . . */ PA_AreaEvent event;
// Get the event event = PA_GetAreaEvent( params ); // to dispatch it: switch ( event ) { // =========================initialization/DE-INITIALIzATION AT RUNTIME case eAE_Init : // If any, get the advanced properties. // Allocate some private data (4th Dimension will give back this data at each call) . . . break;
case eAE_Deinit : // If any, get our private data and dispose of it . . . break; // ========================= Runtime EVENTS // 4D wants to know if our area is able to be focused case eAE_IsFocusable : . . . break;
// 4D tells us it is time to redraw our area case eAE_Update : . . . break;
// We've got a mousedown in our area case eAE_MouseDown: . . . break;
// and so on with other Runtime events . . .
// ========================= DESIGN MODE EVENTS // First time the form is opened in Design Mode case eAE_DesignInit : . . . break;
// 4D wants to redraw the area in a form, in design mode case eAE_DesignUpdate : . . . break;
// 4D asks us if we want to use the AdvancedProperties Dialog button case eAE_AreAdvancedPropertiesEditable : . . . break;
// We edit the properties case eAE_EditAdvancedProperties : . . . break;
// 4D has finish with the properties and has stored them with the form // we can now dispose of it. case eAE_DisposeAdvancedProperties : . . . break;
// ========================= OTHER EVENTS ARE IGNORED default : PA_DontTakeEvent(params); break; }
}
Area events
The different events an area can manage are listed in a PA_AreaEvent enumeration, in the "PublicTypes.h" header file.
eAE_Init | initialization at Runtime. Area can initialize itself, read properties |
(PA_GetPluginProperties, PA_GetAdvancedProperties), set values using | |
advanced properties. | |
eAE_Deinit | De-initialization at Runtime (release memory if necessary). |
eAE_Idle | The user does nothing. The plug-in could, as an example, draw a clock. |
A polite plug-in should not take too much time on this event. Area | |
can call PA_CallPluginAreaMethod. | |
eAE_MouseDown | The mouse button is down. Area can call PA_CallPluginAreaMethod. |
eAE_MouseUp | The mouse button is up. |
eAE_KeyDown | A key is pressed. Area can call PA_CallPluginAreaMethod. |
eAE_Update | Area must be updated, redrawn. |
aAE_Cursor | Mouse has moved (even if the area is not selected) over the area. |
eAE_IsFocusable | 4D wants to know if the plug-in can get the focus by tab (use |
PA_SetAreaFocusable to answer). | |
eAE_Select | The user wants to select the area (PA_AcceptSelect). |
eAE_Deselect | The user wants to leave the area (PA_AcceptDeselect). |
eAE_Scroll | Area rect has changed, call PA_GetAreaRect. |
eAE_TestPrintSize | |
eAE_GetPrintSize | |
eAE_PrintBand | |
eAE_UndoCommand | User selects "Undo" in the Edit menu. |
eAE_CutCommand | User selects "Cut" in the Edit menu. |
eAE_CopyCommand | User selects "Copy" in the Edit menu. |
eAE_PasteCommand | User selects "Paste" in the Edit menu. |
eAE_SelectAllCommand | User selects "Select all" in the Edit menu. |
eAE_SelectDeselect | |
eAE_UpdateEditCommands | 4D asks the plug in to redraw the Edit menu (PA_UpdateEditMenu). |
eAE_LoadRecord | A record of the table of the form that owns the area is loaded. The area |
can get a field of this record (ie, a field whose name is areaName_, like | |
4DWrite). | |
eAE_SaveRecord | The record of the table that owns the area is saved. The area can save |
its content back to a field. | |
eAE_AllowDrop | Area accepts a drop ? (call PA_AllowDrop). If it accepts, it can get |
information about the drop (PA_GetDragAndDropInfo, | |
PA_GetDragAndDropKind, PA_GetDragAndDropVariable, | |
PA_GetDragAndDropField). | |
eAE_Drag | An object is dragged over the area. Get info about the dragging position |
of the object (PA_GetDragPositions). Tell 4D the area will handle itself | |
the way it will show that something is dragging over | |
(PA_CustomizeDragOver). | |
eAE_Drop | Something is dropped, get its value (PA_GetDragAndDropInfo, |
PA_GetDragAndDropKind, PA_GetDragAndDropVariable, | |
PA_GetDragAndDropField). | |
eAE_GetMenuIcon | 4D requests the ID of the 'cicn' resources used in plug-in menu of the |
tools palette in the Form Editor. | |
eAE_EndExecutionCycle | End of an execution cycle in the form. Use PA_RequestRedraw to refresh |
the area. | |
eAE_PageChange | The current form page has changed. Use PA_GetCurrentPage. |
eAE_DesignInit | Layout is opened in Design Mode |
eAE_DesignUpdate | Area must be redrawn in Design Mode. Can use |
PA_GetPluginProperties. | |
eAE_AreAdvancedPropertiesEditable | 4D wants to know if the Area uses the "Advanced |
Properties. Area answers with | |
PA_SetAdvancedPropertiesEditable. | |
eAE_EditAdvancedProperties | User clicks on "Edit " button of the Advanced Properties. |
Call PA_SetAdvancedProperties. | |
eAE_DisposeAdvancedProperties | 4D has stored the advanced properties in the form |
properties, you can now dispose this data | |
(PA_GetAdvancedPropertiesToDispose). |
Calling Area object method
It is possible for the area to call its own 4D object method. In order to do this, the area must have the On plug in area event checked. If this event is set, then the area can call its own object method by calling PA_CallPluginAreaMethod.
This call is available for only 3 events : eAE_MouseDown, eAE_KeyDown, and eAE_Idle.
The example of uses are numerous :
- Let the 4D developer filter each key pressed
- we can imagine an area that makes some hard math calculation during its idle time, and, once done, it calls its object method to inform the user the operation is done.
- An other example could be a time-bombed plug-in, in which an area is available only for x seconds or minutes in a form.
- . . .
Ignoring events
We have seen that a typical area code consists of a dispatch of the received event. It is a good habit to use the "default" keyword of the switch operator, and to call PA_DontTakeEvent at this time. This way, 4D can handle the event for itself if necessary.
Area Reference
Usually, an area will need to have some private data, handling miscellaneous information about itself. To get and retrieve this data, the plug-in should follow those instructions :
- allocate a block of memory at initialization time (eAE_AreaInit), and fill it with the desired values. Once initialized, the area calls PA_SetAreaReference, passing it a pointer to the allocated memory, and its size. This "links" this particular pointer to this particular area.
- Each time the area is called, whatever the event is, it can get back a pointer to its private data by calling PA_GetAreaReference.
Here is a sample code using this. The area stores the number of time it has been called with a eAE_Idle event and a eAE_Update event. When the user clicks in the area, the area displays an alert given those number. An area can use whatever it wants as private data, just remember that this data is stored in RAM.
typedef struct { long idleCount; long updateCount; }AREA;
void DoMyArea (PA_PluginParameters params) { AREA *privateData; // the pointer to our data PA_AreaEvent event; event = PA_GetAreaEvent(params); switch(event) { case eAE_AreaInit: // So, we allocate our data privateData = (AREA *) malloc(sizeof(AREA)); // we initialize it (no error check here) privateData->idleCount = 0; privateData-> updateCount = 0; // Then, we link it to the area PA_SetAreaReference(params, privateData, sizeof(AREA)); break // ========================================================= // From now, each time we call PA_GetAreaReference, we get // our pointer to the AREA private structure allocated // ========================================================= case kAE_AreaDeinit: // Free the memory used privateData = (AREA *) PA_GetAreaReference(params); free(privateData); break;
case kAE_Idle: privateData = (AREA *) PA_GetAreaReference(params); privateData-> idleCount ++ break;
case kAE_Update: privateData = (AREA *) PA_GetAreaReference(params); privateData-> updateCount ++; break;
case kAE_MouseDown: DisplayData(params); break;
default: PA_DontTakeEvent(); break; } }
NOTE
Although the private data is usually allocated at eAE_AreaInit event, it can be allocated at any time, provided that a previously allocated data is disposed before.
Where to draw (GrafPtr, HDC, rectangle) ?
To draw its area, the plug-in must have a valid GrafPort under MacOS or a valid DeviceContext under Windows (again, if the developer uses Altura under Windows, GrafPorts should be used). The drawing region is already clipped for you, limited to the plug-in area.
- Under MacOS, the port is already fixed by 4th Dimension to the current window that holds the area. If you want to draw in the area from another place, you usually get the port at initialization phase.
- Under Windows, you use the PA_GetUpdateHDC routine to get a handle to the current device context, already prepared to draw only in the area.
So, under Windows, at eAE_Update event, the code will looks like:
/* previous switch */ case eAE_Update: { HDC hdc; HBRUSH hbrush; RECT rect; HGDIOBJ old; PA_Rect areaRect; // Get the current HDC hdc = (HDC) PA_GetUpdateHDC(); rect = PA_GetAreaRect( params ); // transfer areaRect in the "regular" RECT MyPA_RectToRECT(areaRect, &rect); // Paint something hbrush = CreateSolidBrush( 0 ); old = SelectObject( hdc, hbrush ); FrameRect( hdc, &rect, hbrush ); SelectObject( hdc, old ); } break;
/* . . . next switch . . . */
The area coordinates in the window, its rectangle, is updated if the user used the scrollbars. This rectangle is expressed in local coordinates to the window, and, when drawing or writing code that uses this rectangle, the developer should always call PA_GetAreaRect.
Note that it is also possible to save the port/device that holds the area at eAE_AreaInit, by reading the appropriate fields of the PA_PluginProperties structure (fMacWindow, fMacPort, fWinHWND, and fWinHDC).
Using properties and Advanced properties
Plug-in properties (defined in the PA_PluginProperties structure) are settings that the 4D developer has selected in Design Mode for the area. Using that information, the area can act according to the user choices:
- font (ID, fac, size, color),
- colors (fore and back),
- justification,
- owning table
- area is draggable/droppable
- platform
- The plug-in properties contains also some information updated depending on the context: the area is being printed
The advanced properties are a fast way to customize, at Runtime, an area. The 4D developer can set different values or settings of an area in Design Mode. At Runtime, the area get those settings and apply them. This is much easy-to-use for the 4D developer than calling long sequences of "plug_Set This", "plug_Set that" routines at "On load" form event.
In order to manage the advanced properties, 4D and the plug-in will talk each others. The scheme of use is this one.
o In Design mode
- 4D asks the area if it wants to use AdvancedProperties, calling the area with the event eAE_AreAdvancedPropertiesEditable. It the area answers "yes" (PA_SetAdvancedPropertiesEditable(params, 1)), 4D enables the "Edit " button of the "Advanced properties", in the properties palette.
- When the user clicks this button, 4D calls the area with the eAE_EditAdvancedProperties event. The area interacts with the user (it can use the Dialogs routines of this API), and then call PA_SetAdvancedProperties to 4D, so they will be stored with the form and 4D can give back those properties at Runtime.
- Once 4D has stored the advanced properties, it calls the plug-in with the eAE_DisposeAdvancedProperties event. The plug-in gets back a pointer to its advanced properties data by calling PA_GetAdvancedPropertiesToDispose. It can then dispose of it.
o At Runtime
If the plug-in has some advanced properties, it can can get them back at initialization time, by calling first PA_GetPluginProperties, passing those properties as a parameter to PA_GetAdvancedProperties. This routine extracts the advanced properties from the plug-in properties.
IMPORTANT NOTE
The PA_PluginProperties (and, so, the AdvancedProperties) are available ONLY AT eAE_AreaInit at Runtime (or at update in the form editor). If the plug-in needs to use later some information handled by those structures, it MUST read them at initialization time and duplicate them in its private data structure.
If PA_GetPluginProperties is called for an other event, PA_GetLastError returns an error.
Since the AdvancedProperties are passed as a pointer to a block of memory, using PA_GetPluginProperties (without checking the PA_GetLastError) and PA_GetAdvancedProperties when it is not the time can lead to a system crash, since the received pointer is a fool pointer.
To avoid this problem, and if the area needs to use later some information stored in the properties or in the advanced properties, it should duplicate the structures (or some fields of those structure) in its private data, as shown in the following sample (no error checking shown) :
// Structure used by the advanced properties typedef struct { long field1; long field2; char string1[256]; /* . . . */ } ADVANCED;
// Structure used as private data of the area typedef struct { PA_PluginProperties props; ADVANCED advanced; /* . . . */ } AREA;
// Main routine of the area void DoMyArea (PA_PluginParameters params) { AREA *areaData; AE_AreaEvent event; long size; event = PA_GetAreaEvent(params); switch (event) { // initialization. case eAE_AreaInit: // We allocate an AREA structure areaData = (AREA *) malloc(sizeof(AREA)); // We save the properties PA_GetPluginProperties(params, &areaData->props); // We save the advanced properties areaData->advanced = PA_GetAdvancedProperties(areaData->props); // Link areaData to this area PA_SetAreaReference(params, areaData, sizeof(AREA); break; case eAE_AreaDeinit; // Free memory areaData = (AREA *) PA_GetAreaReference(params); free(areaData); break; /* From now, each time we need to use the properties (to draw using font, face, size, color set by the 4DE developer) or the advanced properties, we just have to use: areaData = (AREA *) PA_GetAreaReference(params); and to access the different fields. */ } }