Affiliations and addresses should be current. The books and articles recommended are still among the best, though most have newer editions!
Engineering Sciences Laboratory
Georgia Tech Research Institute
Atlanta, GA, 30332
USA
(sl11@prism.gatech.edu)
Pencom Software
9050 Capital of Texas Highway North, Suite 300
Austin, TX, 78759
USA
(meo@pencom.com)
This is not by any means a proper introduction to writing widgets, although it may prove helpful in that context. We assume the reader has some familiarity with at least the concept of either writing or using widgets.
An earlier version of this paper was presented at the 4th Annual X Technical Conference held in Boston, Massachusetts (USA) in January of 1990.
We investigated extensively the few toolkits and widget sets that were available at the time, and decided upon the Xt intrinsics and the Athena widget set (Xaw) that came with the standard distribution.
There were no books on X11 out at the time we started the project; Usenet and some materials from a pre-release DECwindows training course were our resources beyond the distribution tape.
By the time we were done, we were amazed at just how much we had learned during the project. The questions we have seen in the comp.windows.x newsgroup imply that others are struggling with the same sorts of problems we had. Thus, we decided to share what we have learned about developing and using widgets.
We were developing on Sun 386i systems, under SunOS 4.0.2, with X11R2 (and later X11R3) using the C language, so everything we say is directly applicable to this and similar environments. However, as X11 is pretty much X11 no matter what the environment, most of the information should transfer to other situations pretty much intact.
The product is an application generator, running under UNIX and X11, which produces DOS and VMS files which drive other proprietary products. It is fairly complex, as are many of the interactions between its widgets.
At any given time, two hundred or more widgets may be active, and any given run of the program may easily involve over a thousand widgets. This does not include duplication of widgets created and destroyed on the fly, of which there may be many more.
Later we will reference a field. This is an emulation of a data entry field in a PC-based windowing program. Each field contains numerous data structures, one of which is a widget. The widget window is the only part of the field the user actually sees. When a user selects a field with a mouse button the field window inverts.
The O'Reilly X Window System Series Volume 4 X Toolkit Intrinsics programming Manual and Doug Young's book X Window Systems Programming and Applications with Xt (see Books to Take on a Desert Island) both contain an excellent discussion of the first three items mentioned here.
Additional callbacks may be registered on an existing list by the application programmer. As an example, "button" widgets typically execute a callback list when pressed or released. Adding to the callback list is an easy way to bind application-dependent behavior to a button.
A widget may contain as many callback lists as its developer deems necessary. Likewise each list may contain as many callbacks as needed. All widgets contain a Destroy callback list, which is called prior to a widget being destroyed.
A user action is an event directly initiated by a user, such as a mouse button event or a keypress, as opposed to (for example) an expose event.
Each entry in a translation table binds a sequence of user actions to one or more logical action routines. Each entry in an action table binds a logical action to a physical action routine name.
These action routines may be defined within the widget or by the application. An application can add action routines to those provided by a widget. Furthermore, an application may either add to or override the translation list for any widget.
An obvious example of an accelerator, at least to us, was a general help popup bound to the HELP key, that would give context-sensitive help (for each particular widget on the display).
A problem we ran into here is that we could only get routines declared in a widget's table to run when the accelerator event occurred. It appears that accelerators are bound to a widget at compile time, whereas we needed to install something from the application. We finally gave up and simply added a help translation to every widget in the tree. This example does not invalidate the accelerator concept; it simply points out a limitation of the implementation.
Translation tables are the preferred method for handling events; they are more flexible in many ways than either event handlers or callbacks, and they may easily be modified through the resource manager.
Callbacks are handy to use when both the application and the widget must pass data into a routine; we used both types of data frequently with command buttons. Keep in mind that the callback lists are predefined - an applications programmer may define callbacks for existing callback lists, but cannot add new callback lists to the widget.
Event handlers are the lowest level of abstraction for event methods. (Actually, when you use the translation manager, the Xt intrinsics implement the translation mechanism as XtEventHandler routines, allowing you to deal with the events/actions on a higher level basis.) Use them only when you have a good reason to hardwire events into the widget. In real life, there aren't many such reasons.
Accelerators make it very easy to write a general-purpose routine to use in more than one widget, without having to specifically add code to each widget to invoke the routine.
A summary table appears below, showing the relative strengths and weaknesses of each approach.
Method Strengths Weaknesses ------------------ ---------------- ------------------- translation tables most flexible easily modified callbacks data flexibility lists predefined event handlers lowest level accelerators reusability must be in a widget
For example, an Expose routine in a widget might be invoked by calling XClearArea(), which generates an Expose event. In the case of the ST BoxTextWidget (a simplified TextWidget), the AddChar() routine simply places the new characters in their buffer locations and calls XClearArea(); the Expose method routine actually draws the text, but does not have to clear the area, which in some cases would be redundant. Furthermore, this eases the task of telling the Expose routine the area exposed, as that data is passed with the event.
If all else fails, XSendEvent() may be used to send the desired event to the widget's window.
While some things may suggest themselves as needing to be under programmer control, it's rare that resources fit this category. Button names are a typical example. Using the resource database for button names allows easy translation to other languages, or customization for unexpected environments. Some systems' users may expect "exit" to save work and "quit" to cancel, while others may expect the opposite setup. And if some neanderthal really wants to set their foreground/background combination to taupe/mauve and you don't let them, you have an unhappy customer.(Determining the definitions of taupe and mauve is left as an exercise for the reader.
For those users who don't know or care about resources, you should supply reasonable defaults through resource files. X provides for default system, application and user resource files in standard locations. This is covered in O'Reilly Volume 4.
In R2 there was no XtRImmediate type. Early on, in haste due to a deadline, we copied a resource which had a value of 0 to a new resource, which we gave a value of 1. We got some truly strange core dumps. The R3 XtRImmediate resource type makes things much more straightforward.
A somewhat de facto standard for this is to have the client generate a list of available command-line-modifiable resources either when a bad option is entered or when the option "-help" is given.
The R4 release provides a hook for resource querying but the information is not readily available.
These problems were more exaggerated when running clients and servers on separate systems, and even more so when using a slow X11 terminal instead of a workstation-based server. (Faster X terminals had fewer such problems.) Many problems that occurred in all of the above circumstances were easily fixed on the workstations, but continued to plague the people running the clients on X terminals. Workstation loading often had a detrimental effect as well.
The problems seem to be related primarily to the asynchronous nature of X, and secondarily to time stamps.
Some timing issues are related to the specific server implementation in use. For example, HP/Apollo provides its own version of the X server for its Apollo workstations. This server has the capability of working with Apollo's own window manager, rather than replacing it. Unfortunately, this has resulted in some event delivery problems. (Apollo is not unique in such problems.)
The ST application builds PC screens for other software ST sells. Each field (representation of a data entry field . created on the PC screen) has quite a bit of information associated with it. When a user selects a field, one or more database queries occur, and as many as 50 widgets on the screen may be affected. Some of these may need to be mapped, others may have to be created, and the rest will be redrawn. The result, especially over a busy net, with a busy workstation, is that anywhere from 1 to 5 seconds may transpire between the select action and the entire sequence completing. Most of the time this is merely annoying. Later we will explain why this is sometimes a serious problem.
We quickly discovered that neither XFlush nor XSync alone would do the trick. We developed a routine that did the trick for us although it was kludgy and still a bit slow (especially the R2 version).
This is not optimal but it works better than anything else we found.
Under R2 we did the following:
Widget desk; /*a top level widget*/ void StXFlush() { XEvent event; XFlush (XtDisplay(desk)); while (XtPending()) { XtNextEvent(&event); XtDispatchEvent(&event); } XSync (XtDisplay(desk), 0); }Under R3 this no longer worked, due to changes in the way the event queue is processed, so we explored a bit and came up with the following:
Widget desk; /*a top level widget*/ void StXFlush() { XFlush (XtDisplay(desk)); while (XtPending()) { XtProcessEvent(XtIMAll); } }
We also revised our recommendations as to the maximum number of users concurrently running the application on a given system, and encouraged heavy users with X terminals to have their own network.
There is no easy fix for this type of problem.
MapNotify and UnmapNotify events have no timestamps; this appears to be at least part of the problem.
A window will be displayed by the server, but one of several strange behaviors will occur. The window cannot be selected; clicking a mouse button on it merely results in a beep from the server. It appears that this window has become disembodied from a widget.
At other times, the window will act normally as far as the mouse is concerned, but does not bring up the expected database information. Moving or resizing the window connected with the displayed database information moves this phantom field window as well. It appears that two windows have become attached to the same widget. This is, of course, theoretically impossible. (See screen shot.)
In each case, we have not been able to isolate the bug. The only theory we have developed and not subsequently disproved is that the problem ultimately lies in the fact that the Xt intrinsics code is generally not reentrant. (This is true for R2 and R3, and somewhat in R4.) Interrupting widget creation/modification routines to create/modify another widget could well be the heart of the problem.
We are currently relying on user training to solve this problem.
The other time not to write a widget is when the required functionality involves a complex programming interface. Even these cases may be widgetized, at some loss of widget method orthogonality. A widget with potentially large arrays, whose individual elements must be accessible individually, is a good example.
On the other hand, there are potential problems with using the Destroy callbacks. Most inherited (or chained) methods are called in topdown order beginning with the Core widget class. Destroy methods are called bottomup, so superclass Destroy methods must not refer to any subclass parts.
Always use the appropriate intrinsics calls to create or allocate resources, as well as destroy them. This applies to the widgets themselves as well.
When similar widgets are frequently created and destroyed (such as top level button boxes or popup informational boxes) it may be better to create each one once, either at startup or first usage as necessary. After that, simply unmap them instead of destroying them. Wherever you would have created a new one, update the appropriate widget component (such as the button label) and remap them.
When varying numbers of similar widgets are used, consider creating a pool of widgets pointed to by an array or linked list. The pool may be expanded or contracted as required using standard array or linked list manipulation routines.
The above approaches are based on the fact that while users want a system to react quickly most of the time, they will accept occasional lapses. What most users do not want is a system that appears to be slow most or all of the time. Many widgets may be created at startup time; after that consider carefully whether to create widgets on the fly, reuse existing widgets, or use widget pools.
You can debug from your X11 display in a separate xterm if your client doesn't do mouse or keyboard grabs. It is often easier to use a separate physical terminal or display, if possible.
The cleanest way to start a debug session is to start the client normally, and then attach the debugger to it by passing the client's process ID as a command line option. Unfortunately, not all debuggers support this option. As always, RTM for information on your debugger.
It's not a good idea to have the debugger catch the SIGIO signals, or you generate an interrupt for every single mouse event.
Debugging is intrusive by nature. The very act of debugging causes different results than would otherwise occur. This problem can be greatly exaggerated with many X clients.
As an example, if you step past a line that requests the next key pressed, you will probably have to step past at least one more line before the debugger will be able to see the results of the key code request.
One example of this is dragging the fields around the PC screen region. This is currently handled with the widget drag and resize routines, but should probably be replaced with fast line-drawing and rubberbanding - the widget can then be dropped once the move or resize is finished.
A fractal drawing program, for instance, could be done using a 1-pixel widget for each point, but would hardly be worth the effort as it would be substantially slower and larger than one built on top of raw Xlib calls.
A spreadsheet would be much easier to implement with widgets than without them.
If a user interface builder (user interface management system) is available for your system and toolkit, you will find it speeds interface development quite a bit. Evaluate them carefully for size and speed of code produced.
To illustrate these tradeoffs, we offer the following notes about the commercial application mentioned earlier.
In its latest release, the application consisted of about 60,000 lines of source code, and took about 45 person-months to produce (including X11 training). Without using a toolkit, this would have been somewhat larger, taken longer to produce, and would be much more difficult to modify and maintain.
On the other hand, the optimized, stripped executable is about 750KB in size, and requires from 3 to 5 MB of memory at runtime. Without a toolkit, the executable would have required somewhat less runtime memory and executed between 20 and 30 times faster. (This represents our best estimate for the application generator package. Your mileage may vary.) To aid in reducing program size, some sites choose to build the X libraries as sharable. When running a diverse mix of X clients, this is a good idea. For turnkey systems or any time most of the users are running the same client, the client should be linked sharable, especially if it is very large. If in doubt, try both approaches with your expected client mix and load.
R4 runs substantially faster than R3; future versions of the application will benefit greatly from this speedup.
The X11 philosophy of "mechanism, not policy", does not totally extend through the toolkits. Some widgets or toolkits may encourage, if not enforce, certain policies. This may be compared to software engineering. The concepts behind software engineering comprise a mechanism, but selection of a methodology, design tool, or language immediately defines policy to some extent. This is apparent in the Open Look and Motif standard user interface guidelines, for example.
OSF, consisting of DEC, HP/APollo, IBM and others too numerous to mention, provides the Motif toolkit. UI, guided by AT&T and Sun, backs the Open Look toolkit. Developing portable applications to run across platforms from all these vendors, yet keeping a consistent look and feel, is therefore still a problem.
The current de facto standard in the marketplace is Motif. Last year the European Commission selected Motif as its user interface in the absence of any standard. UNIX vendors are for the most part making the same choice. Most of the current and soon-to-be-released X applications are Motif-based, although many developers are promising Open Look versions at some point.
DEC, committed at one time only to DECwindows, now ships Motif as well. AT&T is considering adding Motif support to future releases of System V. Sun, however, citing the large volume of SparcStations shipping with the Open Look interface and related toolkits, remains adamantly behind Open Look. The official explanation from Sun is that Open Look will become the standard by sheer weight of numbers. Unofficially, higher-ups at Sun have indicated Sun will support Motif only over their dead bodies.
What can you do in the meantime? As with other portability concerns, isolation of toolkit calls is a good idea. This can make toolkit ports relatively painless, assuming you do your homework in determining the subset of operations you need, and how they are supported. An Open Look/Motif subset interface definition is in the works by at least one group.
Note that most commercial toolkits at this point are based on R3.
There are always trade-offs, but it is usually best to work within the language at hand. If you attempt to make C look like something else, be sure you are aware of the impacts this has on programmer productivity, project schedules, and the like.
We compromised. The widget code conforms to the guidelines of the Consortium, as do most of the interfaces. The rest of the code usually looks like standard C.
The other main "legitimate" reason is when following the rules doesn't work. While we had less problems with R3 than with R2, there were still a few cases where following the rules didn't work. (Bear in mind that, by definition, the specification is correct, even if the implementation is not. This can make life very interesting. Fortunately very few such cases have turned up in the released MIT code.)
Remember that quick hacks often turn into production code, or at least widely used code.
Realize, also, that many of the rules and guidelines are related to inter-release compatibility. Something that works now, but does not conform to the rules, may well not work with future releases of X11.
In addition, Oliver Jones and Doug Young were a great help, both via email and through their books, as were the folks at O'Reilly (in particular Tim O'Reilly and Adrian Nye).
We offer a special thanks to Dany Guindi, for going out of his way to teach us about X, and for all of his help and support.
Finally, we are deeply indebted to Paul Anderson, our former project manager, for giving us the freedom to learn and explore X so thoroughly during the course of the project, and for his valuable insights into X and user interface programming.
Jones, Oliver, Introduction to the X Window System, Prentice-Hall, 1988. ISBN 0-13-499997-5. An excellent introduction to programming with Xlib (Release 2). Written with the programmer in mind, this book includes many practical tips that are not written anywhere else. You'll still need the MIT Xlib manual, as this book does not try to be totally complete. Highly recommended for beginning Xlib programmers.
McCormack, Joel and Paul Asente, "Using the X Toolkit or How to Write a Widget," in Proceedings of the Summer, 1988 USENIX Conference, pp. 1-13. An excellent tutorial on writing basic X Toolkit widgets. Potential widget writers (and maybe users, too) should probably start by reading this paper. Unfortunately, it's based on X11R2, so some of it is out of date.
O'Reilly and Associates, The X Window System Series (7 volumes), O'Reilly and Associates, 1988, 1989, 1990. ISBN 0-937175-26-9, 0-937175-27-7, etc. This is a 7 (and growing) volume set of books. Volume 1 is a new tutorial on Xlib. Volumes 0, 2, and 3 are approximately the same as the MIT manuals (protocol manual, Xlib manual pages, and popular client manual pages). Volumes 4 and 5 cover the Xt toolkit and Athena widget sets. They currently describe R4, or are being rewritten to do so. Differences from R2 and R3 are covered. Future volumes will reportedly cover the X Toolkit intrinsics, XView, and Motif. Written by technical writers, these are probably the most professional looking of the X books.
Rosenthal, David S., "Going for Baroque", in Unix Review June, 1988, pp. 70-79. A version of the "hello, world" paper, presenting and comparing the basics of the X library and the X Toolkit. All potential X programmers (Xlib or X toolkit) should understand everything in this paper before they attempt writing any X programs. Included in the MIT X distribution.
Young, Doug, X Window Systems Programming and Applications With Xt, Prentice-Hall. ISBN 0-13-972167-3. An excellent tutorial on programming with the Xt intrinsics. Examples in this book use the HP widgets, but a Motif edition is available as well (ISBN 0-13-497074-8).
Copyright 1989, 1990, 1994 Susan Liebeskind and Miles O'Neal, Austin, TX. All rights reserved. Miles O'Neal <roadkills.r.us@XYZZY.gmail.com> [remove the "XYZZY." to make things work!] c/o RNN / 1705 Oak Forest Dr / Round Rock, TX / 78681-1514