Chapter 11. Interclient Communications

This chapter discusses communication through the X server between an application and the window manager, and between two applications. The application--window manager communication is performed by code in the Shell widget. The application sets shell resources to control this communication with the window manager. Application--application communication is usually done with a process called selections. This form of communication is already implemented in most widgets that display text, but you may want to implement it in your own custom widgets. Selections can also pass other kinds of data such as graphics. How to use the Motif Clipboard is also described. Finally, the concepts behind Motif 1.2 drag-and-drop are described.

Applications share the server with other clients. Server resources, such as screen space and colormaps, must be used in a responsible, consistent manner so that applications can work effectively together. In most window systems, the window system itself embodies a set of rules for application interaction. However, the X Protocol, Xlib, and Xt were all specifically designed to avoid arbitrary conventions, so that they provide “mechanism, not policy.”

Instead, the conventions covering interclient communication are described in a separate document, adopted as an X Consortium standard in July, 1989, called the Inter-Client Communication Conventions Manual (ICCCM). This chapter will not fully describe the ICCCM, because the job of implementing its rules is given over to a special client called the window manager and a special widget class called Shell and its subclasses.[70] As a result the details of the ICCCM are, for the most part, irrelevant to the application writer's needs. Widget writers may need to refer to it when implementing selections.

In X Toolkit programs, the Shell widget returned by XtAppInitialize() and used as the top-level window of the application automatically handles most of the required interactions with the window manager. However, the Shell widget needs additional information in certain areas. For example, the application needs to provide an icon pixmap so that the window manager can iconify it properly. The first section in this chapter describes how to set Shell resources to control how an application interacts with the window manager. This portion of the chapter is for application writers, regardless of whether you need to write widgets for your application.

The ICCCM defines the required information that must be sent by the client. The Shell widget class takes care of sending this information. However, mwm, the Motif Window Manager, also allows the application to send or be sent optional information. One set of information warns the application when it is about to be killed or when one of its windows is about to be destroyed. A separate set lets the application specify what kind of decorations it wants on its top-level shell widgets. The final set allows mwm to notify the application when a certain entry on its system menu has been selected. This entry is normally added to the user's .mwmrc as part of the application's installation procedure.

In X Toolkit applications, widgets can communicate with other widgets using a mechanism called selections, which in turn is based on an X mechanism for common storage called properties. Whether the widgets involved in transferring selections are part of the same application or different applications is irrelevant. The communication between widgets takes place without input from the application. However, it can be used as a means of communication between applications. The second major section in this chapter will describe these concepts and how to implement selections between your own custom widgets. Only if your application requires a custom widget that must communicate with other widgets should you actually have to write this code. Thus, this part of the chapter is primarily for widget writers.

Motif 1.2 introduces another layer of abstraction over selections, called drag-and-drop. This is an optional user interface feature where data can be moved or copied from one widget to another (in the same or different applications) by dragging an icon.[71] The most widely known implementation of the drag-and-drop concept is the Apple Macintosh finder where files can be moved from one folder to another by dragging them. The Motif implementation is more general however, since any type of data can be selected and dragged. Motif drag-and-drop is built-in to the Text, TextField, Label, List and most forms of Button widgets for transferring text. The details of how to implement drag-and-drop within a widget are beyond the scope of this book, see the OSF/Motif Programming Manual. That book provides 60 pages on the subject, as well as a 65 page example.

Window Manager Interactions

The window manager was introduced in Chapter 1, Introduction to the X Window System, but little mention of it has been made since then. You may recall that the window manager is just another client running on a server, except that it is given special authority to manage screen space and other limited server resources like colormaps.[72] To let the window manager do a better job of mediating competing demands of the various clients, each client gives the window manager information called window manager hints. These hints specify what resources each client would like to have, but they are only hints; the window manager is not obligated to honor them, and the client must not depend on them being honored.

Application code has little to do to interact properly with the window manager. The Shell widget returned by XtAppInitialize() takes care of setting the essential window manager hints. However, there are a number of optional window manager hints that the application may wish to have passed to the window manager. This is done mainly by setting resources of the Shell widget. Also, there are variations in window managers and it takes some effort to make some applications work equally well under all of them.

The next few sections describe the various resources of the Shell widget, including how and when they should be set. Because the Shell widget is part of the Xt standard, these resources are present when writing applications with any widget set.

Shell Subclasses

There are several types of Shell widgets. The Shell widget class itself, specified by the class structure pointer shellWidgetClass, is never instantiated directly in applications. Only its subclasses are used. You have seen two subclasses of the Shell widget used earlier in this books: the one used for the application's top-level widget and the one used for popups. The application's top-level widget is created by passing the class structure pointer applicationShellWidgetClass as the widget class argument to XtAppCreateShell(); this call is also made internally by XtAppInitialize(). Popup shells for dialog boxes are created by passing transientShellWidgetClass as the widget class argument to XtCreatePopupShell(). There are two other subclasses of Shell that are commonly used in applications. One is the OverrideShellWidgetClass, passed to XtCreatePopupShell() when the shell is used for popup menus. The convention is this: the shell should be an OverrideShell when the pointer is grabbed to prevent other windows from getting input while the popup is up, and the shell should be TransientShell for other popups. This is discussed further in Chapter 13, Menus, Gadgets, and Cascaded Popups.

The other additional subclass of Shell is topLevelShellWidgetClass, which is used to create additional, non-popup, top-level shells. Some applications have multiple permanent top-level windows. One of the top-level shells would be of the applicationShellWidgetClass, and the rest would be of the topLevelShellWidgetClass. Each would have a separate icon.

Setting Shell Resources

Shell resources are primarily a way for the user and the application to send in data to be communicated to the window manager. These window manager hints control several major areas of window manager activity: they manage screen space, icons, and keyboard input. We'll discuss these areas one at a time in the following sections. Table 11-1 lists the Shell widget's resources with a brief description of what they control, and whether the application, the user, or Xt normally sets them.

As indicated in Column 3 of the table, some Shell resources are intended to be set only once. These set-once resources can be left to their default values, set in the app-defaults file, or they can be set in the code before the Shell widget is realized; but they should not be set with XtSetValues() after realization.

Table 11-1. Shell Resources

Resource

Purpose

When Settable

usually set by Xt or Shell itself, depending on the subclass:

XmNargc

Command-line args count

-

XmNargv

Command-line args

-

XmNoverrideRedirect

Set for popup shells not to be decorated

-

XmNtransient

Set for popup shells

-

XmNwaitForWm

Whether to wait at all

-

XmNwindowGroup

Links popups to main window

-

XmNwmTimeout

Waiting time for slow wm

-

usually set by user:

XmNiconX

Icon position

Before realization

XmNiconY

Icon position

Before realization

XmNiconic

Sets XmNinitialState to iconic

Before realization

XmNgeometry

Initial size and position

Before realization

XmNtitle

String for title bar

Anytime

usually set by application:

XmNallowShellResize

Does shell ask wm for size change?

Anytime

XmNbaseHeight

Height of fixed components

Anytime

XmNbaseWidth

Width of fixed components

Anytime

XmNheightInc

Desired height increment

Anytime

XmNwidthInc

Desired width increment

Anytime

XmNiconMask

Mask used with icon pixmap

Before realization

XmNiconName

String for icon

Before realization

XmNiconPixmap

Picture for icon

Before realization

XmNiconWindow

Window for icon

Before realization

XmNinitialState

Whether normal or iconic

Before realization

XmNinput

Keyboard input model

Before realization

XmNmaxAspectX

Maximum aspect ratio x/y

Anytime

XmNmaxAspectY

Maximum aspect ratio x/y

Anytime

XmNmaxHeight

Maximum acceptable height

Anytime

XmNmaxWidth

Maximum acceptable width

Anytime

XmNminAspectX

Minimum aspect ratio x/y

Anytime

XmNminAspectY

Minimum aspect ratio x/y

Anytime

XmNminHeight

Minimum acceptable height

Anytime

XmNminWidth

Minimum acceptable width

Anytime

XmNsaveUnder

Should server save under when mapped

Before realization


Several of the Shell resources are set automatically by Xt or the window manager and under normal circumstances should not be modified by an application:

  • The XmNargc and XmNargv resources are set by Xt to contain the command-line arguments used to invoke the application. These values may be used by the window manager or a session manager to allow the user to reinvoke the application using the same arguments.

  • The XmNwindowGroup resource is used to link popup windows to an application's main window. If not set explicitly, XmNwindowGroup is automatically set to the top-level ancestor of the popup shell, which is usually the application's top-level shell.

  • The XmNtransient and XmNoverrideRedirect resources are set automatically by Xt depending on what kind of Shell widget you create. For top-level application shells, both are set to indicate that the window manager should treat this window as a top-level window. For TransientShell popup shells, XmNtransient is set automatically to the shell specified in XmNwindowGroup. When the top-level window is iconified, the TransientShell popup will also be iconified. The TransientShell may also be decorated differently from main application shells.

  • XmNoverrideRedirect defaults to True in the OverrideShell class, indicating that the popup can map itself completely without window manager intervention. This type of popup shell is typically used for popup menus, because they should not be decorated or interfered with by the window manager.

  • The XmNwaitForWm and XmNwmTimeout resources control the response to delays by the window manager when responding to geometry change requests. By default, XmNwaitForWm is True, and XmNwm_timeout is five seconds. When making a geometry request to the window manager, the Shell widget will wait for five seconds for a response. If a response does not arrive within five seconds, the Shell widget will set XmNwaitForWm to False, and assume that the window manager is not functioning. The Shell widget will continue without waiting for the event that would otherwise arrive to report the size of the window, and also without updating Xt's internal cache of window geometries. When this event does arrive later, Xt may set XmNwaitForWm back to True and update its internal cache. These resources should normally be left to their default values.

The XmNgeometry and XmNiconic resources are intended to be specified by the user. Only these two resources use standard command-line options. The XmNgeometry resource is settable using a command-line option of the form:

-geometry [width{x}height][{+-}xposition{+-}yposition]

(Either the size or position portion of the geometry string may be omitted, as indicated by the square brackets. In specifying the x or y position, a positive value, indicated by a plus sign, is relative to the top or left side of the reference window, while a negative value, indicated by a minus sign, is relative to the bottom or right side.)

The -iconic command-line option, if present, sets the XmNiconic resource, indicating to the window manager that the application should start up as an icon.

The XmNicon_x and XmNicon_y icon position hints are best left specified by the user. Icons' positions are determined by the window manager based on these hints or on an icon-positioning policy. An application with several top-level windows could set the icon position hints in its app-defaults file so that the icons for each top-level window appear side-by-side.

We will discuss the remaining resources in related groups. Section 11.1.3, "Screen Space" discusses the ones related to the size of the application's main window and other screen space issues. Section 11.1.4, "Input Model" describes the keyboard input model hint. Section 11.1.5, "Colormaps" describes how applications should handle colormaps to cooperate with the window manager. Section 11.1.6, "Icons" describes the ones that apply to the application's icon.

Screen Space

The Shell widgets of each application have windows that are children of the root window. These are called top-level windows. The window manager directly controls the size and position of the top-level windows of each application. The window manager does not control the geometry of other widgets that are the descendants of the Shells, except indirectly through the geometry management mechanism described in Chapter 12, Geometry Management.

The most basic size hint, the one that specifies simply the desired initial height and width, is automatically set by Xt based on the size of the child of the Shell widget. This hint is used by most window managers to display the outline of your application when it first appears on the screen, ready for the user to place and/or size the application. If your application does not have specific size needs, you need not set any additional resources.

The additional size hints specify the application's range of desired sizes, desired increments of sizes, and desired range of aspect ratios. These are set by the XmNbaseHeight, XmNbaseWidth, XmNminWidth, XmNminHeight, XmNmaxWidth, XmNmaxHeight, XmNwidthInc, XmNheightInc, XmNminAspectX, XmNminAspectY, XmNmaxAspectX, and XmNmaxAspectY resources.

Size increment hints are useful for applications that prefer to be in units of a particular number of pixels. Window managers that listen for this hint always resize the window to the base size (for each dimension), plus or minus an integral multiple of the size increment hint for that dimension. If the base size resource has not been set, the minimum size is used as the base. For example, xterm uses the font width and font height as width and height increment hints, because it prefers not to have partial characters or dead space around the edges. The bitmap editor application described in Chapter 4, An Example Application, should probably set both the width and height increments to the cell_size_in_pixels, since the bitmap cells are square.

Most applications that use size increment hints redefine the interpretation of geometry specifications (the XmNgeometry resource, settable through the -geometry standard command-line option) to reflect the size increments. For example, the width and height in xterm geometry specifications are in units of characters, not pixels. The Vt100 widget within xterm implements this by having an XmNgeometry resource separate from the shell geometry resource with that name. The entry for this resource in the widget resource list specifies the Vt100 instance structure field, not the shell instance structure field. Then the realize method for the Vt100 parses the geometry string with XParseGeometry() (an Xlib routine that returns four separate variables containing the size and position from the geometry string) and sets the width and height fields of the core structure and the various size hints according to the returned variables. (The Vt100 within xterm is not a real, self-sufficient widget. For one thing, it has no set_values method. For any real widget to implement this approach to interpreting geometry specifications, the set_values method would have to multiply the XmNwidth and XmNheight resource settings by the increment hints.)

An aspect ratio is the ratio of the width to height measurement or vice versa. The XmNminAspectX and XmNminAspectY resources are used together to determine one extreme of acceptable aspect ratios, and XmNmaxAspectX and XmNmaxAspectY determine the other extreme. For example, to suggest that the xmh application never be more than four times larger in one direction than it is in the other, the following values (as they would appear in the app-defaults file) would suffice:

xmh*topLevel.minAspectX:    1
xmh*topLevel.minAspectY:    1
xmh*topLevel.maxAspectX:    4
xmh*topLevel.maxAspectY:    4

Remember that every application must be able to do something reasonable given any size for its top-level window, even if the window is too small to be useful or if any of these hints are ignored by the window manager.

Input Model

Window managers also control which window keyboard input will be delivered to by setting the keyboard focus window (sometimes called just the keyboard focus) to an application's top-level window. The distribution of events according to the current keyboard focus is handled by the server; it is a basic feature of X, not of Xt. Some window managers, like twm, use the pointer-following (also called real-estate-driven) model of keyboard input; the window containing the pointer gets the keyboard input. Setting the keyboard focus once to the root window implements the pointer-following model. Other window managers use the click-to-type model, exemplified by the Macintosh™ user interface, requiring that the user click the pointer in the window where keyboard input is to go. The window manager sets the keyboard focus to the window the user clicks on. mwm lets the user select between these two focus models by setting a resource.

With some window managers other than mwm, how the window manager treats an application window can be modified by the input model hint, which is set by the XmNinput resource. Even though mwm doesn't use the value of this hint, Motif applications should set it in case they are used under a window manager that does use the hint.

If XmNinput is set to True, the window manager will set the keyboard focus to this application or not, according to its pointer-following or click-to-type model of keyboard input. However, if it is set to False, the window manager will not set the keyboard focus to this application. If the application sets this resource to False and wants input, it will have to forcefully take the keyboard focus, and then put it back to the original window when finished.

For historical (not logical) reasons, the Intrinsics default for the XmNinput resource is False. However, there is a special internal Shell widget class called VendorShell, which sets appropriate resources for a given widget set. The proper default for a given widget set (which usually has an accompanying window manager) is set by that widget set's VendorShell. The majority of applications need XmNinput set to True unless they don't require keyboard input (and expect never to have accelerators).

There are four models of client input handling defined by the ICCCM:

  • No Input. The client never expects keyboard input. xload is an example of such an output-only client. This type of client sets XmNinput to False.

  • Passive Input. The client expects keyboard input but never explicitly sets the input focus. This describes the vast majority of applications that always accept keyboard input in the window that contains the pointer. This type of client sets XmNinput to True.

  • Locally Active Input. The client expects keyboard input, and explicitly sets the keyboard focus, but only does so when one of its windows already has the focus. An example would be a client with subwindows defining various data entry fields that uses Next and Prev keys to move the keyboard focus between the fields, once its top-level window has received the keyboard focus. This type of client sets XmNinput to True.

  • Globally Active Input. The client expects keyboard input, and explicitly sets the input focus even when the focus is in windows the client does not own. An example would be a client with a scrollbar that wants to allow users to scroll the window without disturbing the keyboard focus even if it is in some other window. It wants to temporarily set and then reset the keyboard focus when the user clicks in the scrolled region, but not when the user clicks in the scrollbar itself. Thus, it wants to prevent the window manager from setting the keyboard focus to any of its windows. This type of client sets XmNinput to False.

Note that even if the XmNinput resource is not set to True, your application will still work under some window managers, including uwm, twm, and mwm. This is because these window managers use the pointer-following keyboard focus model or ignore this hint. However, it is not wise to assume that all window managers will ignore this hint. Therefore, if your application expects keyboard input, and is not of the globally active type described above, you can use the code shown in Example 11-1 to set the XmNinput resource.

Example 11-1. Setting the XmNinput resource of a Shell widget

main(argc, argv)
int argc;
char *argv[];
{
      .
      .
      .
    /* create the Shell widget, setting resource */
    topLevel = XtVaAppInitialize(&app_context, "Xmh",
            table, XtNumber(table),
            &argc, argv, NULL,
            XmNinput, (XtArgVal)True,
            NULL);
      .
      .
      .
}


Note that the XmNinput resource should always be hardcoded, since the application may fail if the user is allowed to change the expected style of keyboard focus.

For a further discussion of the keyboard focus, see Section 14.4, "The accept_focus Method and the Keyboard Focus."

Colormaps

On most color systems, the display uses one or more hardware registers called colormaps to store the mapping between pixel values and actual colors, which are specified as relative intensities of red, green, and blue primaries (RGB values).

X allows virtual colormaps to be created by applications. Some high-performance systems even allow all virtual colormaps to be installed in hardware colormaps and used at the same time, even to the level of one colormap per window. Far more commonly, though, there is only one hardware colormap, and virtual colormaps have to be copied into the hardware colormap one at a time as needed. Copying a virtual colormap into the hardware colormap is called installing the colormap, and the reverse process where the default colormap is installed is called uninstalling. The window manager is responsible for installing and uninstalling colormaps.

If your application has standard color needs (decoration only), then you do not have to worry about the effects of colormaps being installed and uninstalled. Your application should use the standard XmRString to XmRPixel converter to translate color names into pixel values. If the colormap is full, or becomes full at any of the color allocations in the converter, the warning messages place the burden on the user to kill some applications in order to free some colormap cells. (If the converter cannot allocate the desired color, it prints a warning message, and the default for that resource is used. If the default is not XtDefaultForeground or XtDefaultBackground, it also must be converted and this may also fail. If both allocations fail, the color will default to black. If this is not acceptable, then you will need to write your own type converter.)

If your application absolutely requires accurate colors, a certain number of distinguishable colors, or dynamically changeable colors, you will need to write your own converter to allocate colors. For example, if a color allocation for a known correct color name string fails, it means that all the colormap entries have already been allocated and no entry for that color is available. In this case, your converter might call the Xlib call XCopyColormapAndFree() to copy the allocations your application has already made into a new colormap. Then the converter would allocate the color (and all subsequent colors) from the new colormap.

See Chapter 7, Color, in Volume One, Xlib Programming Manual, for details of various color allocation techniques.

The window manager is responsible for installing and uninstalling colormaps according to its own policy. Typically, the policy is to install the colormap of the application that has the keyboard focus or that contains the pointer. When an application has created a new colormap on a system that supports only one hardware colormap, and that colormap is installed, all applications that were using the other colormap will be displayed using the new colormap. Since the pixel values from the old colormap have not been allocated in the new colormap, all applications that use the old colormap will be displayed with false colors. This is known as “going technicolor.”

The window manager or some other client may also create standard colormaps to facilitate the sharing of colors between applications. A standard colormap is a colormap allocated with a publicly known arrangement of colors. The Xlib routine XGetStandardColormap() allows an application to determine whether the window manager has created a specific standard colormap, and it gets a structure describing the colormap. Xmu provides numerous utilities for creating and using standard colormaps--see Volumes One and Two for a description.

If your application requires more than one colormap, each installed at the same time in a different window, you need to tell the window manager about this so that these colormaps can be properly installed when the application is active. You do this by calling XtSetWMColormapWindows(), specifying the ApplicationShell widget as the first argument, a list of widgets that need certain colormaps as the second argument, and the length of the list as the third argument. This instructs the window manager to read the colormap window attribute of each of the listed widgets, and to install that colormap in each widget (if possible) whenever the application is active. (Of course, any application that depends on the existence of multiple simultaneous colormaps is doomed to run on only a small percentage of existing systems.)

The colormap window attribute of a window is an unchangeable characteristic of that window, that is assigned when the window is created (when the widget is realized). The Core realize method sets it based on the XmNcolormap Core resource of the widget. Therefore, if a widget must have a certain colormap, you must set its XmNcolormap resource while creating the widget. Attempting to set XmNcolormap after a widget is created will have no effect on its colormap window attribute.

Icons

The window manager always manages the icons for each application. Depending on the window manager, these icons may simply contain a text string called the icon name, or they may contain a pattern that identifies the application, called the icon pixmap. The window manager is not obligated to display the application's icon pixmap (by default, mwm and twm do not display the icon pixmap). However, the window manager will always display at least the icon name.

An application may supply an icon name by setting the XmNiconName resource; if it does not, the window manager will usually use the application name or the value of the XmNtitle resource.

The application should supply the pattern for the icon, in the form of a single-plane pixmap, as the XmNiconPixmap resource. There are two basic ways to do this: one is by including bitmap data, and the other is by reading it in at run time. The former is easy to do with an Xlib call; the code that needs to be added is shown in Example 11-2. The latter technique, because it involves building a filename and looking in a number of locations for the file, is better done with a converter defined by the Xmu library. This converter technique is more complicated because the converter has to be registered and called with the proper arguments. The example using a converter is provided in the example source code for this book (xicon2), but is not shown here. See Chapter 10, Resource Management and Type Conversion, for more information on invoking converters.

Example 11-2. Creating an icon pixmap, and setting XmNiconPixmap

  .
  .
  .
/*
 * The following needed if not using Motif:
 *  #include <X11/Shell.h>
 */
#include "icon"
main(argc, argv)
int argc;
char **argv;
{
    Pixmap icon_pixmap;
      .
      .
      .
    /* create topLevel here */
    icon_pixmap = XCreateBitmapFromData(XtDisplay(topLevel),
            RootWindowOfScreen(XtScreen(topLevel)),
            icon_bits,
            icon_width, icon_height );
    XtVaSetValues(topLevel, XmNiconPixmap, icon_pixmap, NULL);
      .
      .
      .
    /* realize widgets */
}


The included file, icon, is in standard X11 bitmap file format. You can create such a file using the bitmap application from the standard distribution, or xbitmap5 from the examples for this book.

The window manager may have a preferred standard size for icons. If so, it will set a property. The application can read this property with the Xlib call XGetIconSizes(). To fully support a variety of window managers, an application should be capable of creating icon pixmaps of different sizes, depending on the values returned by XGetIconSizes().

An application has the option of creating its own icon window, and then passing its ID to the window manager for management. This might be done so that the application can draw a multi-colored picture in the icon, instead of the traditional two-color bitmap, or animate the icon. However, as with all hints, the window manager is not guaranteed to honor your desires and may just ignore the icon window you provide. But if you want to try anyway, create the window using Xlib calls, and select input on it so that your application receives Expose events when they occur. Then set the ID of the window using the XmNiconWindow resource, before realizing the application. Then make sure your event loop redraws the icon when it receives Expose events on it. Accomplishing this will require writing your own modified version of XtAppMainLoop() to handle the icon events.

Window Manager Decorations

Virtually all current window managers decorate windows on the screen.[73]These decorations typically include a title bar for the window with gizmos for moving and resizing the window. Current decorating window managers include twm, awm, mwm, olwm, and gwm.

The way these decorations are implemented can have an impact on Toolkit applications. The window manager places the decorations in a window slightly bigger than the application, and then reparents the application's top-level window into the decoration window. Reparenting gives the top-level window a new parent instead of the root window. The window manager actually creates the frame window as a child of the root window, then reparents the application's top-level window into the frame, and then maps the frame window.

Reparenting affects the application mainly when you try to determine a global position (relative to the root window) from the XmNx and XmNy resources of the Shell widget. This is usually done in order to place popups. Under a nonreparenting window manager such as uwm, these coordinates are indeed relative to the root window, because the parent of the shell is the root window. However, under a window manager that reparents, the coordinates of the application's main Shell widget are relative to the decoration window, not the root window. Therefore, these coordinates cannot be used for placing popups within the application. Motif's internal code that places a popup uses XtTranslateCoords() instead of relying on the position resources.

Interacting With the Motif Window Manager

The application and mwm can communicate back and forth through properties stored on the server. The properties are associated with the top-level window of your application, so unlike selections, similar communications can be going on at the same time between different clients and the window manager without interference. The ICCCM, Inter-Client Communication Conventions Manual, defines a starting point for what information can be sent back and forth.[74] mwm also defines some other information it is willing to send and receive.

All Shell widgets handle the required communication with the window manager; this section concerns optional communication. Note that applications should not depend on this type of communication. An application may run under a different window manager that doesn't listen to this type of message. Furthermore, an application is not required to use this type of communication, because mwm runs fine without it. This type of communication is just icing on the cake. (If you want to know whether mwm is running anyway, use the XmIsMotifWMRunning() function.)

The next three sections describe the two major groups of protocols and a set of supplementary window manager hints. The first protocol group is defined by the ICCCM, and the second by Motif. You must include the header file <X11/Protocols.h> to use any of these features.

Before you can use these features, you must also prepare the required atoms. The type Atom is an ID for the string name for a property. All applications know the name of the property they will use for communication (such as WM_PROTOCOLS), but they don't know the ID for it since it changes each time the server is run. You can get the Atom for a property name by calling XmInternAtom(). This is called interning the atom. All the fully capitalized words used in this section are property names, and must be interned before use.

Both of the following sets of protocols work in the same way. You indicate interest in participating in one or more of them by calling XmAddProtocols(). Then you register a function, using XmAddProtocolCallback(), to be called when the window manager sends you a message on a particular protocol. Of course, it is possible to resign from any of the protocols whenever you want.

WM_PROTOCOLS

The WM_PROTOCOLS are messages sent to your application from the window manager that notify you of conditions requiring immediate action. There are currently three protocols defined in the ICCCM, but the list may expand.

WM_DELETE_WINDOW indicates that the user has just requested that one of your application's windows be destroyed. Your application should respond by destroying that window if possible without exiting. The idea behind this protocol is that some applications have multiple top-level windows, and if the window manager were simply to delete one without the application's cooperation, the application might not be able to recover. (You should participate in this protocol if you have multiple top-level windows.)

WM_SAVE_YOURSELF indicates that the user has just requested that your application be killed. You should save all data immediately in preparation. This should execute code equivalent to the code you have implemented for your application's quit feature, except that it does everything except exit. (You should participate in this protocol if you have data that could be lost by exiting abnormally.)

WM_TAKE_FOCUS indicates that your application has been given the keyboard focus. You would participate in this protocol if you wanted the keyboard focus to always start in one of your subwindows, instead of your main window.

_MOTIF_WM_MESSAGES

The user can configure mwm, using an .mwmrc file, to add one or more buttons to its system menu and then call your application when any of these buttons are pressed. (If you want this feature in your application, though, you'll have to provide the proper text to be added to the user's .mwmrc.)

You indicate interest in this protocol by calling XmAddProtocols() with the atom for _MOTIF_WM_MESSAGES. The leading underscore is intentional; it indicates a property not defined in the ICCCM. When the user presses one of the system menu buttons, your application receives notification in the form of an ClientMessage event. Motif will automatically dispatch this ClientMessage event to a function, if you have registered one using XmAddProtocolCallback().

_MOTIF_WM_HINTS

The _MOTIF_WM_HINTS property is used in the opposite direction from the protocols described above. It is specified by the application and used by mwm if mwm is running. The property contains three hints about how the application would like to be treated: decorations, functions, and input_mode.

The value specified in the decorations field is combined (using AND) with the setting of the XmNclientDecoration shell resource. You can specify whether your application should have a border, resize button, title, window menu button, minimize button, or maximize button.

The functions field controls whether functions selected from mwm menus (that appear on the root window) should operate on your application. These operations consist of resizing, moving, minimizing, maximizing, and closing (exiting).

The input_mode field advises mwm to use various obscure focus management conventions when dealing with your application.

Note that mwm sets the _MOTIF_WM_INFO property on your application's main top-level shell. This property contains information about whether the mwm that is running has been customized (using .mwmrc) or if it is standard.

Selections: Widget-to-Widget Communication

Selections are a general mechanism for communicating information between two clients of the same server. The most familiar example of selections is selecting text from one xterm application and pasting it into another. This text can also be pasted into an xmh mail composition window, or into an xedit application.

In Xt applications, selection is normally implemented within widgets. For example, the Athena Text widget is used by xmh and xedit, and it is this widget that supports cutting and pasting of text in these applications. The application code of xmh and xedit does not play a part in the communication of selection data. The fact that selection is implemented in widgets also means that two widgets within the same application can communicate with each other through selections. For example, the Text widget is sometimes used to provide single-line input fields in an application. Since the Text widget supports selections, the user could select the text in one field and paste it into another field. This feature would be present without any code in the application. If the same widget class supports both copying and pasting, selections can be used to move data in a single widget. For example, selections allow you to copy text within an editor in xterm and paste the text in a new place (although some editors require keyboard commands to position the insertion point).

Motif provides a high-level interface to the selection mechanism that allows Motif applications or widgets to easily transfer data. See Section 11.3, "Motif Cut and Paste Functions and the Clipboard" for a description of this interface. The basic selection mechanism described in this section is valid for all widget sets, not just Motif.

The selection mechanism is not limited to text. It requires only that the sender and recipient have knowledge of the format of the data being transferred. Therefore, selections can be used to transfer graphics between widgets that can understand a common format for communicating graphics. Unfortunately, a standard format for graphics selections has not yet been agreed upon.

The selection mechanism uses properties for transferring data and uses the SelectionClear, SelectionNotify, and SelectionRequest event types to synchronize the communication. Essentially, a property is common storage. Properties are stored in the server, and are named pieces of data associated with a window on that particular server, that any client of that server can read or write. Because the various clients may not be running on the same host, all communication between applications and between applications and the window manager must take place through the server using properties.[75]

The basic routines used to implement selections are part of Xlib. Xt provides an interface to the Xlib selection routines that makes selections easier to use in the context of widget code. Since there is a maximum size for the data stored in a single property, communication of large blocks of data using Xlib requires several partial transfers. Xt supplies routines that transparently perform the multiple partial transfers so that they appear to the application program like a single transfer, called an atomic transfer.

Xt also supplies a separate set of parallel routines that transfer a large selection one chunk at a time so that it appears as multiple small selections. This is called an incremental transfer. Incremental transfer can be preferable on systems with limited memory and more natural when the data is normally stored in small chunks.

The Toolkit selection routines also have built-in timeouts, so that one application won't wait forever for another to provide data.

First we'll give you an overview of how an atomic selection transaction works, and then we'll discuss and demonstrate how to write the code to implement atomic selections in a widget. Following this is a discussion of how incremental selections work, and finally a discussion of the standard selection formats (known as target types).

If you are writing a custom widget that contains data that could be pasted into other instances of the widget or other widgets, you should read on to see how to implement selections. Otherwise, the rest of this chapter is probably only of academic interest to you.

How Atomic Selection Works

Selections communicate between an owner widget and a requestor widget.[76] The owner has the data and the requestor wants it. The owner is the widget in which the user has selected something, and the requestor is the widget in which the user has clicked to paste that something. Many widgets need to act as both owner and requester at different times. The code to handle each of the two roles is separate.

Here is a brief overview of the steps that take place during a single transfer of data from one widget to another. We'll assume we have two instances of the same widget class, which can operate either as the owner or the requestor. Initially, both widgets are in exactly the same state, neither having any text selected, and neither being the owner or requestor. We'll also assume that selections are implemented using actions tied to pointer buttons, as in existing widgets that use selections.

  • The user selects an item in Widget A, and the widget highlights the item. An action, typically called in response to a <Btn1Down> translation, marks the start of a selection. A subsequent <Btn1Motion> event (that is, a motion event with button 1 held down) invokes another action to extend the highlighted selection.

  • A <Btn1Up> event invokes a third action in Widget A that actually makes the selection. The action calls XtOwnSelection() to claim ownership of the PRIMARY selection for Widget A. This means that Widget A claims the sole right to use the XA_PRIMARY property for communication with other widgets, until some other widget claims ownership. (More about XA_PRIMARY below.) The call to XtOwnSelection() also registers three procedures, to be called by Xt in response to selection events. The lose_ownership_proc handles the case where Widget A loses the selection (because the user has made another selection elsewhere), the convert_proc converts the data in the selection to the target property type requested by Widget B (see below), and the optional transfer_done_proc prepares for the next selection request, if necessary (this function is often NULL).

  • The user pastes the item into Widget B, usually by clicking a pointer button. (By convention, a translation for <Btn2Down> invokes the action to do this.)

  • The paste action in Widget B requests the value of the current selection by calling XtGetSelectionValue(); this specifies a target type, and a requestor_callback that will be invoked to actually paste the data when Widget A reports that the conversion has been successfully completed.

  • The XtGetSelectionValue() call by Widget B also generates a SelectionRequest event. In response to this event, Xt invokes the convert_proc registered by the call to XtOwnSelection() in Widget A.

  • The convert_proc in Widget A converts the selected item into the appropriate data type for the target property specified by B, if possible. The converted data is stored in the XA_PRIMARY property.

  • Based on the return values from Widget A's conversion procedure, Xt sends a SelectionNotify event to inform Widget B whether or not the data was successfully converted.

  • The requestor_callback registered in Widget B reads the data from the property and displays it, or if Widget A reported that the conversion could not be made, Widget B beeps or otherwise indicates that the kind of data selected in Widget A cannot be pasted in Widget B, or that the kind of data requested by B cannot be supplied by A.

  • Xt notifies Widget A that the selection has been transferred, so that the widget's transfer_done_proc can disown the selection if the selection is of a kind that can be transferred only once.

Figure 11-1 shows this procedure in graphic form.

The code to implement selections is divided logically into a number of functions within the widget. The next sections show the code necessary to implement both the owner and the requestor roles. The code for each role is separate and cannot share any widget variables with the other role because the transaction may be between two different widget instances.

As an example, we will add support for selections to the BitmapEdit widget described in Chapter 6, Inside a Widget, and Chapter 7, Basic Widget Methods. Little of the existing code in that widget needs to be changed. The examples show only the code added to implement selections, and describe where to add it.

We will start with the owner role, and then proceed to the requestor role, and then back to the owner role.

Figure 11-1. The process of selection transfer


Highlighting the Selected Data (Owner)

The selection process begins when the user selects some text in Widget A. The widget code for the owner role must contain some code to identify the area or items selected and to highlight the text (or whatever) being selected. This user-interface portion of the owner role is carried out by actions added to the widget. Some event sequences that are not already in use must be mapped to these actions. In existing applications that support selections of text:

  • A press of the first pointer button pins a starting point of the selection, dragging the first button extends and highlights the selection, and releasing the first button ends the selection. A subsequent press of the first button starts a new selection, unhighlighting the old one.

  • A press of the second button pastes the selection.

  • A button press or motion with the third button depressed repositions either end of the selected area.

It is a good idea to stick with existing conventions for the user interface of highlighting a selection, so that your application will operate in a familiar way. But since BitmapEdit already uses all three pointer buttons, unmodified, to set, unset, and toggle bitmap cells, we cannot use unmodified button presses to control selections. But we can (and will) use these same buttons modified by Shift.

A selection in BitmapEdit is any rectangular set of bitmap cells. Accordingly, BitmapEdit will use Shift<Btn1Down> to set the top-left corner of a selection rectangle, Shift<Btn1Motion> to follow dragging, and Shift<Btn1Up> to set the bottom right corner.[77] Each of these events will invoke a separate action routine, StartHighlight, ExtendHighlight, and MakeSelection, respectively. These translations are added to the widget's default translation table, and the actions are added to the actions table. Example 11-3 shows the action table, the translation table, and the three action routines.

Example 11-3. BitmapEdit: actions that highlight selection

static char defaultTranslations[] =
        "Shift<Btn1Down>:   StartHighlight()    \n\
        Shift<Btn1Motion>:  ExtendHighlight()   \n\
        Shift<Btn1Up>:      MakeSelection()     \n\
        Shift<Btn2Down>:    PasteSelection()    \n\
        ~Shift<Btn1Down>:   DoCell()            \n\
        ~Shift<Btn2Down>:   UndoCell()          \n\
        ~Shift<Btn3Down>:   ToggleCell()        \n\
        ~Shift<Btn1Motion>: DoCell()            \n\
        ~Shift<Btn2Motion>: UndoCell()          \n\
        ~Shift<Btn3Motion>: ToggleCell()";
static XtActionsRec actions[] = {
    {"DoCell", DoCell},
    {"UndoCell", UndoCell},
    {"ToggleCell", ToggleCell},
    {"StartHighlight", StartHighlight},
    {"ExtendHighlight", ExtendHighlight},
    {"MakeSelection", MakeSelection},
    {"PasteSelection", PasteSelection},
};
  .
  .
  .
/*
 *  User presses first button (by default), starting highlighting.
 */
static void
StartHighlight(w, event)
Widget w;
XButtonEvent *event;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    cw->bitmapEdit.first_box = False;
    cw->bitmapEdit.select_start_x = (cw->bitmapEdit.cur_x + event->x)
            / cw->bitmapEdit.cell_size_in_pixels;
    cw->bitmapEdit.select_start_y = (cw->bitmapEdit.cur_y + event->y)
            / cw->bitmapEdit.cell_size_in_pixels;
    /* clear old selection */
    Redisplay(cw, NULL);
}
/*
 * MakeSelection is call when the first button is released (by
 * default).  This finishes the user's highlighting, and means
 * triggers ownership of the PRIMARY selection.
 */
static void
MakeSelection(w, event)
Widget w;
XButtonEvent *event;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    int temp;
    cw->bitmapEdit.select_end_x = (cw->bitmapEdit.cur_x + event->x)
            / cw->bitmapEdit.cell_size_in_pixels;
    cw->bitmapEdit.select_end_y = (cw->bitmapEdit.cur_y + event->y)
            / cw->bitmapEdit.cell_size_in_pixels;
    if ((cw->bitmapEdit.select_end_x == cw->bitmapEdit.select_start_x)
            && (cw->bitmapEdit.select_end_y ==
            cw->bitmapEdit.select_start_y))  {
        Redisplay(cw, NULL);
        return; /* no selection */
    }
    /* swap start and end if end is greater than start */
    if (cw->bitmapEdit.select_end_x < cw->bitmapEdit.select_start_x) {
        temp = cw->bitmapEdit.select_end_x;
        cw->bitmapEdit.select_end_x = cw->bitmapEdit.select_start_x;
        cw->bitmapEdit.select_start_x = temp;
    }
    if (cw->bitmapEdit.select_end_y
            < cw->bitmapEdit.select_start_y) {
        temp = cw->bitmapEdit.select_end_y;
        cw->bitmapEdit.select_end_y =
                cw->bitmapEdit.select_start_y;
        cw->bitmapEdit.select_start_y = temp;
    }
    if (XtOwnSelection(cw, XA_PRIMARY, event->time, convert_proc,
            lose_ownership_proc, transfer_done_proc) ==
            False) {
        XtWarning("bitmapEdit: failed attempting to become
                selection owner; make a new selection. \n");
        /* Clear old selection, because lose_ownership_proc
         * isn't registered. */
        Redisplay(cw, NULL);
    }
}
/*
 * ExtendHighlight is called when the mouse is being dragged with
 * the first button down (by default).  During this time, the
 * bitmap cells that are selected (by crosses) are dynamically
 * changed as the mouse moves.
 */
static void
ExtendHighlight(w, event)
Widget w;
XMotionEvent *event;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    static int last_drawn_x, last_drawn_y;
    int event_cell_x, event_cell_y;
    event_cell_x = cw->bitmapEdit.cur_x + (event->x /
            cw->bitmapEdit.cell_size_in_pixels);
    event_cell_y = cw->bitmapEdit.cur_y + (event->y /
            cw->bitmapEdit.cell_size_in_pixels);
    if ((event_cell_x == last_drawn_x) && (event_cell_y ==
            last_drawn_y))
        return;
    if (cw->bitmapEdit.first_box) {
        DrawBoxOfXs(cw, last_drawn_x, last_drawn_y, False);
        DrawBoxOfXs(cw, event_cell_x, event_cell_y, True);
    }
    else {
        DrawBoxOfXs(cw, event_cell_x, event_cell_y, True);
        cw->bitmapEdit.first_box = True;
    }
    last_drawn_x = event_cell_x;
    last_drawn_y = event_cell_y;
}
/*
 *  DrawBoxOfXs fills a rectangular set of bitmap cells with
 *  crosses, or erases them.
 */
static void
DrawBoxOfXs(w, x, y, draw)
Widget w;
Position x, y;
Bool draw;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    Position start_pos_x, start_pos_y;
    Dimension width, height;
    GC gc;
    int i, j;
    start_pos_x = cw->bitmapEdit.cur_x +
            cw->bitmapEdit.select_start_x;
    start_pos_y = cw->bitmapEdit.cur_x +
            cw->bitmapEdit.select_start_y;
    /* swap start and end if end is greater than start */
    if (x < start_pos_x) {
        width = start_pos_x - x;
        start_pos_x = x;
    }
    else {
        width = x - start_pos_x;
    }
    if (y < start_pos_y) {
        height = start_pos_y - y;
        start_pos_y = y;
    }
    else {
        height = y - start_pos_y;
    }
    for (i=start_pos_x;i < start_pos_x + width;i++)
         for (j=start_pos_y;j < start_pos_y + height;j++)
              DrawX(cw, i, j, draw);
}
/*
 * DrawX draws an X in a bitmap cell, in white if a black cell, and
 * in black if a white cell.
 */
DrawX(cw, x, y, draw)
BitmapEditWidget cw;
Position x, y;
Bool draw;
{
    GC gc;
    if (cw->bitmapEdit.cell[x + y *
            cw->bitmapEdit.pixmap_width_in_cells] == DRAWN)
        if (draw)
            gc = cw->bitmapEdit.deep_undraw_gc;
        else
            gc = cw->bitmapEdit.deep_draw_gc;
    else
        if (draw)
            gc = cw->bitmapEdit.deep_draw_gc;
        else
            gc = cw->bitmapEdit.deep_undraw_gc;
    XDrawLine(XtDisplay(cw), XtWindow(cw), gc,
            x * cw->bitmapEdit.cell_size_in_pixels,
            y * cw->bitmapEdit.cell_size_in_pixels,
            (x + 1) * cw->bitmapEdit.cell_size_in_pixels,
            (y + 1) * cw->bitmapEdit.cell_size_in_pixels);
    XDrawLine(XtDisplay(cw), XtWindow(cw), gc,
            x * cw->bitmapEdit.cell_size_in_pixels,
            (y + 1) * cw->bitmapEdit.cell_size_in_pixels,
            (x + 1) * cw->bitmapEdit.cell_size_in_pixels,
            y * cw->bitmapEdit.cell_size_in_pixels);
}


The actions shown in Example 11-3 reference some new instance part fields we have added to BitmapEditP.h to store the state of the selection. Four of these variables are the x and y coordinates in cells of the top-left and bottom-right corners of the highlighted area: select_start_x, select_start_y, select_end_x, and select_end_y. These coordinates will be used throughout the owner code. (Because these fields are in units of bitmap cells, not pixels, their type is int rather than Position.)

The StartHighlight action is quite simple. It sets the upper-left corner instance part fields based on the button press position in the triggering event. It also clears the highlighting of the old selection, if one exists, and sets a flag to indicate that this is the beginning of a new selection. The first_box flag is added as an instance part field because it will be needed in the ExtendHighlight action.

ExtendHighlight is responsible for drawing Xs dynamically on the selected cells, much as the window manager draws the outline of a window while it is being moved. Xs are drawn on each cell in a rectangle of bitmap cells beginning from the cell selected in the StartHighlight action and ending at the cell the pointer button was released in. ExtendHighlight is triggered by pointer motion events that occur while the first button is held down. It calculates which bitmap cell the pointer is in, and if the cell is not the same as the cell the pointer was in the last time ExtendHighlight was called, it erases the previous Xs and draws new ones. This is done using two different GCs--one for drawing in black and the other in white, so the color that contrasts with the current state of each cell can be used. The black GC is also used to copy the main pixmap to the screen, and the latter is just for doing the highlighting.

Code was added to the initialize method to create this latter GC. (See the example code distribution for this modified widget instance structure.)

The MakeSelection action is triggered when the button is released after dragging, indicating that the selection is complete. This action sets the select_end_x and select_end_y instance part variables to reflect the bottom-right corner of the selection, and then swaps the top-left and bottom-right coordinates if the end coordinate is to the left or above the start coordinate. If the start and end coordinates are the same, then the action returns, since no selection was made. If a selection was made, MakeSelection calls XtOwnSelection().

Making the Selection with XtOwnSelection (Owner)

Once the area is highlighted, Widget A calls XtOwnSelection() to assert that it wants the right to set the value of a property that will be used to transfer information.

XtOwnSelection() is called with six arguments:

Boolean XtOwnSelection(widget, selection, time, convert_proc,
        lose_ownership_proc, transfer_done_proc)
    Widget widget;
    Atom selection;
    Time time;
    XtConvertSelectionProc convert_proc;
    XtLoseSelectionProc lose_ownership_proc;
    XtSelectionDoneProc transfer_done_proc;

The selection argument specifies an Atom--a number representing a property. Properties are arbitrarily named pieces of data stored on the server. To simplify communication with the server, a property is never referenced by name, but by a unique integer ID called an atom. Standard atoms are defined in <Xatom.h> using defined symbols beginning with XA_; nonstandard atoms can be obtained from the server by calling the Xlib function XtInternAtom.

Widgets that support one selection at a time pass the predefined XA_PRIMARY atom to XtOwnSelection(). The ICCCM also allows you to use the XA_SECONDARY and XA_CLIPBOARD atoms. XA_SECONDARY would be used by widgets implementing behavior involving more than one selection (for example, allowing the user to make two selections, and to exchange the data they contain). No current clients implement this behavior. The XA_CLIPBOARD atom should be used by a widget that allows the user to delete a selection. We'll talk more about the properties referenced by the XA_SECONDARY and XA_CLIPBOARD atoms in Section 11.3, "Motif Cut and Paste Functions and the Clipboard."

The purpose of the XtOwnSelection() call is to make sure that only one widget has the right to set the XA_PRIMARY selection property at a time, and to assert that this widget is prepared to honor requests for this data. Notice that when you select text with xterm, that text is highlighted only until you select a different area in a different window.

The next three arguments to XtOwnSelection() specify procedures that will carry out essential parts of the selection operation:

  • The convert_proc is responsible for converting the selected data into the representation requested by the requestor in its call to XtGetSelectionValue(); this function is called by Xt when a SelectionRequest event arrives, as a result of the requestor calling XtGetSelectionValue(). The convert_proc is of type XtConvertSelectionProc.

  • The lose_ownership_proc clears the highlighted area when this widget has lost the selection (because some other widget has taken it); this routine is called by Xt when a SelectionClear event arrives. The lose_ownership_proc is of type XtLoseSelectionProc.

  • The transfer_done_proc is called by Xt when the requestor has successfully retrieved the converted value. If this selection is intended to be erased after being transferred once, this function should free any storage allocated in the transfer. If, instead, the owner remains ready for pasting the same selection into other widgets, this function pointer should be NULL. (For example, xterm does not clear its selection after pasting once.) The transfer_done_proc is of type XtSelectionDoneProc.

The XtOwnSelection() call also takes a time argument--this should be the time from the event that completed the highlighting, not the constant CurrentTime. If XtOwnSelection() returns False, it means that because of network conditions another client has called XtOwnSelection() with a more recent time argument.

XtOwnSelection() returns True or False to indicate whether Widget A has been granted ownership. If True, and if this widget was not already the owner, then the old owner (if any) will receive a SelectionClear event and will clear its highlighted area. The process may end here if the user never pastes the selected data anywhere. If the user selects a different piece of data, the selection owner will receive a SelectionClear itself before ever having converted the data for a requestor.

Otherwise the owner code waits to hear from the requestor that it is ready to paste the selection.

Requesting the Selection (Requestor)

When the user pastes the selection into Widget B, Widget B becomes the requestor and the second part of the process begins. First of all, Widget B needs a translation that maps a certain key or button event to an action that pastes data. This action calls XtGetSelectionValue().

XtGetSelectionValue() is called with six arguments:

    void XtGetSelectionValue (widget, selection, target, callback,
        client_data, time)
    Widget widget;
    Atom selection;
    Atom target;
    XtSelectionCallbackProc callback;
    XtPointer client_data;
    Time time;

The selection argument is an atom specifying which property is being used to transfer the selection. This will typically be XA_PRIMARY, though in theory two widgets (or two instances of the same widget) could agree to transfer some particular data using other properties.

The target argument is another atom, this one specifying a target representation type in which the requestor wants the information. We'll talk more about the possible values for this atom in a moment.

The callback argument is a pointer to a callback procedure that actually pastes the data. Xt will call this procedure when the owner has converted the data. The XtGetSelectionValue() call registers the callback with Xt, and sends a SelectionRequest event to the owner.

When the selection owner has successfully converted the data, the owner sends back a SelectionNotify event to Xt. Xt then calls the requestor's callback procedure. The callback procedure must handle the pasting of the data into the widget's data structures, and it must handle the case where the data could not be converted into the requested representation. We'll discuss the responsibilities of this procedure in more detail once we've seen the owner's conversion procedure.

The client_data argument of XGetSelectionValue() is normally used to pass the event that triggered the pasting into the requestor callback. The callback will use the coordinates at which the event occurred as the location at which to paste the data.

As in the call to XtOwnSelection(), the time argument should be the time from the event that initiated the pasting, not the constant CurrentTime.

Possible Target Type Atoms

The target argument to XtGetSelectionValue() is an atom that the requestor uses to tell the owner what kind of information it is looking for from the selection.[78]This is not necessarily a conversion of the actual selection data. For example, it might be a timestamp on the data, or some characteristic of it, such as its size or font.

To take full advantage of the Xt selection mechanism, you need to understand what atoms can be used as selection targets. However, apart from some standard, predefined atoms, the atom for a property is not known by a client until it queries the server for the atom using an XInternAtom() call. This call specifies the string name of the property and returns the atom.

Some atoms needed by almost all applications are predefined in the header file <X11/Xatom.h>. These atoms are symbolic constants that can be used without an XInternAtom() call. The constants always start with the prefix XA_. See <X11/Xatom.h> for the complete list of predefined atoms.

Note that many of the predefined atoms have uses in X other than as target types, and not all are appropriate as selection targets. A few of those that might be useful as targets include:

XA_STRING
XA_INTEGER
XA_BITMAP

At present, the Athena widgets and the clients in the MIT core X distribution use only XA_STRING as a target. However, one can imagine uses for other targets as well. For example, XA_FONT might be used as a target to indicate that the requestor doesn't want the text of a selection, but wants only to know its font. XA_TIMESTAMP might be used to indicate that the requestor wants a timestamp for the data. Once the XtGetSelectionValue() request is made, a SelectionRequest event is generated, which Xt uses to invoke the owner widget's convert_proc. The convert_proc will need to branch according to the target type passed in by the requestor through Xt, and convert the data accordingly before transferring the selection to the requestor.

For selection of multiple data types to work correctly between any two arbitrarily chosen widgets, there must be conventions about which targets will be supported. As a step in the direction of interwidget selection compatibility, the ICCCM specifies a required target type of XA_TARGETS, to which the owner is supposed to respond by returning a list of the target types into which it is capable of converting data. We'll talk about how to work with this target in Section 11.2.9, "ICCCM Compliance."

In a custom widget such as BitmapEdit, which has a custom data type not represented by a predefined atom, it is possible to obtain a custom atom using XInternAtom(), and use that as the target. Because the XInternAtom() call requires a round trip to the server, the best place to do this is in the widget's initialize method.[79] The returned atom can be stored in a field added to the widget's instance part. BitmapEdit's target type, “CELL_ARRAY,” is unique to BitmapEdit. The atom is stored in an instance part field called target so that it is available in convert_proc and in the requestor role code. Example 11-4 shows the code added to the initialize method to get an atom for the string “CELL_ARRAY.”

Example 11-4. BitmapEdit: getting the atom for a widget-specific target type

/* ARGSUSED */
static void
Initialize(request, new)
BitmapEditWidget request, new;
{
      .
      .
      .
    new->bitmapEdit.target_atom = XInternAtom(XtDisplay(new),
            "CELL_ARRAY," False);


The target type property name used by BitmapEdit is “CELL_ARRAY.”

The third argument to XInternAtom() is a Boolean that indicates what to do if the atom doesn't already exist. If this argument is True, XInternAtom() will return None if no other client has already initialized this atom. This argument should always be False since your widget might be the first to try to make a selection using this target type atom. In a Motif application, one could use XmInternAtom() instead of XInternAtom().

Note that for repeated calls to XInternAtom() with the same string as an argument, even from different widgets, the returned atom will be the same. There will be only one atom created for any unique string interned on a given server. (Case is important: “Cell_Array” would return a different atom than “CELL_ARRAY”.)

This XInternAtom() call occurs in every instance of the BitmapEdit widget, since all instances need to know the atom to participate in selections using that target type. Both the owner and requestor widgets, in separate applications, will get the same atom in return.

The Paste Action from BitmapEdit

To initiate the requestor role, you need to assign an event sequence to trigger the pasting, write an action that calls XtGetSelectionValue(), and write a callback function that inserts the returned data.

Traditionally, the action to paste data is triggered by a press of the second pointer button. BitmapEdit will use the shifted second button since it uses the unshifted button for other purposes. Example 11-5 shows the action mapped to Shift<Btn2Down>.

Example 11-5. BitmapEdit: action to paste a selection

static void
PasteSelection(w, event)
Widget w;
XButtonEvent *event;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    XtGetSelectionValue(cw, XA_PRIMARY, cw->bitmapEdit.target_atom,
            requestor_callback, event,
            event->time);
}

This action can be dropped verbatim into your widget, replacing only the widget name and the name of the instance fields you are using to store the target atom.

Converting the Selection (Owner)

The real challenge in handling selections is writing the convert_proc, which converts data to the format specified by the requestor and prepares it for transfer.

As mentioned above, the widget's convert_proc is called by Xt when the requestor calls XtGetSelectionValue(). The procedure is passed the selection atom and the target atom, and is expected to return in its arguments the type, value, size, and format of the converted selection data.

To support the possibility of having to convert the data to any one of several targets, the code branches according to the value of the target passed in, and does its conversions accordingly. In the version of BitmapEdit's convert_proc shown here, only one target type is handled. Section 11.2.9, "ICCCM Compliance" describes how to add more target types, including some that are required by the ICCCM such as TARGETS.

Once the conversion is made, the procedure sets the value_return argument passed in to a pointer to a block of memory, and length_return to the size of the block. This block of memory will be set into a property by Xt and passed to the requestor's callback in the same form--as a pointer to a block of memory and a length. This puts constraints on the formats that can be used for the data.

For text selections, the data is usually a simple character string. The convert_proc simply needs to set *value_return to a pointer to the string, and length_return to the length of the string. For BitmapEdit, however, the required data is a string (of bitmap cell states) plus width and height values. Since C provides easy ways to put numeric values into strings and to get them out again at the other end, we've chosen to handle this data by converting the numbers to characters and tacking them on to the beginning of the string.

If your selection is composed of a number of numeric values, you can create a structure containing the values and then pass a pointer to the structure. However, the structure cannot contain pointers, because the data pointed to by these pointers will not be set into the selection property. For example, BitmapEdit cannot pass a pointer to a structure containing a string pointer field and width and height fields because the block of memory pointed to by the string pointer will not be copied.

In this case, the type_return can simply be the target atom that was passed in, indicating that the data is of the requested type.

The format_return is the size in bits of each element in the array. Since we are passing a compound string, this value is 8. If we were passing a structure, length_return would be 1 and *format_return would be 8 times the size of the structure in bytes.

Example 11-6 shows BitmapEdit's convert_proc.

Example 11-6. BitmapEdit: converting the selection value

static Boolean
convert_proc(w, selection, target, type_return, value_return,
        length_return, format_return)
Widget w;
Atom *selection;
Atom *target;
Atom *type_return;
XtPointer *value_return;
unsigned long *length_return;
int *format_return;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    int x, y;
    int width, height;
    /* handle all required atoms, and the one that we use */
    /* Xt already handles MULTIPLE, no branch necessary */
    if (*target == XA_TARGETS(XtDisplay(cw))) {
        /* Required atom: see Example 10-10 */
          .
          .
          .
    }
    else if (*target == cw->bitmapEdit.target_atom) {
        char *data;
        width = cw->bitmapEdit.select_end_x -
                cw->bitmapEdit.select_start_x;
        height = cw->bitmapEdit.select_end_y -
                cw->bitmapEdit.select_start_y;
                /* 8 chars is enough for two 3-digit
                 * numbers and two delimiters */
        *length_return = ((width * height) + 8) * sizeof(char);
        data = XtMalloc(*length_return);
        sprintf(data, "%d@%d~", width, height);
        for (x = 0; x < width; x++) {
            for (y = 0; y < height; y++) {
                data[8 + x + (y * width)] = cw->bitmapEdit.cell[(x +
                        cw->bitmapEdit.select_start_x) +
                        ((y + cw->bitmapEdit.select_start_y) *
                        cw->bitmapEdit.pixmap_width_in_cells)];
            }
        }
        *value_return = data;
        *type_return = cw->bitmapEdit.target_atom;
        *format_return = 8;  /* number of bits in char */
        return(True);
    }
    else {
        /* code to handle standard selections: see Example 10-10 */
          .
          .
          .
    }
}


This code determines the width and height of the selected rectangle from the instance part fields, and then allocates enough memory to fit a character array big enough to fit the width and height values, delimiters, and a width by height character array. Then it copies the current contents of the selected area into the allocated character array. Finally, it sets *value_return to point to the compound string, and sets *length_return to the length of this string. *format_return is the size in bits of each element in the array, which in this case is 8 bits.

Finally Pasting the Selection (Requestor)

When the owner's convert_proc returns, Xt sends a SelectionNotify event to the requestor. The Xt code on the requestor side then invokes the callback routine the requestor registered with the call to XtGetSelectionValue().

The requestor_callback function is passed all the same arguments that the owner received in the convert_proc, plus the values that the owner returned through the argument of convert_proc. In the BitmapEdit widget, it sets instance part fields to paste the data.

Example 11-7 shows the requestor callback function from BitmapEdit.

Example 11-7. BitmapEdit: pasting selection in requestor_callback function

/* ARGSUSED */
static void
requestor_callback(w,client_data,selection,type,value,length,format)
Widget w;
XtPointer client_data;  /* cast to XButtonEvent below */
Atom *selection;
Atom *type;
XtPointer value;
unsigned long *length;
int *format;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    if ((*value == NULL) && (*length == 0)) {
        XBell(XtDisplay(cw), 100);
        XtWarning("bitmapEdit: no selection or selection timed out:\
               try again\n");
    }
    else {
        XButtonEvent *event = (XButtonEvent *) client_data;
        int width, height;
        int x, y;
        int dst_offset_x, dst_offset_y;
        char *ptr;
        dst_offset_x = (cw->bitmapEdit.cur_x + event->x) /
                cw->bitmapEdit.cell_size_in_pixels;
        dst_offset_y = (cw->bitmapEdit.cur_y + event->y) /
                cw->bitmapEdit.cell_size_in_pixels;
        printf("dst offset is %d, %d, dst_offset_x, dst_offset_y);
        ptr = (char *) value;
        width = atoi(ptr);
        ptr = index(ptr, '@');
        ptr++;
        height = atoi(ptr);
        ptr = &value[8];
        for (x = 0; x < width; x++) {
            for (y = 0; y < height; y++) {
                /* range checking */
                if (((dst_offset_x + x) >
                        cw->bitmapEdit.pixmap_width_in_cells)
                        || ((dst_offset_x + x) < 0))
                    break;
                if (((dst_offset_y + y) >
                        cw->bitmapEdit.pixmap_height_in_cells)
                        || ((dst_offset_y + y) < 0))
                    break;
                cw->bitmapEdit.cell[(dst_offset_x + x)
                        + ((dst_offset_y + y) *
                        cw->bitmapEdit.pixmap_width_in_cells)]
                        = ptr[x + (y * width)];
                if (cw->bitmapEdit.cell[(dst_offset_x + x)
                        + ((dst_offset_y + y) *
                        cw->bitmapEdit.pixmap_width_in_cells)]
                        == DRAWN)
                    DrawCell(cw, dst_offset_x + x,
                            dst_offset_y + y,
                            cw->bitmapEdit.draw_gc);
                else
                    DrawCell(cw, dst_offset_x + x,
                            dst_offset_y + y,
                            cw->bitmapEdit.undraw_gc);
            }
        }
        /* Regardless of the presence of a
         * transfer_done_proc in the owner,
         * the requestor must free the data passed by
         * Xt after using it. */
        XtFree(value);
        Redisplay(cw, NULL);
    }
}


The requestor_callback first determines whether the conversion was a success by checking the value and data length. If it was not, it beeps and prints a message. This can happen if no selection has been made, or if there is some delay that causes the selection to have timed out before the owner could convert the data.

If the conversion was a success, the requestor_callback pastes the data. In BitmapEdit's case, the requestor must first convert the data into a more useful form. This means extracting the width and height values, and then setting the widget's cell array based on the data in the character array passed in. Note that the code should check to make sure that the data can be pasted in the desired position. BitmapEdit must make sure that no attempt is made to set a cell array member outside of the bitmap. The routine then updates the screen display of the widget.

The final responsibility of the requestor_callback is to free the memory passed in by Xt.

If the Selection is Lost (Owner)

If the owner loses the selection, either because the selection timed out or because the user made a different selection, the lose_ownership_proc that was registered with its call to XtOwnSelection() will be invoked. Typically, this function simply clears any highlighting or other visual feedback about the selection, and resets to their initial state any internal variables used in handling selections.

Example 11-8 shows the lose_ownership_proc from the BitmapEdit widget.

Example 11-8. BitmapEdit: the lose_ownership_proc

/* ARGSUSED */
static void
lose_ownership_proc(w, selection)
Widget w;
Atom *selection;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    /* clear old selection */
    cw->bitmapEdit.first_box = False;
    cw->bitmapEdit.select_start_x = 0;
    cw->bitmapEdit.select_start_y = 0;
    cw->bitmapEdit.select_end_x = 0;
    cw->bitmapEdit.select_end_y = 0;
    Redisplay(cw, NULL);
}


When the Selection Transfer is Complete (Owner)

The transfer_done_proc, registered in the call to XtOwnSelection(), is called when the transfer is complete. Its job is to do any processing necessary to get ready for the next selection request. In many cases no processing is necessary and this function can be NULL in the call to XtOwnSelection(). However, this function might clear variables or free memory. Some transfers are intended to be made only once to make sure that there is no duplication of information. In these cases, the transfer_done_proc would do a lot more, including erasing the visual selection, calling XtDisownSelection() (and perhaps even erasing the data that was selected).

ICCCM Compliance

For any two widgets to be able to transparently transfer different types of data, there must be agreement about the possible target types and their contents. The ICCCM suggests a list of possible target types, as shown in Table 11-2. If you can, use one of these target types since this will increase the chances that your widget will be able to communicate with widgets written by other people.

Table 11-2. Target Types Suggested in ICCCM

Atom

Type

Meaning

TARGETS

ATOM

List of valid target atoms

MULTIPLE

ATOM_PAIR

Look in the ConvertSelection property

TIMESTAMP

INTEGER

Timestamp used to acquire selection

STRING

STRING

ISO Latin 1 (+TAB+NEWLINE) text

TEXT

TEXT

Text in owner's encoding

LIST_LENGTH

INTEGER

Number of disjoint parts of selection

PIXMAP

DRAWABLE

Pixmap ID

DRAWABLE

DRAWABLE

Drawable ID

BITMAP

BITMAP

Bitmap ID

FOREGROUND

PIXEL

Pixel value

BACKGROUND

PIXEL

Pixel value

COLORMAP

COLORMAP

Colormap ID

ODIF

TEXT

ISO Office Document Interchange Format

OWNER_OS

TEXT

Operating system of owner

FILE_NAME

TEXT

Full path name of a file

HOST_NAME

TEXT

See WM_CLIENT_MACHINE

CHARACTER_POSITION

SPAN

Start and end of selection in bytes

LINE_NUMBER

SPAN

Start and end line numbers

COLUMN_NUMBER

SPAN

Start and end column numbers

LENGTH

INTEGER

Number of bytes in selection

USER

TEXT

Name of user running owner

PROCEDURE

TEXT

Name of selected procedure

MODULE

TEXT

Name of selected module

PROCESS

INTEGER, TEXT

Process ID of owner

TASK

INTEGER, TEXT

Task ID of owner

CLASS

TEXT

Class of owner--see WM_CLASS

NAME

TEXT

Name of owner--see WM_NAME

CLIENT_WINDOW

WINDOW

Top-level window of owner

DELETE

NULL

True if owner deleted selection

INSERT_SELECTION

NULL

Insert specified selection

INSERT_PROPERTY

NULL

Insert specified property


Because not every widget will support every possible target type, the ICCCM specifies a target type of XA_TARGETS, to which the owner is required to respond by returning a list of the target types into which it is capable of converting data.

Normally, a requestor would first call XtGetSelectionValue() for XA_TARGETS, and then in the callback determine which target it wants to request from the list, and then call XtGetSelectionValue() again for the desired target with a separate callback to process the actual data. This is really two separate selection transfers.

XA_TARGETS is not a predefined atom.[80] To use it, you must use the Xmu atom caching mechanism described in Section 11.2.9.1, "Xmu Atom Caching."

Fortunately, there is existing template code that you can copy to handle XA_TARGETS and some other standard target types that are required by the ICCCM.

This template code uses the Xmu routine XmuConvertStandardSelection. It also uses an Xmu atom-caching facility that eliminates the need for you to make XInternAtom() calls for each of the ICCCM standard target atoms.

Xmu Atom Caching

In a Motif application, you can use XmInternAtom() to cache atoms, so that when there are multiple instances of the same widget, only one query of the server will be done. However, Xmu provides an atom caching system that is somewhat more convenient for the atoms it supports.

Xmu's caching facility uses symbols similar to those defined in <X11/Xatom.h>, except that in this case they are macros that take an argument and call XmuInternAtom. For example, the macro for XA_TARGETS is defined as follows in <X11/Xmu.h>:

#define XA_TARGETS(d)        XmuInternAtom(d, _XA_TARGETS)

where d refers to a pointer to a Display structure. (This can be returned by the XtDisplay() macro.) The XmuInternAtom function first tries to get the atom for the string “XA_TARGETS” from Xmu's internal cache. If Xmu doesn't yet have a value for the atom, it calls XInternAtom() to make a server request. Because this facility makes only one query to the server, you can access the atoms in this way every time a selection is made without significant penalty. This allows you to place the XA_TARGETS() macro in your selection code instead of adding an instance part variable and setting it in the initialize method.

You might use this macro as follows in a convert_proc branch dedicated to handling the TARGETS target type:

if (*target == XA_TARGETS(XtDisplay(w))) { ...

The Xmu atom caching mechanism must be initialized before you can make calls of the form just shown. Example 11-9 shows the code that should be placed in the widget's initialize method to initialize this mechanism.

Example 11-9. BitmapEdit: initializing Xmu's atom caching mechanism in the initialize method

(void) XmuInternAtom( XtDisplay(new), XmuMakeAtom("NULL") );


Converting the Standard Selections

The Xmu routine XmuConvertStandardSelection can be used to respond to a TARGETS selection request, as well as to other standard targets defined by Xmu.

Example 11-10 shows the portion of the convert_proc for BitmapEdit that handles the standard targets.

This code is adapted from the standard client xclipboard, and can be copied almost directly into your widget.

Example 11-10. BitmapEdit: converting standard targets in the convert_proc

static Boolean
convert_proc(w, selection, target, type_return, value_return,
        length_return, format_return)
Widget w;
Atom *selection;
Atom *target;
Atom *type_return;
XtPointer *value_return;
unsigned long *length_return;
int *format_return;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    int x, y;
    int width, height;
    XSelectionRequestEvent* req = XtGetSelectionRequest(w,
            *selection, (XtRequestId) NULL);
    /* handle all required atoms, and the one that we use */
    if (*target == XA_TARGETS(XtDisplay(cw))) {
        /* TARGETS handling copied from xclipboard.c */
        Atom* targetP;
        Atom* std_targets;
        unsigned long std_length;
        XmuConvertStandardSelection(cw, req->time, selection,
                target, type_return,
                (XtPointer*)&std_targets,
                &std_length, format_return);
        *value_return = XtMalloc(sizeof(Atom)*(std_length + 1));
        targetP = *(Atom**)value_return;
        *length_return = std_length + 1;
        *targetP++ = cw->bitmapEdit.target_atom;
        bcopy((char*)std_targets, (char*)targetP,
                sizeof(Atom)*std_length);
        XtFree((char*)std_targets);
        *type_return = XA_ATOM;
        *format_return = sizeof(Atom) * 8;
        return(True);
    }
    /* Xt already handles MULTIPLE, no branch necessary */
    else if (*target == cw->bitmapEdit.target_atom) {
         /* handle normal selection - code shown in Example 10-6 */
          .
          .
          .
    }
    else {
        if (XmuConvertStandardSelection(cw, CurrentTime, selection,
                target, type_return, value_return,
                length_return, format_return))
            return True;
        else {
            XtWarning("bitmapEdit: requestor is requesting\
                     unsupported selection target type. \n");
            return(False);
        }
    }
}


Overall, this code handles the TARGETS atom in the first branch, the normal selection target in the second, and any remaining standard atoms and any unknown atoms as two cases in the third branch. For ICCCM-compliant code, you can copy this entire function into your widget and then write just the second branch. Note that branches that successfully provide the requested data return True, and branches that don't return False.[81]

In the first branch you will also need to change the reference to the instance part field that stores the target atom used for selections, bitmapEdit.target_atom. If your widget uses a predefined atom or one supported by the Xmu facility, you would reference that atom here instead of the instance part field. If you called XInternAtom() in initialize and stored the result in an instance part field, you specify that here.

Note that XtGetSelectionRequest() is used to get the time from the SelectionRequest event that the owner received before Xt called the convert_proc function. XtGetSelectionRequest() was introduced in R4 for this specific reason; it needs to meet the current ICCCM and work around the fact that the convert_proc is defined without an event argument. (The convert_proc definition could not be changed because of the required backwards compatibility with R3.)

How Incremental Selection Works

An incremental selection is very similar to an atomic transfer, except that you use different set of routines and procedure types, and Xt calls the procedures you register multiple times instead of just once as in atomic transfers. Incremental transfers and atomic transfers are mutually exclusive--an owner can support both, but if the owner doesn't, a requestor cannot use the incremental routines to request a selection owned atomically, or vice versa.

As in an atomic transfer, the owner and the requestor in an incremental transfer only have to make one call each in order to initiate this multiple-section transfer. Here is the procedure:

  • The owner calls XtOwnSelectionIncremental() in response to the user selecting something. XtOwnSelectionIncremental() returns True or False to indicate whether ownership was granted. XtOwnSelectionIncremental() registers four callbacks:

    XtConvertSelectionIncrProc convert_proc;
    XtLoseSelectionIncrProc lose_selection_proc;
    XtSelectionDoneIncrProc transfer_done_proc;
    XtCancelConvertSelectionProc cancel_conversion_proc;
    
    
    
    
    
  • The user pastes the selection in a widget, which causes that widget to request the selection with XtGetSelectionValueIncremental(). XtGetSelectionValueIncremental() registers one callback:

    XtSelectionCallbackProc requestor_proc;
    
    

    This callback receives the selection data. The requestor_proc is called once upon delivery of each segment of the selection value. It is called with type XT_CONVERT_FAIL when the transfer is aborted--the requestor has the option of keeping or disposing of the partial selection. XtGetSelectionValuesIncremental() is analogous except that it accepts multiple target/client_data pairs which must be received in the requestor_proc.

  • The request for the selection causes Xt to call the convert_proc, which supplies the selection as described for atomic transfers, with the following exceptions. First, convert_proc is called repeatedly by Xt to get each segment. Therefore, it must keep track of what segments have been delivered. Second, when the last segment has been delivered, it should store a non-NULL value in value and zero in length_return. Third, the convert_proc must be ready to supply an additional requestor while it is still engaged in transferring segments to one requestor. The convert_proc is called with a request_id argument that identifies the request. You write the convert_proc to maintain a record of which segments have been delivered for each request_id. One easy way to do this is to use the Xlib context manager.

  • When each segment is converted, Xt calls the requestor_proc (once for each segment), which extracts the data for the requestor and actually pastes it. This function may paste the data chunk-by-chunk or wait until the entire transfer is complete and then paste the entire selection.

  • A zero-length segment terminates the transfer and results in Xt calling the transfer_done_proc.

This was the process of a successful transfer. All the remaining callback functions registered are for less common occurrences or unsuccessful transfers. The cancel_callback is called by Xt on timeout. This means that the transfer is considered complete even though it failed. In this process the owner frees memory allocated for the transfer.

Calls of the lose_ownership_proc do not indicate completion of in-progress transfers--these transfers should continue. It just means that the owner lost ownership of the selection, so that the owner should unhighlight what the user selected.

If transfer_done_proc is specified, the owner allocates and frees storage. If transfer_done_proc is NULL, convert_proc must allocate storage using XtMalloc(), XtCalloc() or XtRealloc(), but Xt will free that memory. The owner may use XtDisownSelection() to relinquish ownership. (This is the same routine used to relinquish ownership of an atomic selection.)

Miscellaneous Selection Routines

If the user deletes the information selected, the owner should call XtDisownSelection(). XtSetSelectionTimeout() sets the time in which widgets must respond to one another. This is initially set by the XmNselectionTimeout resource, and defaults to 5 seconds. The selection timeout prevents hanging when the user pastes but the current owner is slow or hung. XtGetSelectionTimeout() reads the current selection timeout value. Widgets should not normally require these two calls, since the selection timeout should remain under the user's control.

If the requestor can request more than one target type, such as TARGETS and its normal selection target, it normally does so using separate actions. (Both actions can be invoked by the same triggering event, if desired.) Each action specifies a different target type and a different requestor callback. That way, each requestor callback handles only one type of target.

Beware: there is a danger in this approach. The selection owner might change between the repeated XtGetSelectionValue() calls. XtGetSelectionValues() (plural) can be used instead if the requestor would like to receive the data in more than one representation. The requestor's single callback function would then be called once for each representation. (The owner's convert_proc would also be called once per representation.)

Motif Cut and Paste Functions and the Clipboard

You've seen that Xt provides an underlying mechanism called selections that can transfer data from one widget instance to another, whether or not the instances are in the same application. The communication occurs through the server, using properties. Most widgets that display text already support selections.

You can also use selections in your application code to transfer data to other instances of your application or to or from any other widget or application that understands the format of the data. However, selections are quite cumbersome to implement.

Motif provides some easier-to-use functions that pass data to and from the clipboard. The clipboard is actually a property stored in the server, so that any client can read or write it. The clipboard can only hold one piece of data at a time.

Passing a large amount of data can take a few seconds. Therefore, the clipboard is set up so that the application does not actually pass data to the clipboard until the data has been requested by some other application or widget. Your application makes data available by registering a function that supplies the data, and Xt calls that function when the data is requested. Thus if there is any delay in sending the data, it occurs when the data is pasted, not when it is cut.

Motif provides about 15 functions that read and write this data. To copy data to the clipboard, you use the sequence of functions XmClipboardStartCopy(), XmClipboardCopy(), and XmClipboardEndCopy(). To retrieve data from the clipboard, you use XmClipboardInquireLength() and XmClipboardRetrieve().

According to the ICCCM, if a widget or application allows the user to delete a selection, the selection owner code should place the deleted data on the CLIPBOARD selection. Since the CLIPBOARD selection is a list of selections, the most recent deletion should be pushed onto the stack of existing selections.

The standard X distribution includes a special client, xclipboard, that shows the current value of the CLIPBOARD property, and allows it to be manipulated. Except when a widget asserts ownership of the CLIPBOARD with XtOwnSelection() in order to place newly deleted data on it, the xclipboard client is the owner of this property. When it starts up, xclipboard asserts ownership of the CLIPBOARD selection. If it loses the selection (which will happen whenever a widget or client has newly deleted the contents of a selection), it obtains the contents of the selection from the new owner, then reasserts its own ownership of the selection.

Clients wishing to restore deleted data should request the contents of the CLIPBOARD, using the same techniques as we've shown for the PRIMARY selection. xclipboard will respond to these requests, returning the deleted data.

The use of xclipboard allows the value of a selection to survive the termination of the original selection owner.



[70] If you do need to look up certain details of the ICCCM, see Appendix L, Inter-Client Communication Conventions, in Volume Zero, X Protocol Reference Manual. The ICCCM is also included in troff source form in the standard X distribution from MIT.

[71] Dragging is the act of pressing a mouse button with the mouse over the object, and moving the mouse with the button held down. The object is finally released by releasing the mouse button.

[72] Note that we are using the term resources here in a general sense, rather than implying its Xt-specific meaning.

[73] The uwm window manager doesn't decorate, but it is defunct as of R4 since it doesn't honor the current ICCCM.

[74] The ICCCM is reprinted as an appendix of Volume Zero, X Protocol Reference Manual.

[75] It is not practical to write your own networking routines in an application to communicate with other clients running on different hosts because your client may be communicating with the server using TCP/IP, and the other client may be using DECnet. Both may be the same X application that you wrote, but compiled with a version of Xlib that uses a different protocol layer underneath the X Protocol. When you communicate through the server using properties, the server takes care of the translation.

[76] Note that since selections can be implemented in the application, as they are by non-Xt applications, the words “application” and “widget” are interchangeable in this section.

[77] For simplicity, we won't copy the third button semantics used in text selections. This could easily be added.

[78] As described earlier, atoms (rather than strings) are always used in network transmissions to identify properties.

[79] Since Xlib functions causing round-trip requests can impact performance, they should be called only once if possible, and where possible, during the set-up phase of the application as opposed to the event-loop phase. If XInternAtom() were called every time the user made a selection, it would slow the application. In addition, since atoms are server resources that are not freed until the server shuts down, it is important to use predefined atoms whenever possible.

[80] The X Protocol defines all the predefined atoms. Therefore, even though XA_TARGETS would be convenient to have as a predefined atom, this can't be arranged with changing the protocol (albeit in a minor way). And the protocol is unlikely to be changed in even minor ways when there are workarounds, as in this case.

[81] The ICCCM also specifies that functions implementing selections must be able to respond to a MULTIPLE target value, which is used to handle selections too large to fit into a single property. However, the necessary handling is done by the Intrinsics. Your procedures do not need to worry about responding to the MULTIPLE target value; a selection request with this target type will be transparently transformed into a series of smaller transfers.