Chapter 2. A Motif Widget Writing Tutorial

This chapter walks you through the steps in creating a Motif widget. It focuses on a demonstration widget named ExmSimple. While not an officially released widget, the ExmSimple widget does follow the same guidelines used to write standard Motif widgets. Furthermore, this example widget provides an excellent opportunity for you to study the inner workings of widgets. You can find the source code ExmSimple and the other sample C widgets in the demos/widgets/Exm/lib directory.

In this chapter, we focus more on the big picture of writing widgets than on the fine details of perfecting them. (Later chapters provide these details.) For now, this chapter focuses on producing a working widget, albeit a rather simple one.

This chapter also includes general information on writing multithread-safe widgets.

Namespace

You will undoubtedly create many names while coding a new widget. For example, you will need to create names for new resources, new macros, new variables, new methods, and the new widgets themselves. We recommend that all these names begin with the same prefix.

The chosen prefix should be a short name, generally only two or three characters long. Furthermore, the chosen prefix should help identify the organization or the product from which the widgets came. For instance, all the example widgets accompanying this guide are tagged with the prefix Exm (Example Motif).

You should avoid the following prefixes:

  • X

  • Xt

  • _Xt

  • xm

  • Xm

  • Xme

  • XmQT

  • _Xm

  • Exm

Xlib reserves the X prefix. The X Toolkit reserves the Xt and _Xt prefixes. Motif reserves the other six prefixes.

Recommended Files for Each Widget

We recommend that you implement each Motif widget in three files: a public header file, a private header file, and a source code file. If you call the new widget ExmMyName, Motif recommends that you call the public header file MyName.h, the private header file MyNameP.h, and the source code file MyName.c.

Some operating systems prohibit long filenames. If you are concerned with portability, abbreviate the filenames as described by the Intrinsics documentation.

The Widget Public Header File

The purpose of the widget public header file is to define the Application Programming Interface (API) for the widget. That is, the widget public header file defines the mechanism to instantiate a widget and the methods to access or modify the widget's public data.

All Intrinsics-based widgets provide a widget public header file. Motif adds few recommendations for widget public header files beyond those imposed by the Intrinsics. The purpose of this section is to review the important features of Intrinsics-based public header files and fill in the details relevant to Motif. Follow these steps to create a widget public header file:

  1. Ensure that the file is included only once.

  2. Include the appropriate header files.

  3. Allow for use by a C++ application. (This is an optional step, but is recommended.)

  4. Specify the names for the widget class and instance types.

  5. Define the string equivalents of any new resource names used by this widget.

  6. Define the application programmer's interface to the widget, including any possible convenience functions for this widget.

The following subsections detail these steps by examining the public header file for the ExmSimple widget. This file is stored online in the demos/widgets/Exm/lib directory at pathname Simple.h.

Step 1: Ensure That the File Is Included Only Once

You must encase the contents of the header file inside a conditional compilation directive like the following:

#ifndef _ExmSimple_h
#define _ExmSimple_h
  ...
#endif /* _ExmSimple_h */

These lines prevent compiler errors by ensuring that the code inside the file will be included only once per compilation.

Step 2: Include the Appropriate Header Files

You must include the public header file for your widget's superclass. The superclass of ExmSimple widget is XmPrimitive. The public header file of the XmPrimitive widget is Xm/Primitive.h. Therefore, Simple.h includes the following declaration:

#include <Xm/Primitive.h>

Now consider the ExmString demonstration widget. The superclass of theExmString widget is ExmSimple widget. Therefore, the ExmString public header file must include the following declaration:

#include <Exm/Simple.h>

Step 3: Allow for C++ Compilation

You should encase the remainder of the public header file inside the following pair of conditional compilation directives:

#ifdef __cplusplus
extern "C" {
#endif
 ...

#ifdef __cplusplus
}  /* Close scope of 'extern "C"' declaration which encloses file. */
#endif

The preceding code prevents link-time errors when C++ applications use this widget.

When thinking up variable names, try to avoid names that are C++ keywords, such as class and

new. Using such keywords as variable names could prevent C++ compilation.

Step 4: Specify Widget Class Names

You must create externally accessible names for the widget and widget class.

For example, the following provides the definition of widget and widget class for the ExmSimple widget:

externalref WidgetClass exmSimpleWidgetClass;
typedef struct _ExmSimpleClassRec *ExmSimpleWidgetClass;
typedef struct _ExmSimpleRec      *ExmSimpleWidget;

The externalref macro encapsulates all system dependencies regarding external data references. Therefore, for portability, you should use the externalref macro instead of the extern keyword in order to make variables externally accessible. You should use extern to make functions externally accessible.

Step 5: Define String Equivalents of New Resource Names

You must define string equivalents for every new resource name created by your widget. However, if your widget is using a resource name already used in the Motif toolkit, you should not define a string equivalent for it. You define the string equivalents with the #define preprocessor directive.

For example, the ExmSimple widget defines a new resource named ExmNsimpleShape. Since this resource name is not defined in the standard Motif widget set, the widget public header file defines the following two string equivalents:

#define ExmNsimpleShape "simpleShape"
#define ExmCSimpleShape "SimpleShape"

The preceding definitions associate a literal string with the new resource name.

In addition to ExmNsimpleShape, the ExmSimple widget specifies two other resources: XmNmarginHeight and XmNmarginWidth. However, the widget public header file does not need to define string equivalents for these resources because these resource names are already used in the standard Motif widget set (for example, by XmLabel ). If you want to determine whether a particular resource name has already been defined by Motif, look in the Xm/XmStrDefs.h file.

You must also define a string equivalent for each new representation type used by your widget. (See Chapter 6 for details on representation types.) For example, the ExmSimple widget creates a representation type named ExmRSimpleShape. Therefore, the widget public header file for ExmSimple defines this string equivalent as follows:

#define ExmRSimpleShape "ExmSimpleShape"

In addition, since the ExmRSimpleShape representation type is an enumerated data type, the widget public header file must also specify all its enumerated constants as follows:

enum { ExmSHAPE_OVAL=0, ExmSHAPE_RECTANGLE=1 };

You should use Motif's representation type facility to register new enumerated types. (See Chapter 6 for details.)

Step 6: Specify the API for This Widget

Your widget public header file should define the API for the widget. For example, the following code establishes the API for the ExmSimple widget:

extern Widget ExmCreateSimple(
                              Widget    parent,
                              String    name,
                              Arg      *arglist,
                              Cardinal  argCount
                             );

If your widget contains additional convenience functions, then this is the place to declare them.

The Widget Private Header File

The widget private header file serves the same purpose in Motif that it does for all Intrinsics-based widgets. Follow these steps to create a private header file that is compatible with other Motif widgets:

  1. Ensure that the file is included only once.

  2. Specify the appropriate header files.

  3. Allow for use by a C++ application. (This is an optional step, but is recommended.)

  4. Define inheritance macros for methods that your widget wishes to export to subclasses. Define new data types to support these macros.

  5. Declare the widget class part.

  6. Declare the full widget class record.

  7. Declare the widget instance part.

  8. Declare the full widget instance record.

  9. Declare constraint structures if you are writing a manager widget.

  10. Define the API of any private functions used by other classes.

The following subsections detail these steps by examining the private header file for the ExmSimple widget. This file is stored online in the demos/widgets/Exm/lib directory at pathname SimpleP.h.

Step 1: Ensure That the File is Included Only Once

You must encase the contents of the file inside a conditional compilation directive, as follows:

#ifndef _ExmSimpleP_h
#define _ExmSimpleP_h
 ...
#endif /* _ExmSimpleP_h */

These lines prevent compiler errors by ensuring that the code inside the file will only be included once per compilation.

Step 2: Include the Appropriate Header Files

You must include the following two header files:

  • The widget's public header file

  • The private header file of your widget's immediate superclass

For example, the ExmSimple widget derives from the XmPrimitive class. Therefore, the widget private header file of ExmSimple includes the following two files:

#include <ExmSimple.h>
#include <Xm/PrimitiveP.h>

Step 3: Allow for C++ Compilation

You should encase the remaining code of the file inside the following pair of conditional compilation directives:

#ifdef __cplusplus
extern "C" {
#endif
 ...

#ifdef __cplusplus
}  /* Close scope of 'extern "C"' declaration which encloses file. */
#endif

If the application program using your widget is written in C++, then allowing for C++ compilation is mandatory. Otherwise, allowing for C++ compilation is merely a good idea.

Step 4: Define Inheritable Methods

You must define inheritance class method macros. Once defined, your widget's subclasses can inherit your widget's methods simply by specifying the inheritance macro in the appropriate field of the class record. For example, the ExmSimple widget creates the following eight inheritance macros:

#define ExmInheritDrawVisual     ((XtWidgetProc) _XtInherit)
#define ExmInheritDrawShadow     ((XtWidgetProc) _XtInherit)
#define ExmInheritCreateGC       ((XtWidgetProc) _XtInherit)
#define ExmInheritDestroyGC      ((XtWidgetProc) _XtInherit)
#define ExmInheritSelectGC       ((ExmSelectGCProc) _XtInherit)
#define ExmInheritCalcVisualSize ((XtWidgetProc) _XtInherit)
#define ExmInheritCalcWidgetSize ((XtWidgetProc) _XtInherit)
#define ExmInheritReconfigure    ((ExmReconfigureProc) _XtInherit)

If an inheritance macro requires a new data type definition, you must provide it here. For example, ExmInheritSelectGC and ExmInheritReconfigure both require new data type definitions, as follows:

typedef GC   (*ExmSelectGCProc)(
                        Widget);
typedef void (*ExmReconfigureProc)(
                        WidgetClass,
                        Widget,
                        Widget) ;

The inheritance macros should follow Motif naming conventions. The inheritance macro names should begin with the widget set prefix (in this case, Exm) followed by the word Inherit. Similarly, the new data type definitions should begin with the widget set prefix and end with Proc.

Step 5: Define the Widget Class Part

The widget class part defines the inheritable methods and data members of the widget. For example, following is the widget class part of the ExmSimple widget:

typedef struct _ExmSimpleClassPart
{
        XtWidgetProc                   draw_visual;
           XtWidgetProc                draw_shadow;
           XtWidgetProc                create_gc;
           XtWidgetProc                destroy_gc;
           ExmSelectGCProc                  select_gc;
        XtWidgetProc                   calc_visual_size;
           XtWidgetProc                calc_widget_size;
   ExmReconfigureProc      reconfigure;
        XtPointer                         extension;
} ExmSimpleClassPart;

Motif strongly recommends specifying an extension field as the last field of every widget class part. Providing an extension field helps implement binary compatibility if you add methods to the widget in future releases. The extension field should have the XtPointer data type.

Step 6: Declare the Full Class Record

You must define the widget's full class record. This structure specifies the class parts of all widget classes in the current widget's hierarchy. The first field in the full class record specifies the class part of the top widget in the hierarchy (Core). The next field specifies the class part of the widget below the top widget, and so on. The final field specifies the class part of the current widget.

For example, the ExmSimple widget derives from Core and XmPrimitive. Therefore, the class record structure of ExmSimple is as follows:

typedef struct _ExmSimpleClassRec
{
        CoreClassPart           core_class;
        XmPrimitiveClassPart    primitive_class;
        ExmSimpleClassPart      simple_class;
} ExmSimpleClassRec;

There are a few conventions that you should follow in the full class record:

  • The tag name (for example, _ExmSimpleClassRec) should begin with an underscore.

  • The data type name (for example, ExmSimpleClassRec) should have the same name as the tag except for the leading underscore.

After defining the data type, you must now declare an externalref variable having this data type; for example:

externalref ExmSimpleClassRec exmSimpleClassRec;

By convention, the variable has the same name as the data type except that the first character of the variable is always lowercase.

All manager widgets derive from Core, Composite, Constraint, and XmManager. Therefore, the class record structure of the sample manager widget ExmGrid is the following:

typedef struct _ExmGridClassRec
{
    CoreClassPart       core_class;
    CompositeClassPart  composite_class;
    ConstraintClassPart constraint_class;
    XmManagerClassPart  manager_class;
    ExmGridClassPart    grid_class;
} ExmGridClassRec;
externalref ExmGridClassRec exmGridClassRec;

Step 7: Define the Widget Instance Part

Your private header file typically declares a widget instance record. The widget instance record describes the inheritable data members of your widget. The inheritable data members consist of your widget's resources plus those variables that subclasses may need access to. For example, following is the widget instance record for the ExmSimple widget:

typedef struct _ExmSimplePart
{
    unsigned char           simple_shape;
    Dimension               margin_height;
    Dimension               margin_width;
    GC                      normal_gc;
    GC                      insensitive_gc;
    Dimension               pref_width;
    Dimension               pref_height;
    Boolean                 need_to_compute_width;
    Boolean                 need_to_compute_height;
    XRectangle              visual;
    Boolean                 need_to_reconfigure;
    Pixel                   saved_foreground;
} ExmSimplePart;

By convention, the top fields in the widget instance part are declarations of the widget's resources. The ExmSimple widget declares three new resources, so the widget instance part declares them in the top three fields.

The fields below the resource fields declare variables that are accessible to subclasses of ExmSimple. For example, the ExmString widget is a subclass of ExmSimple, so any function in ExmString can access any of these variables. Conversely, a widget that is not a subclass of ExmSimple should not access any of these variables.

Step 8: Declare the Full Widget Instance Record

You must declare a full instance record for your widget private header file. The full instance record of ExmSimple appears as follows:

typedef struct _ExmSimpleRec
{
    CorePart            core;
    XmPrimitivePart     primitive;
    ExmSimplePart       simple;
} ExmSimpleRec;

All Motif manager widgets derive from Core, Composite, Constraint, and XmManager. Therefore, the full instance record of the ExmGrid sample manager widget is as follows:

typedef struct _ExmGridRec
{
    CorePart            core;
    CompositePart       composite;
    ConstraintPart      constraint;
    XmManagerPart       manager;
    ExmGridPart         grid;
} ExmGridRec;

Step 9 (Optional): Declare Constraints Structures


Note: If you are writing a primitive widget, you can skip this section.

If you are writing a manager widget, you should always define the following two structures:

  • A constraint part structure

  • A full constraint structure

You should define these structures even if the widget does not currently support any constraint resources. Declaring these constraint structures now will prevent source compatibility problems if a future subclass wants to provide constraint resources.

The following subsections detail the two constraint structures.

The Constraint Part Structure

The constraint part structure defines the constraints themselves, one constraint per field. For example, the ExmGrid widget defines two constraints, so its constraint part structure is as follows:

typedef struct _ExmGridConstraintPart
{
        Dimension      grid_margin_width_within_cell;
        Dimension      grid_margin_height_within_cell;
} ExmGridConstraintPart, * ExmGridConstraint;

You should follow these conventions:

  • The tag name (for example, _ExmGridConstraintPart) should begin with an underscore.

  • The first data type name (for example, ExmGridConstraintPart) should have the same name as the tag except without the leading underscore.

  • The second data type name (for example, ExmGridConstraint) should be a pointer having the same name as the first data type except for the Part suffix.

The Full Constraint Structure

The full constraint structure specifies the widgets in the current widget hierarchy that define constraints. The last field in the structure specifies the current widget (assuming it defines constraints). For example, the ExmGrid widget defines the following full constraint structure:

typedef struct _ExmGridConstraintRec
{
        XmManagerConstraintPart manager;
        ExmGridConstraintPart   grid;
} ExmGridConstraintRec, *ExmGridConstraintPtr;

You may be wondering why we specified XmManagerConstraintPart as one of the fields. After all, the XmManager widget does not currently have any constraints. However, in order to allow for the future possibility of constraints in XmManager, the XmManager widget defines a constraint part structure. The first field of your full constraint structure should always contain XmManagerConstraintPart.

The Constraint Access Macro

If you are writing a manager widget that provides constraints, your private header file should define a constraint access macro. The constraint access macro should be named widget_nameCPart. For example, the ExmGrid widget defines a macro named ExmGridCPart. Here is its definition:

#define ExmGridCPart(w) \
  (&((ExmGridConstraintPtr) (w)->core.constraints)->grid)

Step 10 (Optional): Declaring Private Functions

In the course of writing your own widgets, you can create convenience functions that could help other widget writers (or perhaps yourself when writing a subsequent widget). Prototypes for these convenience functions must appear in the widget private header file.

Think about it this way. If you want a function to be accessible to application programmers, define it in the widget public header file. If you want a function to be inaccessible to application programmers, define it in the widget private header file.

The Widget Source Code File

The most time consuming portion of writing a widget is, of course, writing the widget source code file itself. The steps for creating a widget source code file are as follows:

  1. Include the appropriate header files.

  2. Define any macros that your widget may need. However, if a subclass might need a macro, define it in the widget private header file instead of the widget source code file.

  3. Declare all the widget methods as static functions.

  4. Define the widget's translations string and its actions table.

  5. Declare the widget's resources.

  6. Declare the widget class record.

  7. Provide external definitions of the widget class record and the widget class.

  8. Declare any static variables needed throughout the widget.

  9. Declare any trait record variables.

  10. Provide the code for all the widget methods.

The Simple.c file is an example of a widget source code file. The following subsections examine selected portions of this file.

Step 1: Include the Appropriate Header Files

The typical widget source code file begins by including several header files. The order of inclusion is important. You should include

  • Any operating system or C header files first

  • Any Xlib or Xt header files next

  • Your widget's private header file (required)

  • Any Motif header files

For example, the widget source code file for the ExmSimple widget does not specify any operating system, C, Xlib, or Xt header files. (Actually, a lot of these header files will be automatically included by Motif header files.) Simple.c does require its private header file (SimpleP.h). Simple.c also needs a bunch of other Motif header files in order for Xme calls and traits to work properly. The entire collection of header files included in Simple.c is as follows:

#include <Exm/SimpleP.h>    /* widget private header file for ExmSimple */
#include <Xm/DrawP.h>       /* for Xme drawing functions */
#include <Xm/RepType.h>     /* for representation type facility */
#include <Xm/Screen.h>      /* for screen information */
#include <Xm/TraitP.h>      /* for installing traits */
#include <Xm/CareVisualT.h> /* for XmQTcareParentVisual trait */
#include <Xm/ContItemT.h>   /* for XmQTcontainerItem trait */
#include <Xm/ContainerT.h>  /* for XmQTcontainer trait */

Table 2-1 lists some of the files that are more commonly used by widget writers.

Table 2-1. Helpful Header Files For Motif Widget Writers

FileContains:
DrawP.hDefinitions for Motif-style internal drawing functions
RepTypeP.hDefinitions for representation type facility
TraitP.hDefinitions for trait installation and access routines
XmP.hWidget private header file for XmPrimitive widget; definitions for many Motif macros and constants; definitions to support XmPartOffset binary compatibility; definitions of many data types used by Motif widgets; definitions of many enumerated constants used by Motif widgets
Xm.hTypedefs for callback structures and representation types; this file includes XmP.h
XmStrDefs.hString definitions for all Motif XmN, XmC, and XmR data types; this file includes Xm.h

Step 2 (Optional): Define Your Macros

After specifying header files, you can optionally define any macros useful to your widget. Motif makes the following stylistic suggestions regarding your macros:

  • Try to avoid duplicating macros that Motif already defines. The Motif header file Xm/XmP.h defines several macros that your widget can access. The wise widget writer always looks at this file prior to creating a new macro. Some of these macros (such as XtWidth) are required for any Intrinsics-based widget set. Other macros (like XmLOOK_AT_BACKGROUND) are specific to Motif. All Motif-specific macros begin with the Xm prefix.

  • Format the code for long macros as you would format the code for a function. In other words, make the macro easy to understand.

  • Comment your macros.

  • Place all macros in this section, even if the macro is only used in one function. (In other words, do not define local macros.)

Step 3: Declare All static Functions

You should declare all static functions.

We recommend that you use ANSI C style function prototypes when declaring functions.

You should name class methods after their class record field names. For example, if the class record field name is class_initialize, then the class method should be named ClassInitialize. The class method name differs from the class record field name in only two respects:

  • The class method name should not contain any underscores.

  • The first letter of every word in the class method name should be uppercase.

There is only one exception to these naming rules and it concerns the class record field named expose. The preceding conventions suggest that you should name the associated class method Expose. Unfortunately, Xlib already defines a macro named Expose. Therefore, by convention, you should name this class method Redisplay instead of Expose.

By convention, you should declare the functions in the following order:

  1. If your widget defines any XmRCallProc resources, then declare the dynamic resource defaulting methods first.

  2. If your widget defines any synthetic resources, declare the synthetic resource methods next.

  3. Declare all your class methods in superclass-to-subclass order. For example, when writing a primitive widget you declare any class methods from Core first, from XmPrimitive second, and from your own widget third.

  4. Declare any trait methods last.

Step 4: Define Translations and Actions

Chapter 7 details Motif translations and actions. Motif makes the following stylistic demands on widget writers:

  • You should give the translations string a name ending with Translations. For example, defaultTranslations is a good name.

  • You should use the name actions for the XtActionsRec array.

  • You should list exactly one action per line.

  • You should not declare the actions array as a constant.

Step 5: Declare Resources

Chapter 6 details resources. For now, you should be aware of the following Motif resource naming conventions:

  • The resource structure should be named resources.

  • The synthetic resource structure should be named syn_resources.

  • The constraint resource structure should be named constraint_resources.

  • The constraint synthetic resource structure should be named syn_constraint_resources.

  • You should not declare the resources array or the syn_resources array as a constant.

The fifth field of each resource structure defines an offset. If you want to create a widget that will be a binary compatible with future releases, then you should use the XmPartOffset macro to define the offset. (See Chapter 15 for details on binary compatibility.) If binary compatibility is not a goal, then you should use the XmPartOffset macro to define the offset.

Step 6: Declare the Class Record

The widget class record is your widget's table of contents. Chapter 3 details the class record for Motif primitive widgets; Chapter 4 details the class record for Motif manager widgets. For now, you should concentrate on a few stylistic points.

Make sure that all fields in the class record are commented with the formal names of the fields. For the portions of the class record defined by the Intrinsics, refer to Intrinsics documentation to find the formal names. For the portions of the class record defined by Motif, refer to Chapters 3 and 4. For example, the following excerpt from ExmSimple shows the proper way to comment the fields:

...
/* class_part_initialize */      ClassPartInitialize,
/* class_inited */               FALSE,
/* initialize */                 Initialize,
/* initialize_hook */            NULL,
/* realize */                    XtInheritRealize,
...

Motif recommends that the last field of every Motif class record be an extension field. An extension field must contain one of the following:

  • The name of a class extension record

  • NULL, to indicate the absence of a class extension record

Motif and the Intrinsics provide the following class extension record data types:

  • XmBaseClassExtRec Motif strongly recommends that your widget not define this extension record.

  • CompositeClassExtensionRec This is an Xt extension record. (See documentation on the Intrinsics for details.)

  • XmPrimitiveClassExtRec This is a Motif extension record. (See Chapter 3 for details on its fields.) If you declare an XmPrimitiveClassExtRec variable in your widget, it should be named primClassExtRec.

  • XmManagerClassExtRec This is a Motif extension record. (See Chapter 4 for details on its fields.) If you declare a XmManagerClassExtRec variable in your widget, it should be named managerClassExtRec.

Motif strongly recommends that all class extension record variables be declared as static; for example:

static XmPrimitiveClassExtRec  primitiveClassExtRec = ...

You should not declare the class record as a constant.

Step 7: Provide the External Definitions

Your widget must provide an external definition of the widget class record and the widget class. For example, the external definition of the widget class record for the ExmSimple widget is as follows:

externaldef (exmsimpleclassrec) ExmSimpleClassRec exmSimpleClassRec = {

and the external definition of the widget class pointer is as follows:

externaldef (exmsimplewidgetclass) WidgetClass exmSimpleWidgetClass =
                                                         (WidgetClass) &exmSimpleClassRec;

Motif recommends using the externaldef macro instead of the extern keyword when declaring variables. The externaldef macro handles a few portability problems.

Step 8: Declare Any Static Variables

If your source code file requires any static (file scope) variables, declare them next. For instance, if your widget uses representation types, you will probably have to declare at least one static representation type variable. For example, the ExmSimple widget creates a new representation type. Therefore, it provides the following static variables:

/* The SimpleShapeNames variable holds some normalized values.
   The XmRepTypeRegister function will use these values to determine the
   legal values for a given representation type. */
static String SimpleShapeNames[] = {
        "simple_oval",
        "simple_rectangle"
};
/* Declare a representation type variable. */
static XmRepTypeId simpleShapeId;

Step 9: Declare Any Trait Record Variables

If your widget installs any traits, you should declare the trait record variable(s) here. (See Chapter 5 for details about traits. For example, the ExmString widget installs the XmQTaccessTextual trait, so it requires the following trait record variable declaration:

static XmConst XmAccessTextualTraitRec StringATT = {
  0,                            /* version */
  StringGetValue,
  StringSetValue,
  StringPreferredFormat,
};

Notice that the variable declaration contained the XmConst macro. If your C compiler supports the const keyword, then XmConst resolves to const. If your C compiler does not support the const keyword, then XmConst resolves to nothing.

Step 10: Write the Widget's Methods

The majority of code in most widget source code files is taken up by the code for the widget's methods. Place the widget's methods in the following order:

  1. All static functions (in the order in which they were declared).

  2. Any private global functions (in the order in which they were declared in the private header file).

  3. Public functions (in the order in which they were declared in the public header file).

Table 2-2 lists all the methods of the Simple.c source code file in the order in which they appear in the file:

Table 2-2. Functions in ExmSimple

MethodWhat the Method Does in ExmSimple:
ClassInitializeCreates a new representation type
ClassPartInitializeEstablishes method inheritance
InitializeValidates resources, sets certain geometry management fields
DestroyCalls the DestroyGC method in response to the widget's destruction
RealizeCreates the window for the widget
ResizeDetermines what parts of the widget will be reduced in size when there is not enough space to display everything
RedisplayRenders the widget after an Exposure event
SetValuesResponds to changes in resource values
QueryGeometryReturns the preferred size of the widget
DrawVisualDraws the widget's visual (its filled arc or filled rectangle)
DrawShadowDraws a Motif-style shadow at the widget's outer edge
CreateGCCreates a sensitive GC and an insensitive GC
DestroyGCDeallocates the two GCs
SelectGCPicks one of the two GCs
CalcVisualSizeCalculates the ideal size of the widget's visual
CalcWidgetSizeCalculates the widget's ideal size
WidgetDisplayRectDetermines the bounding box of the widget's visual
ReconfigureOrchestrates several aspects of geometry management
ContItemSetValuesSets new visual attributes on ExmSimple
ContItemGetValuesGets the values of the current visual attributes of ExmSimple
SetSelectedVisualGets the select color of the parent
HandleRedrawAdjusts the select color under certain situations
ExmCreateSimpleInstantiates an ExmSimple widget; accessible to applications

You should precede each widget method with a comment describing the purpose of the method. Following is a conventional Motif method comment:

/************************************************************************
 *
 *  ClassPartInitialize
 *      Processes the class fields which need to be inherited.
 *
 ************************************************************************/

Make the Widget Accessible to Applications

After writing a widget, you will need to test it by instantiating it from a Motif application. Motif provides two mechanisms for generating applications:

  • Through a C application

  • Through a UIL or WML-based application, as described in Chapter 14

It is very easy to make your new widget accessible to C applications. All you have to do is compile your widget's source code file. The resulting object file can be bound to Motif applications. You do not have to place the object file into libXm.a.

If you have written several related widgets, then we suggest bundling the resulting object files into an archive file (a library).

From a Motif application programmer's point of view, it is very easy to access the new widget. Application programmers need to do only the following three things:

  • The application must include the public header file for the new widget For example, to access the ExmSimple widget, the application must include its header file, as follows:

    #include <Exm/Simple.h>

  • The application must instantiate the widget. For example, to instantiate the ExmSimple widget, call ExmCreateSimple or call XtCreateWidget, passing exmSimpleWidgetClass as an argument.

  • On the link command line, make sure that your application gets bound to the new widget's object file(s) or archives.

The Motif directory demos/programs/Exm/simple_app contains a short C application that exercises the ExmSimple widget. This directory also contains an Imakefile for building the ExmSimple widget and the application.

MultiThread-Safe Motif Widgets

Writing a multithreaded application ideally requires that the libraries the application uses be MT-safe. Otherwise the application must ensure that only one thread is inside the library at any given time. This could lead to unnatural design and coding strategies and to sacrificing parallelism in the application. Making a library MT-safe basically means that the library should take the responsibility for ensuring consistent internal state even when the application starts multiple threads that may simultaneously invoke its API.

This section describes how to develop a MT-safe Motif widget library.

MT-Safety in the Xt Intrinsics

The Xt library in Release-6 of the X-Window system (X11R6) is MT-safe. A brief description of the MT-safety provided by Xt follows.

Concurrency

Concurrency refers to the number of threads that can safely execute a piece of code at the same time. Xt permits one thread per XtAppContext to be active. So multiple threads can be active within Xt simultaneously, if each one of them is spawned in distinct XtAppContexts. Thus the concurrency provided by Xt is at the XtAppContext level.

This model is elegant for application programmers. However it can be difficult for widget programmers since it allows multiple active threads within the widget methods concurrently, and hence all global data access within the widget library needs to be suitably protected.

Fortunately, most data in a widget is stored in the widget instance. And since a widget instance hierarchy is rooted in an XtAppContext, the one thread per XtAppContext model protects instance data from corruption due to multiple threads.

AppLock

Xt implements the one thread per XtAppContext policy by protecting all public entry points into the library with AppLocks. Every public Xt API locks the application context at entry and releases it at exit. Xt provides the following functions to lock and unlock the XtAppContext:

  • void XtAppLock(XtAppContext app)

  • void XtAppUnlock(XtAppContext app)

Note that these locks are recursive; that is, the same thread invoking XtAppLock multiple times with the same application context will not deadlock.

ProcessLock

Xt provides ProcessLocks to ensure that only one thread at a time can execute critical code paths. All access to global data is considered critical code and is done while the process is locked. Xt provides the following functions to lock and unlock the ProcessLock:

  • void XtProcessLock(void)

  • void XtProcessUnlock(void)

Note that these locks are recursive; that is, the same thread invoking XtProcessLock multiple times will not deadlock.

Deadlock

Xt avoids deadlock by mandating a simple locking hierarchy: always acquire the AppLock first; then acquire the ProcessLock.

MT-Safe Motif Widgets

Motif uses the same locking strategies implemented in Xt:

  • Envelop all public APIs using AppLocks

  • Protect all global data using ProcessLocks.

API Protection

All public entry points into the widget should be enveloped with the XtAppLock, XtAppUnlock pair. Private entry points intended for internal use only need not be protected, as the invoking public API will have acquired AppContext locks. Public functions that are just wrappers around Xt functions (such as XmCreate* functions) do not require AppLocks since the Xt function already does this.

Typically, widget APIs have either a Widget, Display or Screen parameter. The AppContext can be obtained from these using the following Xt functions:

  • XtAppContext XtWidgetToApplicationContext(Widget)

  • XtAppContext XtDisplayToApplicationContext(Display *)

  • XtAppContext XtDisplayToApplicationContext(DisplayOfScreen(Screen *))

Widget APIs that do not have either a Widget, Display or Screen parameter cannot be protected using AppLocks. A potential deadlock exists if such a function wants to acquire the ProcessLock (because the function has not acquired the AppLock yet), resulting in a break in the locking hierarchy. In this situation, one must ensure that once the ProcessLock is obtained, an AppLock should not be attempted until the ProcessLock is released. Basically this means that Xt function calls, user callbacks, or other non-safe routines that can AppLock should not be invoked from within ProcessLock'ed regions in this function. For example, consider a widget public API XmFooBar:

Dimension XmFooBar(Widget w)
{
        .....
 return w->core.width;
}

The MT-safe version of the above function would be:

Dimension XmFooBar(Widget w)
{
      int return_value;
      XtAppContext app = XtWidgetToApplicationContext(w);
      XtAppLock(app);
      ......
      return_value = w->core.width;
      XtAppUnlock(app);
            return return_value;
}

Global Data Protection

Global data can be classified as either read-only or read-write:

  • Read-only globals are those that are written into only during initialization and read thereafter. The initialization can happen either at compile time or once during runtime (typically in the Widget's ClassInitialize method). These globals are MT-safe and do not require any access protection, except during initialization.

  • Read-Write globals can be written into at any time during program execution. All accesses must be protected by XtProcessLocks.

Other random pieces of global data (mostly function static variables) can be usually dealt with by either making them instance fields or stack variables. If that does not work, ProcessLocks can be used to protect global access regions. Exceptions are those variables that are used to communicate state information across function. These can be handled using thread specific storage to retain their semantics.

A discussion on the common global categories in Motif widgets follows.

Class Methods and Class Fields

A widget basically has two parts: a class record and an instance record. The instance record is per-widget and is dynamically allocated when the widget is created, and thus is not global data. Class records however are statically allocated global data. The class record typically contains methods and few data fields. The class methods are either set up at compile time and never changed thereafter or are set up at runtime during ClassInitialize (to resolve inheritance). Class initialization happens once when the first widget of that class is created. This process happens within Xt and is already under ProcessLocks. Thus we can be assured that these class method pointers get written into only once by only one thread.

So, we can consider the above two cases to be read-only situations and hence in general, class methods can be accessed without locks.

However, the Xt Base classes (RectObj, Core, Constraint, Shell) are exceptions since Motif does change their class method pointers at runtime to implement Pre and Post hooks. Hence we consider all the Xt Classes' class methods to be read-write and we need to access those method pointers within XtProcessLocks.

Consider a code fragment that invokes the resize method:

{
      WidgetClass wc; Widget w;
      (*wc->core_class.resize)(w);

}

The MT-safe version would be

{
      WidgetClass wc; Widget w;
      XtWidgetProc resize;
      XtProcessLock();
      resize = wc->core_class.resize;
      XtProcessUnlock();
      (*resize)(w);
}

Most class fields are read-write and hence require XtProcessLock protection.

XContext

XContexts are an Xlib abstraction to implement per display storage. Since Display connections cannot be shared among XtAppContexts, we are ensured that the data hanging off XContexts also cannot be shared among XtAppContexts. Hence we don't need to protect these data using Locks.

However, ProcessLock protection may be needed to protect the initialization of the XContext (created using XUniqueContext if the initialization does not happen in a Widget's ClassInitialize procedure.

Resource Lists

Widgets define their resource lists as static global XtResource structures. These lists are set up at compile time and never changed thereafter. Hence these can be considered as read-only data and do not require access protection.

Motif widgets also define synthetic resource tables as static global XmSyntheticResource structures. These lists are also read-only and do not require protection.

Action Tables and Translation Tables

Widgets define their Actions and Translations using static global XtActionRec and XtTranslation structures respectively. These are set up at compile time and only modified by the (MT-safe) Xt intrinsics. Hence these can be considered read-only data and do not require access protection.

Traits

Motif defines the Trait mechanism as a means of sharing behavior among widget classes. A widget establishes a trait by setting up a static global XmTrait structure. This table is set up at compile time and its contents should not be changed thereafter. Thus Trait tables also do not require any protection.

Resource Converters

Xt Resource converters are used for inter-type resource conversions. The converter can return the converted data either in local static storage or in the heap. The choice is made based on the address passed by the caller to store the converted value in. If the address is NULL, static storage is used, else the memory (on the heap) pointed to by address is used. Since static storage should be avoided for MT-safety, always pass in a valid heap address when invoking any converter. The guidelines when using/writing resource converters are:

  • Resource converters should be able to handle both the cases of internal static storage and passed in heap storage.

  • Invoke resource converters with a valid heap address for the converter to store the converted value. (Xt always invokes resource converters by passing in a valid heap address to store the converted value.)

Default Resource Value Procedures

XtCallProc procedures are used in widget resource lists to provide default values for a resource. Default procedures typically use static storage to store the default value. Fortunately, this usage is MT safe, since the code in Xt that invokes this procedure and copies over the default value into the resource location is within XtProcessLocks. Thus, the statics in default procedures present no problems.

Event Loops

Widgets may use their own event loops to implement Drag and Drop and other such esoteric features.

The event processing functions in R6 Xt that block while waiting for events drop the XtAppLock during the wait (XtAppNextEvent, XtAppPeekEvent, and XAppProcessEvent ). This is done so that other threads get a chance to issue Xt calls while the event processing thread waits for events. However in the case of Drag and Drop, we really do not want other threads to get into Xt and change state while the Drag operation is in progress. There is also the danger that another event processing thread might come in and "steal" events meant for this thread.

To avoid this situation, event loops should be replaced with "busy" loops that check the event queue without giving up the XtAppLock.

The following code implements a "busy" event loop that does not give up the XtAppLock by replacing XtAppNextEvent:

while (XtAppGetExitFlag(app) == False)  {
        XEvent event;
#ifndef MT_SAFE
        XtAppNextEvent(app, &event);
        XtDispatchEvent(&event);  /* Process it */
#else  /* MT_SAFE version ...  */
        XtInputMask mask;
        while (!(mask = XtAppPending(app)))
      /* EMPTY */;  /* Busy waiting - so that we don't lose our Lock! */
        if (mask & XtIMXEvent) {  /* We have a XEvent */
       /* Get the XEvent - we know its there! Note that XtAppNextEvent would also process timers/alternate inputs */
                XtAppNextEvent(app, &event); /* No blocking, since an event is ready */
             XtDispatchEvent(&event);  /* Process it */
        }
        else /* Not a XEvent, its an alternate input/timer event - Process it.
            XtAppProcessEvent(app, mask); /* No blocking, since an event is ready */
  }

MT-Safe C Library Functions

Some of the C library functions are inherently MT unsafe. A MT-safe libc implementation will provide alternatives to those functions. This section lists the common libc functions that are MT-unsafe along with their safe POSIX equivalents.

MT-Unsafe

MT-Safe

getlogin

getlogin_r

ttyname

ttyname_r

readdir

readdir_r

strtok

strtok_r

ctime, localtime...

ctime_r, localtime_r...

getgrnam, getgrgid

getgrnam_r, getgrgid_r

getpwnam, getpwuid

getpwnam_r, getpwuid_r


MT-Safe Motif Library Functions

The following Motif library functions are MT-safe:

  • XmFontListEntryCreate_r

  • XmFontListCreate_r

  • XmStringCreateFontList_r

Thread Specific Storage

Occasionally, one might end up using globals to communicate state information across functions. The only way to retain the proper semantics in this case might be to use thread specific storage. The Xthreads.h header file in X11R6 provides a set of wrapper functions to support thread_specific storage.

The sequence of steps to creating and using Thread Specific Storage are:

  • Create a unique key that identifies this storage. This step should be done only once for all the threads. The most convenient way to enforce this is to do the key creation in the concerned widget's ClassInitialize procedure. This also has the nice side effect of automatically being within XtProcessLocks. The Xthreads API for key creation is

    xthread_key_create(xthread_key_t *key_return);

  • Set and Get thread specific data: once a key is created, each thread may bind a value to that key. The values are specific to the binding thread and are maintained for each thread independently. Typically, this value is a pointer to heap storage where the actual data is stored. The heap is set up once for each thread during the first call to get the thread specific data. Subsequently, that heap location is used as the storage for the global variable. The Xthreads API is:

    void xthread_getspecific(xthread_key_t key, void **value_return);
    int xthread_setspecific(xthread_key_t key, const void *value);

    Note that xthread_getspecific returns NULL the first time its invoked for a specific thread, so that gives us the chance to set up the heap storage for each thread.

Thread specific storage can be expensive, hence it should be utilized as a last resort.

Live Resources

For MT-safety, widget resources should be designed so that XtGetValues returns a copy of the resource value (in the heap). The GetValuesHook method or the XmSyntheticResource export_proc can be used to achieve this.

If a live pointer to the actual instance field has to be returned, flag this situation as a MT-unsafe usage so that the application programmer is aware of it.

CheckList

Following is a summary of the discussions in this section:

  • First and foremost, avoid global and static variables. Use

    • Instance fields to store widget specific information.

    • XContexts to store per Display information to be shared among multiple widgets.

  • If forced to use globals or statics, protect them by either

    • Using XtProcessLocks (XtProcessLock and XtProcessUnlock) around all access points or

    • Using thread specific storage for globals that communicate state information across functions

  • The exceptions to the above are Xt resource converters and XtCallProc, where it is safe to use statics. Note that resource converters should be invoked with a valid n heap address to store the converted value.

  • The Xt base classes' class methods need to be protected since their function pointers can be dynamically written into by Motif. Copy the pointer into a stack variable and invoke the method through the stack variable. The copying needs to be done within XtProcessLocks. Use a similar strategy when accessing other fields in any class record (accesses of this nature are rare).

  • Protect all public APIs with XtAppLocks. Call XtAppLock on entry into the function and XtAppUnlock when exiting.

  • Maintain the locking hierarchy: Always acquire the XtAppLock first, then the XtProcessLock

  • Investigate usage of private Event loops and make them MT-safe as described earlier.

  • Copy out resource values if possible when an application does XtGetValues on the resource. Use the Xm synthetic resource mechanism or the GetValuesHook method to do this. If this cannot be done because it is inefficient, document these resources with appropriate warnings.

  • Use MT safe versions of unsafe libC functions.

  • Use conditional #defines to isolate all MT related changes, so that the code can be built non-MT-safe on platforms that do not support threading.