Copyright (c) 2012-2016 Hyperion Entertainment and contributors.

BOOPSI Gadgets

From AmigaOS Documentation Wiki
Jump to: navigation, search

Introduction

One of the major enhancements to Intuition is the implementation of customizable BOOPSI gadgets. BOOPSI gadgets are not limited by dependencies upon Intuition Image and Gadget structures. Unlike ordinary gadgets, which were handled exclusively by Intuition, BOOPSI gadgets handle their own rendering and their own user input.

Since BOOPSI gadgets draw themselves, there is almost no restriction on what they can look like. A BOOPSI gadget can use graphics.library RastPort drawing functions to draw vector-based imagery which the gadget can scale to any dimension. Instead of just a two-state Boolean gadget, a BOOPSI gadget can have any number of states, each of which has its own imagery. If a programmer wanted to he could even make a BOOPSI gadget that uses the animation system to render itself.

Because BOOPSI gadgets handle their own input, they see all the user's input, which the gadget is free to interpret. While the user has a BOOPSI gadget selected, the gadget can track mouse moves, process mouse and keyboard key presses, or watch the timer events.

The power of a BOOPSI gadget is not limited to its ability to handle its own rendering and user input. BOOPSI gadgets are also BOOPSI objects so the gain all the benefits BOOPSI provides. This means all BOOPSI gadgets inherit the methods and attributes from their superclasses. BOOPSI gadgets can use BOOPSI images to take care of rendering their imagery. A BOOPSI gadget could be a "composite" gadget that is composed of several BOOPSI gadgets, images, and models.

The BOOPSI Gadget Methods

Intuition drives a BOOPSI gadget by sending it BOOPSI messages. Intuition uses the following BOOPSI methods:

Method Description
GM_DOMAIN Obtain gadget sizing requirements.
GM_EXTENT Inquire about rendering extent. (V51)
GM_GOACTIVE This method asks a gadget if it wants to be the active gadget.
GM_GOINACTIVE This method tells a gadget that it is no longer active.
GM_HANDLEINPUT This method passes a gadget an input event.
GM_HELPTEST Determine if gadget help was hit.
GM_HITTEST This method asks a gadget whether it has been "hit" by a mouse click.
GM_LAYOUT Calculate relative gadget coordinates.
GM_RENDER This method tells the gadget to render itself.

The formats of each of these BOOPSI messages differ, but they all have two things in common. Like all BOOPSI messages, each starts with their respective method ID. For each of these methods, the method ID field is followed by a pointer to a GadgetInfo structure (defined in <intuition/cghooks.h>). The GadgetInfo structure contains information about the display on which the gadget needs to render itself:

struct GadgetInfo {
    struct Screen               *gi_Screen;
    struct Window               *gi_Window;     /* null for screen gadgets */
    struct Requester            *gi_Requester;  /* null if not GTYP_REQGADGET */
 
    /* rendering information: don't use these without cloning/locking.
     * Official way is to call ObtainGIRPort()
     */
    struct RastPort             *gi_RastPort;
    struct Layer                *gi_Layer;
 
    /* copy of dimensions of screen/window/g00/req(/group)
     * that gadget resides in.  Left/Top of this box is
     * offset from window mouse coordinates to gadget coordinates
     *  screen gadgets:                 0,0 (from screen coords)
     *  window gadgets (no g00):        0,0
     *  GTYP_GZZGADGETs (borderlayer):  0,0
     *  GZZ innerlayer gadget:          borderleft, bordertop
     *  Requester gadgets:              reqleft, reqtop
     */
    struct IBox                 gi_Domain;
 
    /* these are the pens for the window or screen      */
    struct {
        UBYTE   DetailPen;
        UBYTE   BlockPen;
    }                           gi_Pens;
 
    /* the Detail and Block pens in gi_DrInfo-&gt;dri_Pens[] are
     * for the screen.  Use the above for window-sensitive colors.
     */
    struct DrawInfo             *gi_DrInfo;
 
    /* reserved space: this structure is extensible
     * anyway, but using these saves some recompilation
     */
    ULONG                       gi_Reserved[6];
};

All the fields in this structure are read only.

Although this structure contains a pointer to the gadget's RastPort structure, applications should not use it for rendering. Instead, use the intuition.library function ObtainGIRPort() to obtain a copy of the GadgetInfo's RastPort. When the gadget is finished with this RastPort, it should call ReleaseGIRPort() to relinquish the RastPort.

GM_DOMAIN

Intuition uses the GM_DOMAIN method to determine the basic sizing requirements of an object. The domain is most often used by layout gadgets to aid in distributing the gadgets within the layout via GM_LAYOUT. The GM_DOMAIN method uses the gpDomain structure (defined in <intuition/gadgetclass.h>) as its message:

struct gpDomain
{
    uint32		 MethodID;
    struct GadgetInfo	*gpd_GInfo;
    struct RastPort	*gpd_RPort;	/* RastPort to layout for */
    int32		 gpd_Which;
    struct IBox		 gpd_Domain;	/* Resulting domain */
    struct TagItem	*gpd_Attrs;	/* Additional attributes */
};

The gpd_Which field is used to determine which domain the caller is interested in: GDOMAIN_MINIMUM, GDOMAIN_NOMINAL or GDOMAIN_MAXIMUM. The object is expected to fill in the provided gpd_Domain with the width and height dimensions. The location coordinates are not used. The gpd_Attrs pointer, if not NULL, may contain additional tags which are defined on a per-class basis.

GM_EXTENT (V51)

The GM_EXTENT method is used to ask the gadget what pixels (at least) it will fully redraw when its GM_RENDER method is invoked in the same context. By "fully redraw" we mean changing the pixel's color in a way that is totally unrelated to its previous value -- so this doesn't apply to alpha-blended pixels for example.

Intuition uses that information for optimization purposes. During GUI refreshes, it will skip filling or erasing those pixels that the gadget would then completely re-render anyway. Supporting this method in your gadgets will help Intuition improve the smoothness of user interface refresh by preventing redundant graphic calls and minimizing "flicker" effects caused by background clearing especially during window resizes.

Simple Support

The easiest way to support GM_EXTENT is to make sure your gadget always fills every pixel within its dimensions and just return the relevant result value (GMR_FULLHBOX or GMR_FULLBBOX). In this case, you may safely ignore the actual message contents and the following text.

If the above solution is not feasible (for instance because the gadget has an irregular or not fully connected shape) then you should check the gpe_Region and gpe_RPort message fields: if any of them is non-NULL you may decide to employ a more detailed way to tell the caller exactly what pixels your GM_RENDER method does fill.

Region and RastPort Support

If gpe_Region is non-NULL you can compose in it your gadget's shape by using graphics.library's XxxxRectRegion() functions. Remember to look at the gpe_Action field to determine what function to use. For example, if gpe_Action is GEXTENT_ADD you should use OrRectRegion(). Note you must NOT pre-clear or alter the initial region's contents in any way other than to compose your own gadget's shape. Once finished composing your gadget's shape in the region, return GMR_CLIPDONE to let the caller know you updated the region's contents.

If gpe_RPort is non-NULL you can draw your gadget's shape into it just like you would do for a GM_RENDER message -- however, you're only allowed to use colors 0 or 1 because you're actually drawing into a single-bitplane mask. Again, check the gpe_Action field to find out whether you should actually set, clear or invert the pixels making up your gadget's shape in the mask. You are not allowed to alter any pixels other than those belonging to your gadget's shape nor to clear the mask's background before rendering. Once finished drawing your gadget's shape in the mask plane return GMR_MASKDONE to let the caller know you updated the mask's contents.

If both gpe_Region and gpe_RPort are non-NULL you may simply choose the method which is most suited for your purposes. You don't have to support both for the same message. If you do, however, you can let the caller know by ORing together the appropriate return values. The region solution is best suited for gadgets made up of a few rectangular parts whereas the mask method is better in the case of more complex gadget shapes.

Images

If all or part of your gadget's rendering is performed by some BOOPSI image you could also try asking the image for its extent information by way of an IM_EXTENT or IM_EXTENTFRAME message (see BOOPSI Images).

If for whatever reasons your GM_EXTENT method finds itself unable to support any of the described solutions it should return GMR_INVALID. This particular return value will tell Intuition not to clip away the gadget's shape at all. While this is usually slower and more prone to flickering it will still produce correct graphic results. This is the same fallback applied for any gadgets not recognizing the GM_EXTENT method.

Superclass Extent Handling

Since it is very important that a gadget's GM_RENDER and GM_EXTENT methods remain synchronized the default behavior of a gadget class should be to only handle GM_EXTENT if it is the "true class" of the object the method is invoked on and just return GMR_INVALID otherwise. This is because a subclass might override the behavior of GM_RENDER in such a way that your class' GM_EXTENT results are no longer correct.

If a gadget subclass needs and knows it's ok to let its superclass handle GM_EXTENT it must set the GPEF_ALLOWSUPER flag in gpe_Flags before calling IDoSuperMethodA() and clear it as soon as the call returns. If GPEF_ALLOWSUPER is set in gpe_Flags your gadget should accept and handle GM_EXTENT regardless of whether it is the true class or not.

Note
As of Intuition V51 the mask method is not yet implemented.

GM_GOACTIVE/GM_HANDLEINPUT

If a gadget returns GMR_GADGETHIT, Intuition will send it a GM_GOACTIVE message (defined in <intuition/gadgetclass.h>):

struct gpInput                          /* Used by GM_GOACTIVE and GM_HANDLEINPUT */
{
    ULONG             MethodID;
    struct GadgetInfo *gpi_GInfo;
    struct InputEvent *gpi_IEvent;      /* The input event that triggered this method
                                         * (for GM_GOACTIVE, this can be NULL) */
    LONG              *gpi_Termination; /* For GADGETUP IntuiMessage.Code */
    struct
    {
        WORD X;                         /* Mouse position relative to upper          */
        WORD Y;                         /* left corner of gadget (LeftEdge, TopEdge) */
    } gpi_Mouse;
};

The GM_GOACTIVE message gives a gadget the opportunity to become the active gadget. The active gadget is the gadget that is currently receiving user input. Under normal conditions, only one gadget can be the active gadget (it is possible to have more than one active gadget using a groupgclass object (See the BOOPSI Class Reference for more details).

While a gadget is active, Intuition sends it GM_HANDLEINPUT messages. Each GM_HANDLEINPUT message corresponds to a single InputEvent structure. These InputEvents can be keyboard presses, timer events, mouse moves, or mouse button presses. The message's gpi_IEvent field points to this InputEvent structure. It's up to the GM_HANDLEINPUT method to interpret the meaning of these events and update the visual state of the gadget as the user manipulates the gadget. For example, the GM_HANDLEINPUT method of a prop gadget has to track mouse events to see where the user has moved the prop gadget's knob and update the gadget's imagery to reflect the new position of the knob.

For the GM_GOACTIVE method, the gpi_IEvent field points to the struct InputEvent that triggered the GM_GOACTIVE message. Unlike the GM_HANDLEINPUT message, GM_GOACTIVE's gpi_IEvent can be NULL. If the GM_GOACTIVE message was triggered by a function like intuition.library's ActivateGadget() and not by a real InputEvent (like the user clicking the gadget), the gpi_IEvent field will be NULL.

For gadgets that only want to become active as a direct result of a mouse click, this difference is important. For example, the prop gadget becomes active only when the user clicks on its knob. Because the only way the user can control the prop gadget is via the mouse, it does not make sense for anything but the mouse to activate the gadget. On the other hand, a string gadget doesn't care how it is activated because, as soon as it's active, it gets user input from the keyboard rather than the mouse. Not all gadgets can become active. Some gadgets cannot become active because they have been temporarily disabled (their Gadget.Flags GFLG_DISABLED bit is set). Other gadgets will not become active because they don't need to process input. For example, a toggle gadget won't become active because it only needs to process one input event, the mouse click that toggles the gadget (which it gets from the GM_GOACTIVE message). If a toggle gadget gets a GM_GOACTIVE message and its gpi_IEvent field is not NULL, it will toggle its state and refuse to "go active".

The GM_GOACTIVE method has to take care of any visual state changes to a gadget that a GM_GOACTIVE message might trigger. For example, the toggle gadget in the previous paragraph has to take care of toggling its visual state from selected imagery to unselected imagery. If the gadget goes through a state change when it becomes the active gadget, (like when a string gadget positions its cursor) GM_GOACTIVE has to take care of this.

Return Values

The return values of both GM_GOACTIVE and GM_HANDLEINPUT tell Intuition whether or not the gadget wants to be active. A gadget's GM_GOACTIVE method returns GMR_MEACTIVE (defined in <intuition/gadgetclass.h>) if it wants to become the active gadget. A gadget's GM_HANDLEINPUT method returns GMR_MEACTIVE if it wants to remain the active gadget. If a gadget either does not want to become or remain the active gadget, it returns one of the "go inactive" return values:

GMR_NOREUSE
Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent.
GMR_REUSE
Tells Intuition to process the gpInput.gpi_IEvent InputEvent.
GMR_NEXTACTIVE
Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent and activate the next GFLG_TagCycle gadget.
GMR_PREVACTIVE
Tells Intuition to throw away the gpInput.gpi_IEvent InputEvent and activate the previous GFLG_TagCycle gadget.

GMR_NOREUSE tells Intuition that the gadget does not want to be active and to throw away the InputEvent that triggered the message. For example, an active prop gadget returns GMR_NOREUSE when the user lets go of the left mouse button (thus letting go of the prop gadget's knob).

For the GM_HANDLEINPUT method, a gadget can also return GMR_REUSE, which tells Intuition to reuse the InputEvent. For example, if the user clicks outside the active string gadget, that string gadget returns GMR_REUSE. Intuition can now process that mouse click, which can be over another gadget. Another case where a string gadget returns GMR_REUSE is when the user pushes the right mouse button (the menu button). The string gadget becomes inactive and the menu button InputEvent gets reused. Intuition sees this event and tries to pop up the menu bar.

For the GM_GOACTIVE method, a gadget must not return GMR_REUSE. If a gadget gets a GM_GOACTIVE message from Intuition and the message has an gpi_IEvent, the message was triggered by the user clicking on the gadget. In this case, Intuition knows that the user is trying to select the gadget. Intuition doesn't know if the gadget can be activated, but if it can be activated, the event that triggered the activation has just taken place. If the gadget cannot become active for any reason, it must not let Intuition reuse that InputEvent as the gadget has already taken care of the the event's purpose (clicking on the gadget). In essence, the user tried to activate the gadget and the gadget refused to become active.

The other two possible return values are GMR_NEXTACTIVE and GMR_PREVACTIVE. These tell Intuition that a gadget does not want to be active and that the InputEvent should be discarded. Intuition then looks for the next (GMR_NEXTACTIVE) or previous (GMR_PREVACTIVE) gadget that has its GFLG_TABCYCLE flag set in its Gadget.Activation field (see the gadgetclass GA_TabCycle attribute in the BOOPSI Class Reference).

For both GM_GOACTIVE and GM_HANDLEINPUT, the gadget can bitwise-OR any of these "go inactive" return values with GMR_VERIFY. The GMR_VERIFY flag tells Intuition to send a GADGETUP IntuiMessage to the gadget's window. If the gadget uses GMR_VERIFY, it has to supply a value for the IntuiMessage.Code field. It does this by passing a value in the gpInput.gpi_Termination field. This field points to a long word, the lower 16-bits of which Intuition copies into the Code field. The upper 16-bits are for future enhancements, so clear these bits.

GM_GOINACTIVE

After an active gadget deactivates, Intuition sends it a GM_GOINACTIVE message (defined in <intuition/gadgetclass.h>):

struct gpGoInactive
{
    ULONG             MethodID;    /* GM_GOINACTIVE */
    struct GadgetInfo *gpgi_GInfo;
    ULONG             gpgi_Abort; /* gpgi_Abort=1 if gadget was aborted by Intuition   */
                                  /* and 0 if gadget went inactive at its own request. */
};

The gpgi_Abort field contains either a 0 or 1. If 0, the gadget became inactive on its own power (because the GM_GOACTIVE or GM_HANDLEINPUT method returned something besides GMR_MEACTIVE). If gpgi_Abort is 1, Intuition aborted this active gadget. Some instances where Intuition aborts a gadget include: the user clicked in another window or screen, an application removed the active gadget with RemoveGList(), and an application called ActiveWindow() on a window other than the gadget's window.

GM_HELPTEST

This method uses the same message structure as GM_HITTEST, struct gpHitTest, and operates similarly to GM_HITTEST. While a window is in help mode, when the user positions the pointer within the bounding box of a BOOPSI gadget that supports gadget help, Intuition sends the gadget a GM_HELPTEST message. After an active gadget deactivates, Intuition sends it a GM_GOINACTIVE message.

struct gpHitTest
{
    uint32             MethodID;    // GM_HELPTEST
    struct GadgetInfo *gpht_GInfo;
    struct
    {
        int16 X;                    // Is this point inside
        int16 Y;                    // gadget?
    } gpht_Mouse;
};

Like the GM_HITTEST method, the GM_HELPTEST method asks a gadget if a point is within the gadget's bounds. Like the GM_HITTEST method, the GM_HELPTEST method allows a BOOPSI gadget to have a non-rectangular hit area (or in this case, a help area). If the point is within the gadget, the gadget uses one of two return codes. If the gadget returns a value of GMR_HELPHIT, Intuition places a value of 0xFFFF in the Code field of the IDCMP_GADGETHELP message. The gadget also has the option of using GMR_HELPCODE instead of GMR_HELPHIT. GMR_HELPCODE is a little peculiar as a return value. Although GMR_HELPCODE is 32 bits long, Intuition identifies GMR_HELPCODE using only its upper 16 bits. If the upper 16 bits of GM_HELPTEST's return value matches the upper 16 bits of GMR_HELPCODE, Intuition copies the lower word of the return value into the Code field of the IDCMP_GADGETHELP message. The BOOPSI gadget is free to set the return value's lower word to any 16-bit value.

If the point is not within the gadget's "help spot", the gadget returns GMR_NOHELPHIT. Intuition will then look under that gadget for more gadgets. If a gadget's dispatcher passes the GM_HELPTEST method on to the gadgetclass dispatcher, the gadgetclass dispatcher will always return GMR_HELPHIT.

An application can put several windows in the same help group. This feature groups several windows so that as long as one of those windows is active, the application can receive IDCMP_GADGETHELP messages about all of those windows. For more information, See the HelpControl() Autodoc and the WA_HelpGroup section of the OpenWindow() Autodoc (both are in intuition.doc).

GM_HITTEST

When Intuition gets a left mouse button click in a window, one of the things it does is check through the window's list of gadgets to see if that click was inside the bounds of a gadget's Gadget structure (using the LeftEdge, TopEdge, Width, and Height fields). If it was (and that gadget is a BOOPSI gadget), Intuition sends that gadget a GM_HITTEST message (defined in <intuition/gadgetclass.h>):

struct gpHitTest
{
    uint32             MethodID;    // GM_HITTEST
    struct GadgetInfo *gpht_GInfo;
    struct
    {
        int16 X;                    // Is this point inside
        int16 Y;                    // gadget?
    } gpht_Mouse;
};

This message contains the coordinates of the mouse click. These coordinates are relative to the upper-left of the gadget (LeftEdge, TopEdge).

Because Intuition can only tell if the user clicked inside gadget's "bounding box", Intuition only knows that the click was close to the gadget. Intuition uses the GM_HITTEST to ask the gadget if the click was really inside the gadget. The gadget returns GMR_GADGETHIT (defined in <intuition/gadgetclass.h>) to tell Intuition that the user hit it, otherwise it returns zero. This method allows a gadget to be any shape or pattern, rather than just rectangular.

GM_LAYOUT

Intuition uses the GM_LAYOUT method to tell a GREL BOOPSI gadget that its window's dimensions have changed. The GM_LAYOUT method uses the gpLayout structure (defined in <intuition/gadgetclass.h>) as its message:

struct gpLayout
{
    uint32              MethodID;
    struct GadgetInfo  *gpl_GInfo;
    uint32              gpl_Initial; /* This field is non-zero if this method was invoked
                                      * during AddGList() or OpenWindow().  zero if this
                                      * method was invoked during window resizing.
                                      */
};

For GREL gadgets, Intuition sends a GM_LAYOUT message just after erasing the gadget's bounding box. Intuition does not touch the values of the gadget's bounding box or hit box, it lets the gadget handle recalculating these values. The gadget can lay itself out based on the new window (or requester) dimensions found in the GadgetInfo structure passed in the GM_LAYOUT message (gpl_GInfo from the gpLayout structure). Intuition also adds the old and new bounding box of the gadget to the window's damaged regions so that the gadget will appear is its new position. The gadget must not perform any rendering inside the GM_LAYOUT method. Intuition will send a GM_RENDER message to the gadget when its time to redraw the gadget in its new position.

There are two cases where Intuition sends a GM_LAYOUT message:

  1. When the gadget's window is resized
  2. When Intuition adds the gadget to a window

For most GREL BOOPSI gadgets, Intuition expects the values in the LeftEdge, TopEdge, Width, and Height fields to follow the existing convention for regular GREL gadgets. Each of these fields has a flag in the Gadget.Flags field (GFLG_RELRIGHT, GFLG_RELBOTTOM, GFLG_RELWIDTH, and GFLG_RELHEIGHT, respectively). If that field's flag is set, Intuition expects the value in that field to be relative to either the window border or the window dimensions. For example, if GFLG_RELRIGHT is set, Intuition expects the value in the gadget's LeftEdge field to be relative to the right window border.

There is a special kind of GREL BOOPSI gadget called a custom relativity gadget. For this type of gadget, Intuition expects the values in the LeftEdge, TopEdge, Width, and Height fields to be absolute measurements, just like the values Intuition expects in these fields for non-GREL gadgets.

Setting the GFLG_RELSPECIAL bit in the Gadget.Flags field marks the gadget as a custom relativity gadget. The best way to set this bit is by setting the GA_RelSpecial attribute to TRUE when creating the gadget.

GM_RENDER

Every time Intuition feels it is necessary to redraw a BOOPSI gadget, it sends a gadget a GM_RENDER message. The GM_RENDER message (defined in <intuition/gadgetclass.h>) tells a gadget to render itself:

struct gpRender
{
    uint32             MethodID;   /* GM_RENDER */
    struct GadgetInfo *gpr_GInfo;
    struct RastPort   *gpr_RPort;  /* all ready for use */
    int32              gpr_Redraw; /* might be a "highlight pass" */
};

Some events that cause Intuition to send a GM_RENDER are: an application passed the gadget to OpenWindow(), the user moved or resized a gadget's window, or an application explicitly asked Intuition to refresh some gadgets.

The GM_RENDER message contains a pointer to the gadget's RastPort so the GM_RENDER method does not have to extract it from the gpr_GInfo GadgetInfo structure using ObtainGIRPort()). The gadget renders itself according to how much imagery it needs to replace. The gpr_Redraw field contains one of three values:

GREDRAW_REDRAW Redraw the entire gadget.
GREDRAW_UPDATE The user has manipulated the gadget, causing a change to its imagery. Update only that part of the gadget's imagery that is effected by the user manipulating the gadget (for example, the knob and scrolling field of the prop gadget).
GREDRAW_TOGGLE If this gadget supports it, toggle to or from the highlighting imagery.

Intuition is not the only entity that calls this method. The gadget's other methods may call this method to render the gadget when it goes through state changes. For example, as a prop gadget is following the mouse from the gadget's GM_HANDLEINPUT method, the gadget could send itself GM_RENDER messages, telling itself to update its imagery according to where the mouse has moved.

The DoRender() method should always be used by class implementers to invoke a GM_RENDER method on a gadget object. Applications use the RefreshGList() function which will invoke GM_RENDER for the caller in the proper context.

The Active Gadget

While a gadget is active, Intuition sends it a GM_HANDLEINPUT message for every timer pulse, mouse move, mouse click, and key press that takes place. A timer event pulse arrives about every tenth of a second. Mouse move events can arrive at a much higher rate than the timer pulses. Without even considering the keyboard, a gadget can get a lot of GM_HANDLEINPUT messages in a short amount of time. Because the active gadget has to handle a large volume of GM_HANDLEINPUT messages, the overhead of this method should be kept to a minimum.

Because the gadget will always receive a GM_GOACTIVE message before it is active and a GM_GOINACTIVE message after it is no longer active, the gadget can use these methods to allocate, initialize, and deallocate temporary resources it needs for the GM_HANDLEINPUT method. This can significantly reduce the overhead of GM_HANDLEINPUT because it eliminates the need to allocate, initialize, and deallocate resources for every GM_HANDLEINPUT message.

Note that the RastPort from ObtainGIRPort() is not cachable using this method. If the GM_HANDLEINPUT method needs to use a RastPort, it has to obtain and release the RastPort for every GM_HANDLEINPUT message using ObtainGIRPort() and ReleaseGIRPort().

RKMButtonclass.c

The following example is a sample BOOPSI gadget, RKMButClass.c. While the user has the RKMButton selected, the gadget sends an OM_UPDATE message to its ICA_TARGET for every timer event the button sees. The gadget sends notification about its RKMBUT_Pulse attribute, which is the horizontal distance in screen pixels the mouse is from the center of the button. The gadget takes care of rendering all of its imagery (as opposed to using a BOOPSI image to do it). The gadget's imagery is scalable to any dimensions and can be set (using SetGadgetAttrs()) while the gadget is in place.

One possible use for such a gadget is as buttons for a prop gadget. If the user has the prop gadget's RKMButton selected, while the mouse is to the left of the button's center, the knob on the prop gadget moves left. While the mouse is to the right of the button's center, the knob on the prop gadget moves right. The speed at which the knob moves is proportional to the horizontal distance from the mouse to the active RKMButton.

/* RKMButClass.c - Example BOOPSI gadget */
 
#include <exec/types.h>
#include <intuition/intuition.h>
#include <intuition/classes.h>
#include <intuition/classusr.h>
#include <intuition/imageclass.h>
#include <intuition/gadgetclass.h>
#include <intuition/cghooks.h>
#include <intuition/icclass.h>
#include <utility/tagitem.h>
#include <utility/hooks.h>
 
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/utility.h>
 
#include <graphics/gfxmacros.h>
 
CONST_STRPTR vers = "$VER: TestBut 50.1";
 
/***********************************************************/
/****************      Class specifics      ****************/
/***********************************************************/
#define RKMBUT_Pulse   (TAG_USER + 1)
 
struct ButINST
{
    LONG midX, midY; /* Coordinates of middle of gadget */
};
 
/* ButINST has one flag:  */
#define ERASE_ONLY   0x00000001 /* Tells rendering routine to */
                                /* only erase the gadget, not */
                                /* rerender a new one.  This  */
                                /* lets the gadget erase it-  */
                                /* self before it rescales.   */
 
/* The functions in this module */
Class *initRKMButGadClass(void);
BOOL   freeRKMButGadClass(Class *);
ULONG  dispatchRKMButGad(Class *, Object *, Msg);
void   NotifyPulse(Class *, Object *, ULONG, LONG, struct gpInput *);
ULONG  RenderRKMBut(Class *, struct Gadget *, struct gpRender *);
void   MainLoop(ULONG, ULONG);
 
/*************************************************************************************************/
/* The main() function connects an RKMButClass object to a BOOPSI integer gadget, which displays */
/* the RKMButClass gadget's RKMBUT_Pulse value.  The code scales and move the gadget while it is */
/* in place.                                                                                     */
/*************************************************************************************************/
 
struct TagItem pulse2int[] =
{
    {RKMBUT_Pulse, STRINGA_LongVal},
    {TAG_END,}
};
 
#define INTWIDTH  40
#define INTHEIGHT 20
 
struct IntuitionIFace *IIntuition;
struct GraphicsIFace *IGraphics;
struct Window *w;
Class *rkmbutcl;
struct Gadget *integer, *but;
struct IntuiMessage *msg;
 
int main(void)
{
    struct Library *IntuitionBase = IExec->OpenLibrary("intuition.library", 50);
    struct Library *GfxBase = IExec->OpenLibrary("graphics.library", 50);
 
    IIntuition = (struct IntuitionIFace*)IExec->GetInterface(IntuitionBase, "main", 1, NULL);
    IGraphics = (struct GraphicsIFace*)IExec->GetInterface(GfxBase, "main", 1, NULL);
 
    if (IIntuition != NULL && IGraphics != NULL)
    {
         if (w = IIntuition->OpenWindowTags(NULL,
             WA_Flags,       WFLG_DEPTHGADGET | WFLG_DRAGBAR |
                                 WFLG_CLOSEGADGET | WFLG_SIZEGADGET,
             WA_IDCMP,       IDCMP_CLOSEWINDOW,
             WA_Width,           640,
             WA_Height,          200,
             TAG_END))
         {
             IIntuition->WindowLimits(w, 450, 200, 640, 200);
 
             if (rkmbutcl = initRKMButGadClass())
             {
                 if (integer = (struct Gadget *)IIntuition->NewObject(NULL,
                                "strgclass",
                                GA_ID,            1L,
                                GA_Top,           (w->BorderTop) + 5L,
                                GA_Left,          (w->BorderLeft) + 5L,
                                GA_Width,         INTWIDTH,
                                GA_Height,        INTHEIGHT,
                                STRINGA_LongVal,  0L,
                                STRINGA_MaxChars, 5L,
                                TAG_END))
                 {
                     if (but = (struct Gadget *)IIntuition->NewObject(rkmbutcl,
                                NULL,
                                GA_ID,       2L,
                                GA_Top,      (w->BorderTop) + 5L,
                                GA_Left,     integer->LeftEdge +
                                                 integer->Width + 5L,
                                GA_Width,    40L,
                                GA_Height,   INTHEIGHT,
                                GA_Previous, integer,
                                ICA_MAP,     pulse2int,
                                ICA_TARGET,  integer,
                                TAG_END))
                     {
                         IIntuition->AddGList(w, integer, -1, -1, NULL);
                         IIntuition->RefreshGList(integer, w, NULL, -1);
 
                         IIntuition->SetWindowTitles(w,
                             "<-- Click to resize gadget Height",
                             NULL);
                         MainLoop(TAG_END, 0L);
 
                         IIntuition->SetWindowTitles(w,
                             "<-- Click to resize gadget Width",
                             NULL);
                         MainLoop(GA_Height, 100L);
 
                         IIntuition->SetWindowTitles(w,
                             "<-- Click to resize gadget Y position",
                             NULL);
                         MainLoop(GA_Width, 100L);
 
                         IIntuition->SetWindowTitles(w,
                             "<-- Click to resize gadget X position",
                             NULL);
                         MainLoop(GA_Top, but->TopEdge + 20);
 
                         IIntuition->SetWindowTitles(w,
                             "<-- Click to quit", NULL);
                         MainLoop(GA_Left, but->LeftEdge + 20);
 
                         IIntuition->RemoveGList(w, integer, -1);
                         IIntuition->DisposeObject(but);
                     }
                     IIntuition->DisposeObject(integer);
                 }
                 freeRKMButGadClass(rkmbutcl);
             }
             IIntuition->CloseWindow(w);
         }
    }
 
    IExec->DropInterface((struct Interface*)IGraphics);
    IExec->DropInterface((struct Interface*)IIntuition);
    IExec->CloseLibrary(GfxBase);
    IExec->CloseLibrary(IntuitionBase);
 
    return 0;
}
 
void MainLoop(ULONG attr, ULONG value)
{
    ULONG done = FALSE;
 
    IIntuition->SetGadgetAttrs(but, w, NULL, attr, value, TAG_END);
 
    while (done == FALSE)
    {
        IExec->WaitPort((struct MsgPort *)w->UserPort);
        while (msg = (struct IntuiMessage *)
           IExec->GetMsg((struct MsgPort *)w->UserPort))
        {
            if (msg->Class == IDCMP_CLOSEWINDOW)
            {
                done = TRUE;
            }
            IExec->ReplyMsg(msg);
        }
    }
}
 
/***********************************************************/
/**    Make the class and set up the dispatcher's hook    **/
/***********************************************************/
Class   *initRKMButGadClass(void)
{
    Class *cl = NULL;
    extern ULONG HookEntry();     /* defined in amiga.lib */
 
    if ( cl =  IIntuition->MakeClass( NULL,
                "gadgetclass", NULL,
                sizeof ( struct ButINST ),
                0 ))
    {
        /* initialize the cl_Dispatcher Hook    */
        cl->cl_Dispatcher.h_Entry = dispatchRKMButGad;
    }
    return ( cl );
}
 
/***********************************************************/
/******************     Free the class      ****************/
/***********************************************************/
BOOL freeRKMButGadClass( Class *cl )
{
    return IIntuition->FreeClass(cl);
}
 
/***********************************************************/
/**********       The RKMBut class dispatcher      *********/
/***********************************************************/
ULONG dispatchRKMButGad(Class *cl, Object *o, Msg msg)
{
    struct ButINST *inst;
    ULONG retval = FALSE;
    Object *object;
 
    switch (msg->MethodID)
    {
        case OM_NEW:       /* First, pass up to superclass */
            if (object = (Object *)IIntuition->IDoSuperMethodA(cl, o, msg))
            {
                struct Gadget *g = (struct Gadget *)object;
 
                            /* Initial local instance data */
                inst = INST_DATA(cl, object);
                inst->midX   = g->LeftEdge + ( (g->Width) / 2);
                inst->midY   = g->TopEdge + ( (g->Height) / 2);
 
                retval = (ULONG)object;
            }
            break;
        case GM_HITTEST:
                   /* Since this is a rectangular gadget this  */
                   /* method always returns GMR_GADGETHIT.     */
            retval = GMR_GADGETHIT;
            break;
        case GM_GOACTIVE:
            inst = INST_DATA(cl, o);
 
                    /* Only become active if the GM_GOACTIVE   */
                    /* was triggered by direct user input.     */
            if (((struct gpInput *)msg)->gpi_IEvent)
            {
                       /* This gadget is now active, change    */
                       /* visual state to selected and render. */
                ((struct Gadget *)o)->Flags |= GFLG_SELECTED;
                                RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);
                retval = GMR_MEACTIVE;
            }
            else            /* The GM_GOACTIVE was not         */
                            /* triggered by direct user input. */
                retval = GMR_NOREUSE;
            break;
        case GM_RENDER:
            retval = RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);
            break;
        case GM_HANDLEINPUT:   /* While it is active, this gadget sends its superclass an        */
                               /* OM_NOTIFY pulse for every IECLASS_TIMER event that goes by     */
                               /* (about one every 10th of a second).  Any object that is        */
                               /* connected to this gadget will get A LOT of OM_UPDATE messages. */
            {
                struct Gadget *g = (struct Gadget *)o;
                struct gpInput *gpi = (struct gpInput *)msg;
                struct InputEvent *ie = gpi->gpi_IEvent;
 
                inst = INST_DATA(cl, o);
 
                retval = GMR_MEACTIVE;
 
                if (ie->ie_Class == IECLASS_RAWMOUSE)
                {
                    switch (ie->ie_Code)
                    {
                        case SELECTUP: /* The user let go of the gadget so return GMR_NOREUSE    */
                                       /* to deactivate and to tell Intuition not to reuse       */
                                       /* this Input Event as we have already processed it.      */
 
                                       /*If the user let go of the gadget while the mouse was    */
                                       /*over it, mask GMR_VERIFY into the return value so       */
                                       /*Intuition will send a Release Verify (GADGETUP).        */
                            if ( ((gpi->gpi_Mouse).X < g->LeftEdge) ||
                                 ((gpi->gpi_Mouse).X > g->LeftEdge + g->Width) ||
                                 ((gpi->gpi_Mouse).Y < g->TopEdge) ||
                                 ((gpi->gpi_Mouse).Y > g->TopEdge + g->Height) )
                                retval = GMR_NOREUSE | GMR_VERIFY;
                            else
                                retval = GMR_NOREUSE;
 
                                           /* Since the gadget is going inactive, send a final   */
                                           /* notification to the ICA_TARGET.                    */
                            NotifyPulse(cl , o, 0L, inst->midX, (struct gp_Input *)msg);
                            break;
                        case MENUDOWN: /* The user hit the menu button. Go inactive and let      */
                                       /* Intuition reuse the menu button event so Intuition can */
                                       /* pop up the menu bar.                                   */
                            retval = GMR_REUSE;
 
                                           /* Since the gadget is going inactive, send a final   */
                                           /* notification to the ICA_TARGET.                    */
                            NotifyPulse(cl , o, 0L, inst->midX, (struct gp_Input *)msg);
                            break;
                        default:
                            retval = GMR_MEACTIVE;
                    }
 
                }
                else if (ie->ie_Class == IECLASS_TIMER)
                              /* If the gadget gets a timer event, it sends an interim OM_NOTIFY */
                    NotifyPulse(cl, o, OPUF_INTERIM, inst->midX, gpi); /*     to its superclass. */
            }
            break;
 
        case GM_GOINACTIVE:           /* Intuition said to go inactive.  Clear the GFLG_SELECTED */
                                      /* bit and render using unselected imagery.                */
            ((struct Gadget *)o)->Flags &= ~GFLG_SELECTED;
            RenderRKMBut(cl, (struct Gadget *)o, (struct gpRender *)msg);
            break;
        case OM_SET:/* Although this class doesn't have settable attributes, this gadget class   */
                    /* does have scaleable imagery, so it needs to find out when its size and/or */
                    /* position has changed so it can erase itself, THEN scale, and rerender.    */
            if ( IUtility->FindTagItem(GA_Width,  ((struct opSet *)msg)->ops_AttrList) ||
                 IUtility->FindTagItem(GA_Height, ((struct opSet *)msg)->ops_AttrList) ||
                 IUtility->FindTagItem(GA_Top,    ((struct opSet *)msg)->ops_AttrList) ||
                 IUtility->FindTagItem(GA_Left,   ((struct opSet *)msg)->ops_AttrList) )
            {
                struct RastPort *rp;
                struct Gadget *g = (struct Gadget *)o;
 
                WORD x,y,w,h;
 
                x = g->LeftEdge;
                y = g->TopEdge;
                w = g->Width;
                h = g->Height;
 
                inst = INST_DATA(cl, o);
 
                retval = IIntuition->IDoSuperMethodA(cl, o, msg);
 
                                                          /* Get pointer to RastPort for gadget. */
                if (rp = IIntuition->ObtainGIRPort( ((struct opSet *)msg)->ops_GInfo) )
                {
                    UWORD *pens = ((struct opSet *)msg)->ops_GInfo->gi_DrInfo->dri_Pens;
 
                    IGraphics->SetAPen(rp, pens[BACKGROUNDPEN]);
                    IGraphics->SetDrMd(rp, JAM1);                            /* Erase the old gadget.       */
                    IGraphics->RectFill(rp, x, y, x+w, y+h);
 
                    inst->midX = g->LeftEdge + ( (g->Width) / 2); /* Recalculate where the       */
                    inst->midY = g->TopEdge + ( (g->Height) / 2); /* center of the gadget is.    */
 
                                                                  /* Rerender the gadget.        */
                    IIntuition->IDoMethod(o, GM_RENDER, ((struct opSet *)msg)->ops_GInfo, rp, GREDRAW_REDRAW);
                    IIntuition->ReleaseGIRPort(rp);
                }
            }
            else
                retval = IIntuition->IDoSuperMethodA(cl, o, msg);
            break;
        default:          /* rkmmodelclass does not recognize the methodID, let the superclass's */
                          /* dispatcher take a look at it.                                       */
            retval = IIntuition->IDoSuperMethodA(cl, o, msg);
            break;
    }
    return(retval);
}
 
 
 
/*************************************************************************************************/
/************** Build an OM_NOTIFY message for RKMBUT_Pulse and send it to the superclass. *******/
/*************************************************************************************************/
void NotifyPulse(Class *cl, Object *o, ULONG flags, LONG mid, struct gpInput *gpi)
{
    struct TagItem tt[3];
 
    tt[0].ti_Tag = RKMBUT_Pulse;
    tt[0].ti_Data = mid - ((gpi->gpi_Mouse).X + ((struct Gadget *)o)->LeftEdge);
 
    tt[1].ti_Tag = GA_ID;
    tt[1].ti_Data = ((struct Gadget *)o)->GadgetID;
 
    tt[2].ti_Tag = TAG_END;
 
    IIntuition->IDoSuperMethod(cl, o, OM_NOTIFY, tt, gpi->gpi_GInfo, flags);
}
 
 
 
/*************************************************************************************************/
/*******************************   Erase and rerender the gadget.   ******************************/
/*************************************************************************************************/
ULONG RenderRKMBut(Class *cl, struct Gadget *g, struct gpRender *msg)
{
    struct ButINST *inst = INST_DATA(cl, (Object *)g);
    struct RastPort *rp;
    ULONG retval = TRUE;
    UWORD *pens = msg->gpr_GInfo->gi_DrInfo->dri_Pens;
 
    if (msg->MethodID == GM_RENDER)   /* If msg is truly a GM_RENDER message (not a gpInput that */
                                      /* looks like a gpRender), use the rastport within it...   */
        rp = msg->gpr_RPort;
    else                              /* ...Otherwise, get a rastport using ObtainGIRPort().     */
        rp = IIntuition->ObtainGIRPort(msg->gpr_GInfo);
 
    if (rp)
    {
        UWORD back, shine, shadow, w, h, x, y;
 
        if (g->Flags & GFLG_SELECTED) /* If the gadget is selected, reverse the meanings of the  */
        {                             /* pens.                                                   */
            back   = pens[FILLPEN];
            shine  = pens[SHADOWPEN];
            shadow = pens[SHINEPEN];
        }
        else
        {
            back   = pens[BACKGROUNDPEN];
            shine  = pens[SHINEPEN];
            shadow = pens[SHADOWPEN];
        }
        IGraphics->SetDrMd(rp, JAM1);
 
        IGraphics->SetAPen(rp, back);          /* Erase the old gadget.       */
        IGraphics->RectFill(rp, g->LeftEdge,
                     g->TopEdge,
                     g->LeftEdge + g->Width,
                     g->TopEdge + g->Height);
 
        IGraphics->SetAPen(rp, shadow);     /* Draw shadow edge.            */
        IGraphics->Move(rp, g->LeftEdge + 1, g->TopEdge + g->Height);
        IGraphics->Draw(rp, g->LeftEdge + g->Width, g->TopEdge + g->Height);
        IGraphics->Draw(rp, g->LeftEdge + g->Width, g->TopEdge + 1);
 
        w = g->Width / 4;       /* Draw Arrows - Sorry, no frills imagery */
        h = g->Height / 2;
        x = g->LeftEdge + (w/2);
        y = g->TopEdge + (h/2);
 
        IGraphics->Move(rp, x, inst->midY);
        IGraphics->Draw(rp, x + w, y);
        IGraphics->Draw(rp, x + w, y + (g->Height) - h);
        IGraphics->Draw(rp, x, inst->midY);
 
        x = g->LeftEdge + (w/2) + g->Width / 2;
 
        IGraphics->Move(rp, x + w, inst->midY);
        IGraphics->Draw(rp, x, y);
        IGraphics->Draw(rp, x, y  + (g->Height) - h);
        IGraphics->Draw(rp, x + w, inst->midY);
 
        IGraphics->SetAPen(rp, shine);    /* Draw shine edge.           */
        IGraphics->Move(rp, g->LeftEdge, g->TopEdge + g->Height - 1);
        IGraphics->Draw(rp, g->LeftEdge, g->TopEdge);
        IGraphics->Draw(rp, g->LeftEdge + g->Width - 1, g->TopEdge);
 
        if (msg->MethodID != GM_RENDER) /* If we allocated a rastport, give it back.             */
            IIntuition->ReleaseGIRPort(rp);
    }
    else retval = FALSE;
    return(retval);
}