Artifact [5fba6c45e1]

Login

Artifact 5fba6c45e15e5bf26b050a561eb791cb4331f0d63eb6cfbe4d5c3da706f52911:


# TIP 5: Make TkClassProcs and TkSetClassProcs Public and Extensible
	Author:         Eric Melski <[email protected]>
	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.