TIP 5: Make TkClassProcs and TkSetClassProcs Public and Extensible

Login
Bounty program for improvements to Tcl and certain Tcl packages.
Author:         Eric Melski <ericm@ajubasolutions.com>
State:          Final
Type:           Project
Tcl-Version:    8.4
Vote:           Done
Created:        17-Oct-2000
Post-History:

Abstract

At certain critical moments in the lifetime of a Tk widget, Tk will invoke various callbacks on that widget. These callbacks enable the widget to do lots of interesting things, such as react to configuration changes for named fonts, or create and manage truly native widgets (such as the scrollbar widget on Windows platforms). The API for setting up these callbacks for a particular window are, as of Tk 8.3.2, private. This prohibits extension widget authors from fully utilizing this powerful system; those developers can either copy the private declarations into their own source code (leading to future maintenance hassles), or forego the system entirely, hampering their ability to make truly native and well-integrated widgets. This proposal offers an extensible means for making that API public.

Rationale: Why make TkClassProcs and TkSetClassProcs public?

(The following text is adapted from George Howlett http://dev.scriptics.com/lists/tclcore/2000/10/msg00143.html )

The Tk toolkit was originally written strictly for Xlib. It created wrappers for many of the Xlib calls. A good example is creating a window. Tk's Tk_CreateWindow call in turn calls Xlib's XCreateWindow. This is so that the toolkit can perform bookkeeping on the window and manage it in various ways. The down side was that if you needed to pass specific information/flags to the XCreateWindow call you couldn't. But this only affected extensions.

Now when Tk 8.0 added native widgets, Tk also had the same problem. For example to create a Win32 button control, you have to pass information through the X emulation layer to the eventual Win32 CreateWindow or CreateWindowEx call.

So the Sun Tk developers created this notion of class procedures. A widget of particular type may need to make different calls at the time the window is created. They added to the TkWindow structure pointers to both the widget instance (i.e. the data the represents the specific widget) and a structure of function pointers (such as one to call when the window is to be created).

TkClassProcs tkpButtonProcs =

    CreateProc,             /* createProc. */
    TkButtonWorldChanged,   /* geometryProc. */
    NULL                    /* modalProc. */
};

Inside of Tk, such as in Tk_MakeWindowExist, code was added to check if the createProc of the structure isn't NULL and call that routine to create the native window.

This mechanism was also used to handle font aliasing. I can create a font "fred" that is really a { courier bold } font and use it with any Tk widget.

font create fred -family courier -weight bold
button .b -font fred

The widget will get the real font and use it in its graphics context. Think of GCs like a pen drawing a particular color. A GC draws with a particular font.

Now if I change the font, the widget's GC must be updated too.

font create fred -family helvetica -weight medium

You can see where a geometryProc is needed to indicate when font aliases change. It gets called for all the widgets using the font.

Another callback is used to handle modal events. This is currently needed only for the Win32 native scrollbar.

So here's the private structure and TkSetClassProcs call.

typedef Window (TkClassCreateProc) _ANSI_ARGS_((Tk_Window tkwin,
	 Window parent, ClientData instanceData));
typedef void (TkClassGeometryProc) _ANSI_ARGS_((ClientData instanceData));
typedef void (TkClassModalProc) _ANSI_ARGS_((Tk_Window tkwin,
	 XEvent *eventPtr));

/*
 * Widget class procedures used to implement platform specific widget
 * behavior.
 */

typedef struct TkClassProcs {
    TkClassCreateProc *createProc;
			 /* Procedure to invoke when the
			    platform-dependent window needs to be
			    created. */
    TkClassGeometryProc *geometryProc;
			 /* Procedure to invoke when the geometry of a
			    window needs to be recalculated as a result
			    of some change in the system. */
    TkClassModalProc *modalProc;
			 /* Procedure to invoke after all bindings on a
			    widget have been triggered in order to
			    handle a modal loop. */
} TkClassProcs;

void
TkSetClassProcs(tkwin, procs, instanceData)
    Tk_Window tkwin;        /* Token for window to modify. */
    TkClassProcs *procs;    /* Class procs structure. */
    ClientData instanceData;/* Data to be passed to class procedures. */
{
    register TkWindow *winPtr = (TkWindow *) tkwin;

    winPtr->classProcsPtr = procs;
    winPtr->instanceData = instanceData;
}

Extension developers could not use this interface, however, because it was private to Tk. The original authors of the interface didn't think that anything outside of the Tk widgets would need it. Of course, hindsight is 20-20, and we have since found that this is not true. Extension developers do need to use this system: widget writers that use fonts obviously need to know when a font alias changes, and new Win32 native widgets also need access to this mechanism.

Most extensions authors had/have already found a workaround: copy in the TkClassProcs structure and TkSetClassProcs routine into your code. However, this workaround leads to future code maintenance problems. Because the structure is private, its members and usage are not guaranteed to remain constant between versions of Tk. If the structure changes, the extension authors have to update all of their code accordingly.

Making the system public locks in the format and usage of the system, so that extension authors can rely on it existing from one version to the next, and they will no longer have to maintain parallel redundant copies of the structure and function definition.

Rationale: Why make TkClassProcs and TkSetClassProcs extensible?

Every time we've made a public structure, we've regretted it later when we needed to extend it to handle some new feature that we didn't originally anticipate. In general we should avoid designing new API's that preclude making future changes without introducing incompatibilities. http://dev.scriptics.com/lists/tclcore/2000/10/msg00083.html

This system is one that seems likely to require extension in the future. There are currently three callbacks: create window, geometry change, and modal event. Already one request to extend the mechanism has been made, to support the notion of a "client area" related to geometry management and labelled frame widgets (http://dev.scriptics.com/lists/tclcore/2000/10/msg00121.html , http://dev.scriptics.com/lists/tclcore/2000/10/msg00170.html ). Another possible extension is a focus management callback, to allow for smoother focus transitions between native widgets and Tk widgets; note that this focus management callback is a purely hypothetical extension at this time.

If the system is one that we are likely to want to extend with additional callbacks in the future, it behooves us to make it public in a manner that allows us to extend it while causing the minimum amount of disruption for extension authors. There are two concerns here. First is binary compatibility: will an extension compiled against a version of Tk which features the base (three callback) TkClassProcs system work with a version of Tk that features an extended TkClassProcs system? Second is source compatibility: will an extension author have to update their sources when they want to recompile their extension against a version of Tk that features an extended TkClassProcs system? Ideally, the system that we make public will allow extension while retaining binary and source compatibility between versions of Tk.

Specification

I propose that the following steps be taken to make TkClassProcs and TkSetClassProcs public:

  1. Rename TkClassProcs to Tk_ClassProcs; rename TkSetClassProcs to Tk_SetClassProcs; rename TkClassCreateProc, etc., to Tk_ClassCreateProc, etc. Move the structure definition, function prototype, and callback typedefs from tkInt.h to tk.h. This is in keeping with Tk public interface naming conventions.

  2. Add a single size field to the Tk_ClassProcs structure. This field is initialized at the time that the structure is allocated, and always contains the size of the structure. This field will be used to provide a simple versioning scheme for the structure. Portions of Tk that use the class proc callbacks will inspect this size field to ascertain whether or not a particular instance of the Tk_ClassProcs structure is of a version that contains a given callback. See the example below.

  3. Rename the geometryProc callback to worldChangedProc. The name geometryProc is somewhat misleading. Currently, the callback is used only to support font aliasing, as described above. This is sort of geometry related, but it doesn't necessarily mean that geometry of the widget must change, it just means that the widget will have to update its world view to reflect the current state of the world. In addition, the callback will likely be used to support color aliasing when that is added to Tk (imagine defining a color "myColor" to mean "#c4d3a2" and then configuring widgets to use "myColor" instead of the literal value; this provides all the benefits for colors that font aliasing does for fonts). When that is done, geometryProc will be truly misleading, since a color change probably does not mean a geometry change for the widget.

  4. Change the order of the callback fields in the Tk_ClassProcs structure, making worldChangedProc the first of the callbacks listed in the structure. In the existing private TkClassProcs structure, the first callback is the createProc. It is not strictly necessary to make worldChangedProc the first callback. However, most widgets in Tk (canvas, entry, scale, text, message, listbox, menu, menubuttons, scrollbars on Unix and Mac, and buttons on Unix and Mac) use only this callback. Making it first in the structure (after the size field, which must be the very first entry) means a little bit less work for widget authors in the common case, because they need not include the NULL declaration for the createProc slot in the structure. Compare:

    static Tk_ClassProcs myClassProcs = {
        sizeof(Tk_ClassProcs), NULL, myWorldChangedProc
    };
    
    with:
    
    static Tk_ClassProcs myClassProcs = {
        sizeof(Tk_ClassProcs), myWorldChangedProc
    };
    
    Since the _createProc_ is used so infrequently, why require
    

    that all widget authors explicitly declare it to be NULL? This change just simplifies everybody's life that much more.

Usage of the public API will be very similar to usage of the existing private API:

static Tk_ClassProcs myClassProcs = {
    sizeof(Tk_ClassProcs),
    myWorldChangedProc
};

static int Tk_MyWidgetObjCmd(...) {
    ...
    Tk_SetClassProcs(widgetPtr->tkwin,myClassProcs,(ClientData)widgetPtr);
    ...
    return TCL_OK;
}

Portions of Tk that need to use a particular callback, such as Tk_MakeWindowExist, use code like the following:

Tk_ClassProcs *thisClassProcs = tkwin->classProcs;
createProc *procPtr;

/* Make sure the structure we were given has the createProc field
 * in it by checking that the size of the structure is at least
 * big enough to have that slot.
 */

if (thisClassProcs->size <= Tk_Offset(Tk_ClassProcs, createProc)) {
    procPtr = NULL;
} else {
    procPtr = thisClassProcs->createProc;
}

if (procPtr != NULL) {
    /* Invoke the createProc for this window. */
    ...
} else {
    /* Use the default Tk window creation mechanism. */
    ...
}

Benefits of this implementation

Benefits of this implementation are as follows:

  1. Usage of Tk_ClassProcs and Tk_SetClassProcs very, very closely parallels the usage of the existing private API. In fact, the only difference is a small change in the particular fields of the Tk_ClassProcs structure (especially, the new size field, for version information, and the reordering of the callback fields).

  2. All instances of "mywidget" reference the same Tk_ClassProcs structure. This is memory efficient.

  3. We do not need to explicitly initialize to NULL those fields of myClassProcs that we don't use. The ANSI C specification states that static variables (and members of statically declared structures) that are not explicitly initialized are initialized to zero.

  4. This retains binary compatibility. The size field of the Tk_ClassProcs structure is set at compile time, so when a later version of Tk checks the size field to see if a new callback can be used, it will fail. That is, if extension author A compiles the extension against version X of Tk, which has three fields in Tk_ClassProcs, the size field of myClassProcs will be set to 12 (assuming 4-byte pointers). When using that extension with Tk version Y, which may have four fields in Tk_ClassProcs, the size check for that fourth field will fail, since the size field, set to 12, will be less than or equal to the offset of the fourth field in the structure.

  5. This retains source compatibility. Because of #3 above, unless the extension author wants to use the new callbacks, they need not worry about their addition, because the new fields will be automatically set to zero.

  6. There is minimal API bloat. Only one public API is added, Tk_SetClassProcs.

  7. The system is "type safe" with respect to the function signatures of the callback functions. Any type mismatches will be caught at compile time.

  8. If desired, widget authors can directly reference elements of the Tk_ClassProcs structure:

    myClassProcs.createProc = myCreateProc;
    

Drawbacks of this implementation

The drawbacks of this implementation are as follows:

  1. The required value of the size field will seem like a bit of black magic to developers new to the system. The question "Why does this field have to be set to this value? If it's always the same thing, why is it stored at all?" Of course, experienced programmers will recognize why it has to be set, and that in fact, it is not always the same value. This issue can best be addressed by appropriate documentation.

  2. Extensions that use the existing private TkClassProcs and TkSetClassProcs mechanism and which were compiled against versions of Tk <= 8.3 will not work with new versions of Tk, since the format of the Tk_ClassProcs structure will change. However, this is the consequence of using private structures and API's in your extensions: when those private structures and API's change, you have to update your extension accordingly. We cannot allow ourselves to be overly constrained by this issue. The existing mechanism is private, period. Authors that use it do so knowingly and willfully.

Reference Implementation

http://sourceforge.net/patch/?func=detailpatch&patch\_id=102213&group\_id=10894

Copyright

This document has been placed in the public domain.

History