Life Cycle of a Widget

This chapter covers the following topics:

This chapter examines the life cycle of a basic widget using the ShadowedBox widget as an example. We'll show you how to create, use, and destroy this widget. We'll also outline the life cycle differences between basic, container, and compound widgets.

All widgets

If a widget is already realized or in the process of being realized, it's automatically anchored by the widget library.


Anchoring


Default anchoring for a container widget.

PtAnchorWidget() and PtApplyAnchors() in the widget-building library allow anchoring to be applied at any time, provided the parent widget's extent is valid.

Basic widgets

If we were building an application and wanted to use the custom ShadowedBox widget (a “basic” widget), we would simply create it by calling PtCreateWidget():

PtWidget_t  *box;
PhArea_t    area = { 10, 10, 100, 100 };
PtArg_t     args[2];

PtSetArg( &args[0], Pt_ARG_AREA, &area, 0 );
PtSetArg( &args[1], SBW_SHADOW_COLOR, Pg_BLUE, 0 );
box = PtCreateWidget( ShadowedBox, Pt_DEFAULT_PARENT,
                      2, args );
PtRealizeWidget( box );

What happens when we call PtCreateWidget()? What does the widget library do? Let's follow through the widget creation process and examine the various parts of the widget code as they get executed.

Instantiating the widget

The first step, instantiating the widget, creates an instance of the widget class. The process of instantiating a widget is as follows:

  1. The widget class is created and initialized.
  2. All members of the widget instance structure are initialized to default values.
  3. The resources specified through PtCreateWidget() are applied.

Creating and initializing the widget class

When a widget is created via a call to PtCreateWidget(), the specified widget class (i.e. ShadowedBox) is first checked to see if it has been initialized. Since this is the first time this widget has been created, the widget's initialization function is called to create the widget class.

In the ShadowedBox.c source file, the widget class is defined as a class reference structure:

PtWidgetClassRef_t WShadowedBox = { NULL,
                                    CreateShadowedBoxClass };
PtWidgetClassRef_t *ShadowedBox = &WShadowedBox;

The first member is the widget class pointer, which is initialized to NULL. The second member is the widget class-creation function. If the value of the class pointer is NULL, the class-creation function is called and it sets the widget class pointer in the structure.

Let's look at CreateShadowedBoxClass() from our example to see how it defines the ShadowedBox class:

//
// ShadowedBox class creation function
//
PtWidgetClass_t *CreateShadowedBoxClass( void )
{
    // define our resources
    static PtResourceRec_t resources[] = {
        SBW_SHADOW_COLOR, Pt_CHANGE_REDRAW, 0,
            Pt_ARG_IS_NUMBER( ShadowedBoxWidget, shadow_color ), 0,
        SBW_SHADOW_OFFSET, Pt_CHANGE_RESIZE_REDRAW, 0,
            Pt_ARG_IS_NUMBER( ShadowedBoxWidget, shadow_offset ), 0,
        };

    // set up our class member values
    static PtArg_t args[] = {
        { Pt_SET_VERSION, 110},
        { Pt_SET_STATE_LEN, sizeof( ShadowedBoxWidget ) },
        { Pt_SET_DFLTS_F, (long)shadowedbox_dflts },
        { Pt_SET_DRAW_F, (long)shadowedbox_draw },
        { Pt_SET_FLAGS, 0, Pt_RECTANGULAR },
        { Pt_SET_NUM_RESOURCES,
          sizeof( resources ) / sizeof( resources[0] ) },
        { Pt_SET_RESOURCES, (long)resources,
          sizeof( resources ) / sizeof( resources[0] ) },
        };

    // create the widget class
    return( ShadowedBox->wclass = PtCreateWidgetClass(
        PtBasic, 0, sizeof( args )/sizeof( args[0] ), args ) );
}

The class definition includes:

Resources array
The static array resources defines the class resources. The class resources are accessed through a table of resources unique to the ShadowedBox class.
Widget class array
The static array args defines the ShadowedBox class. It defines ShadowedBox's class methods and its table of resources.

The args array is passed to PtCreateWidgetClass() as follows:

  return( ShadowedBox->wclass = PtCreateWidgetClass(
     PtBasic, 0, sizeof( args )/sizeof( args[0] ), args )
     );

In PtCreateWidgetClass(), space is allocated for the ShadowedBox class and its superclass's structure (i.e. PtBasic) is copied in. Then the args array is used to set up the custom class and its table of unique resources.

Setting default values

After the ShadowedBox class is initialized, space is allocated for its instance structure. The Defaults methods of all ancestors of the ShadowedBox class are called next, ending with ShadowedBox's Defaults method (e.g. shadowedbox_dflts()):

//
// ShadowedBox defaults function
//
static void shadowedbox_dflts( PtWidget_t *widget )
{
    ShadowedBoxWidget    *sb = ( ShadowedBoxWidget * ) widget;
    PtBasicWidget_t      *basic = ( PtBasicWidget_t * ) widget;

    basic->fill_color    = Pg_WHITE;
    sb->shadow_color     = Pg_BLACK;
    sb->shadow_offset    = 4;
}

To access the private members of the instance structure, this Defaults method casts the widget pointer to a ShadowedBoxWidget pointer and a PtBasicWidget_t pointer. It then defaults the fill_color member to Pg_WHITE, the shadow_color member to Pg_BLACK and the shadow_offset member to 4.

Setting application resources

After the Defaults method is called, the array of resources provided to PtCreateWidget() is applied. In the sample source code, these two resources are passed in args:

PtSetArg( &args[0], Pt_ARG_AREA, &area, 0 );
PtSetArg( &args[1], SBW_SHADOW_COLOR, Pg_BLUE, 0 );

The first resource manifest, Pt_ARG_AREA, sets the widget area (position and size). Because this resource isn't defined by the ShadowedBox class, it's handled by the first superclass that defines Pt_ARG_AREA (i.e. PtWidget).

The second resource manifest SBW_SHADOW_COLOR is handled by the ShadowedBox class. Because ShadowedBox doesn't define a method for setting the resource value, the Photon widget library takes care of assigning a new value to this instance structure member (i.e. the value is changed from the default, Pg_BLACK, to Pg_BLUE).

Finally, PtCreateWidget() returns the widget pointer and the application receives access to a newly created instance of the ShadowedBox class:

box = PtCreateWidget( ShadowedBox, Pt_DEFAULT_PARENT,
                      2, args );

Now box points to an instance of the ShadowedBox class, but the widget itself won't be displayed until it's realized.

Realizing a widget instance

There are a number of class methods used in the process of realizing a widget. These methods are executed in a specific order that we'll call “steps”:

Step What it does
Initialization method Initializes the widget instance.
Extent method Calculates the actual widget extent based on the area specified.
Connection method Creates any optional Photon regions.
Realization method Performs any operations required immediately prior to drawing.
Draw method Draws the widget.

The ShadowedBox example doesn't define an Initialization method, an Extent method, or a Connection method. These methods are handled by PtWidget or PtBasic.

Processing methods

Before we get into a detailed discussion of the individual methods used in a widget class, we need to look at their usage in general. Class methods can be chained up or down or inherited.

When a method is chained, the method of each class in the widget hierarchy is executed. Execution can start at the lowest class (e.g. ShadowedBox) in the hierarchy and go up, or at the highest class (i.e. PtWidget) and go down.


Class methods and chaining


Methods being chained up.

Some chained methods can be stopped. This means any class in the hierarchy can stop the chaining process to prevent the superclass or subclassed methods from being executed. When chaining is stopped, the class stopping the chain is responsible for supplying the equivalent functionality otherwise provided by another class.

Inherited methods work differently: one method only is called, as determined by the lowest class in the hierarchy to define it (e.g. our ShadowedBox class doesn't define an Extent method, so the Extent method is inherited from the PtBasic superclass).


Class methods and chaining


Inherited methods.

This means PtBasic's Extent method is used to determine the size of the ShadowedBox widget. Inherited methods make building a widget class easier because only the methods unique to a class need to be defined.

Let's look at the class methods in more detail. The following table shows whether the methods of a basic widget are chained or inherited:

Method Processing Processing note
Defaults Chained down Can't be stopped
Initialization Chained up Return Pt_END to stop
Extent Inherited
Connection Chained up Return Pt_END to stop
Realization Inherited
Draw Inherited
Unrealization Chained up Can't be stopped
Destruction Chained up Can't be stopped
Set Resources Inherited
Get Resources Inherited

PtBasic extends these methods with three other methods. These methods are available only to subclasses of PtBasic:

Method Processing
Got Focus Inherited
Lost Focus Inherited
Calc Opaque Rect Inherited

Initialization method

This is the first class method called during the realization process. This method is chained up; the Initialization method of the ShadowedBox class would be called first, followed by the Initialization method of the PtBasic class, ending with PtWidget's Initialization method.

Extent method

This inherited method is used to determine the exact size of the widget based on default values and/or the widget's position, size, margins, borders, and highlighting information, as set by the program. An Extent method isn't defined for ShadowedBox, so the Extent method is inherited from the PtBasic superclass.

Connection method

The Connection method is chained up. It's used primarily for creating any Photon regions needed by a widget. Due to its simplicity, the ShadowedBox widget doesn't require any regions — a superclass method will create regions for ShadowedBox only if needed (e.g. a custom cursor is defined).

Realization method

The Realization method is used to adjust any region attributes prior to the actual drawing of a widget. Because ShadowedBox doesn't define a Realization method, it's inherited from a superclass.

Draw method

This is the last class method called during the realization process. It's used to render a widget on the screen. This method can be inherited, but since ShadowedBox defines its own Draw method (shown below), it's executed instead of a superclass's Draw method:

//
// ShadowedBox draw function
//
static void shadowedbox_draw( PtWidget_t *widget, PhTile_t *damage )
{
    ShadowedBoxWidget    *sb = ( ShadowedBoxWidget * ) widget;
    PtBasicWidget_t      *basic = ( PtBasicWidget_t * ) widget;
    PhRect_t             shadow_rect, rect;
    PgColor_t            color;

    // We want to use basic's draw function to get borders
    // and default focus rendering... but we don't want it to
    // fill the background with the basic fill color,
    // so we set the fill color to transparent for Basic's draw func.
    color = basic->fill_color;
    basic->fill_color = Pg_TRANSPARENT;
    PtSuperClassDraw( PtBasic, widget, damage );

    // we don't want to draw outside our canvas! So we clip.
    PtCalcCanvas( widget, &rect );
    PtClipAdd( widget, &rect );

    basic->fill_color = color;
    shadow_rect = rect;
    shadow_rect.ul.x += sb->shadow_offset;
    shadow_rect.ul.y += sb->shadow_offset;
    PgSetStrokeColor( sb->shadow_color );
    PgSetFillColor( sb->shadow_color );
    PgDrawRect( &shadow_rect, Pg_DRAW_FILL_STROKE );

    PgSetFillTransPat( basic->trans_pattern );
    PgSetStrokeColor( basic->color );
    PgSetFillColor( color );
    rect.lr.x -= sb->shadow_offset;
    rect.lr.y -= sb->shadow_offset;
    PgDrawRect( &rect, Pg_DRAW_FILL_STROKE );

    /* remove the clipping */
    PtClipRemove();
}

Let's look at the code in more detail. From the function declaration:

static void shadowedbox_draw( PtWidget_t *widget, PhTile_t *damage )

The Draw method is passed two arguments. The widget argument is the widget's pointer and the damage argument is a list of damage rectangles to be applied to the widget when it's drawn.


Note: The program can use the damage list to make intelligent decisions about what to draw. However, drawing operations for most widgets are minimal and any extra processing might not be worthwhile.

Notice the widget pointer is of type PtWidget_t, which means the pointer must be cast to a ShadowedBoxWidget pointer to allow access to the ShadowedBox widget's internal structure members. Since PtBasic is a superclass of ShadowedBox, we can cast the widget pointer to a PtBasicWidget_t structure too; this allows access to the basic widget members without resorting to sb->basic.

 ShadowedBoxWidget *sb = ( ShadowedBoxWidget * ) widget;
 PtBasicWidget_t   *basic = ( PtBasicWidget_t * ) widget;

Since the Draw method isn't chained, all drawing of the ShadowedBox widget, including the borders and highlighting common to all Photon-style widgets, must be done within the Draw method of the ShadowedBox class. For convenience, the Photon widget building library allows you to call the draw method of a superclass using PtSuperClassDraw() as shown below:

    // We want to use basic's draw function to get borders
    // and default focus rendering... but we don't want it to
    // fill the background with the basic fill color,
    // so we set the fill color to transparent for Basic's draw func.
    color = basic->fill_color;
    basic->fill_color = Pg_TRANSPARENT;
    PtSuperClassDraw( PtBasic, widget, damage );

After this, we add clipping so we won't draw beyond the widget's canvas:

    // we don't want to draw outside our canvas! So we clip.
    PtCalcCanvas( widget, &rect );
    PtClipAdd( widget, &rect );

The rest of the code in the Draw method determines the widget's position and size based on its area and margins, and then draws a shadowed box using low-level Photon Pg*() graphics functions. Before leaving the Draw method, the clipping we added is removed by calling PtClipRemove().

When the draw buffer is flushed later on, the widget appears on the screen.

After realizing a widget

After a widget is realized, it must be ready to:

Fortunately, both of these operations are handled automatically because of the way widget classes are defined — the resource list specifies what should be done if the value of a resource changes:

static PtResourceRec_t resources[] = {
    SBW_SHADOW_COLOR, Pt_CHANGE_REDRAW, 0,
        Pt_ARG_IS_NUMBER( ShadowedBoxWidget, shadow_color ), 0,
    SBW_SHADOW_OFFSET, Pt_CHANGE_RESIZE_REDRAW, 0,
        Pt_ARG_IS_NUMBER( ShadowedBoxWidget, shadow_offset ), 0,
    };

In the example source code for ShadowedBox, both resources are defined with a Pt*REDRAW manifest so they're automatically marked as damaged. This causes a redraw if the value changes.

When a widget is damaged for any reason, the Photon library automatically invokes the widget's Draw method to repair the damage. If any of the inherited resources are changed, they're handled according to the resource declaration of the first superclass to define that resource.

Destroying a widget

The widget must handle its own destruction. This process involves:

Unrealization method

The purpose of this method is to undo everything of consequence done during realization. The Unrealization method is responsible for:

This method is chained up. Since the ShadowedBox class doesn't define an Unrealization method, PtBasic's Unrealization method will be called, followed by PtWidget's Unrealization method.

Destruction method

This method is responsible for releasing any system resources used by a widget but not referenced by a widget resource. Like the Unrealization method, it's chained up. This lets all classes in the hierarchy release system resources in turn.

Other methods

The remaining class methods aren't described in this section, but you should now have a general understanding of the overall process. The Anatomy of a Widget chapter describes these methods in detail. Let's now look at some of the additional requirements for container and compound classes.

Container widgets

The life cycle of a container widget is similar to that of a basic widget. The main difference is that container widgets must support widget children, including the following features:

The Extent method of a container class is very important — it must enforce its resize policy based on its children. This means a container widget may need to determine its size based on the positions and sizes of its children. For this reason, all child widgets are extented before the container is extented.

Child constraints

The container class extends the PtBasic class definition with a number of child-constraint methods. These methods let a container adjust itself automatically according to any changes made to its children. These child-constraint methods include:

A container dynamically identifies which child constraints need to be handled by setting and clearing bits in the flags member of its instance of the class structure. For example:

     cntr->flags |= Pt_CHILD_CREATED;

Any child-constraint method a container will support must have its bit set in cntr->flags or the method won't be invoked.

Child redirection

A custom container class can optionally provide a child-redirector function to restrict certain widget classes from being added to the container. This function allows the container to redirect parentage to other widgets inside or outside the container itself. The redirector function is usually defined in the class-creation function. For example:

{ Pt_SET_CHILD_REDIRECT_F, (long)PtSampContainerRedirect },

The PtMenuBar widget uses this feature to prevent certain types of widgets from being added to a menu bar. For more information, see Container widget anatomy in the Anatomy of a Widget chapter.

Compound widgets

The compound class is used to design widgets made from other widgets. The compound class is a subclass of the container class, so it inherits the functionality described above for containers. In addition, the compound class adds a mechanism for exporting subordinate widgets. This is achieved by extending the container class definition to include:

The exporting mechanism lets users set and get the resources of subordinate widgets by treating the compound widget as if it actually were the subordinate widget.

A good example is the PtComboBox widget — it's built from a text widget and a list widget. It also defines its own behavior by supplying a pull-down button beside the text widget for accessing the list widget.

You can change the subordinate widgets inside a compound widget using the resources defined for the subordinate widgets. For example, you could change the PtText widget inside a PtComboBox widget by using the widget pointer of the PtCompound widget as follows:

PtArg_t args[1];

PtSetArg( &args[0], Pt_ARG_TEXT_STRING,
          "Subordinate Text", 0 );
PtSetResources( mycombobox_widget_pointer, 1, args );

This is a very powerful feature that greatly simplifies the construction of compound widgets since resources don't have to be duplicated.

However, changing the resources of subordinate widgets could destroy the look and feel of a compound widget. For this reason, we recommend that you consider using the blocking mechanism in a custom compound class. The blocking mechanism lets the compound class discard attempts to set specific resources on its subordinate widgets. The compound class can override any resource defined by its superclasses or by its subordinate widgets.

Exporting subordinate widgets

A compound widget identifies the widgets it intends to export in the class-creation function. This is done by setting values for the number of subordinates and the list of subordinates.

Each widget specified in the list of subordinates must be created in the Defaults method of the compound class.

For more information, see Compound widget anatomy in the Anatomy of a Widget chapter.