Chapter 10. Data Transfer Between Widgets with UTM

Motif provides several mechanisms for transferring data from one widget to another. For example, Motif users can use any of the following mechanisms to move text between any two XmText widgets:

From a user's point of view, each of these data transfer methods is rather different; each one is accomplished by pressing different combinations of keys or mouse buttons. From a Motif widget writer's point of view, however, there are a number of similarities in implementing them. These similarities in data transfer implementation fit under the umbrella term "Uniform Transfer Model" (UTM).

UTM is not a user-accessible data transfer mechanism. In other words, there is no magic sequence of keys that will allow a user to transfer data through UTM. Rather UTM is simply a way for a widget writer (and possibly, an application programmer) to implement one of the four data transfer mechanisms.

UTM gives the widget writer the following arsenal of software for implementing data transfer:

One way to think of UTM is that it is a convenient front end (or wrapper) to existing data transfer mechanisms. You could, for example, implement primary transfer with existing Xt and Motif calls. However, you would probably find it far easier to implement primary transfer through UTM. You will almost certainly find it easier to implement data transfer through UTM when your widget supports more than one transfer mechanism.

UTM is completely compatible with the ICCCM (the Inter-Client Communications Conventions Manual). In fact, to get the most out of this chapter, you should already be comfortable with the material in the ICCCM. We do, however, provide the following tutorial for those whose knowledge may need a little refreshing.

A Data Transfer Tutorial

Transferring data between two widgets is analogous to transferring data between two computers. In both cases, data transfer requires both a channel and a communications protocol.

Consider how two computers transfer a file. Before the transfer, one computer owns the file to be transferred and the other computer (the receiver) does not. In order for the transfer to take place, some sort of channel (probably a modem or a cable) must connect the two machines. However, having a physical link between the two machines is not enough; the two machines must also agree on the rules or protocols governing the file transfer. For example, the computer that owns the file needs some way to tell the destination computer that it is ready to send the file. The destination computer needs to signal back to the owner computer when it is ready to receive. At the end of the file transfer, the destination computer needs to acknowledge receipt of the file.

Now let's consider how two widgets transfer data. Before the data transfer, one widget owns the data to be transferred and the other widget (the destination widget) does not. In order for the transfer to take place, some sort of channel must connect the two widgets. The X Windows server acts as this virtual channel. However, having a channel is not enough; the two widgets must also share the same data transfer protocol. This protocol is defined by the ICCCM; the recommended Motif implementation of the ICCCM is called UTM.

The ICCCM protocol defines the kinds of messages passed between the owner widget and the destination widget. The two widgets can belong to the same client or to two different clients.

Problems Solved by the Protocol

Many newcomers wonder why the ICCCM protocol has to be so complicated. One answer is that each widget is a self-contained object that knows how to take care of itself but ignores other widgets. Therefore, widgets monitor their own events only and ignore the events of other widgets.

For example, suppose a user wants to copy some data from one widget (the source) to another (the destination). To do this, the user must first select the data that is to be copied. The user then moves the pointer to the destination widget and indicates where the data should be transferred. Since the destination widget does not receive the source widget's events, the destination widget does not have the following information:

  • The data the user selected for copying

  • What kind of data (such as text, a pixmap, a color) the user has selected

  • The format data is in or how much data there actually is

The ICCCM protocol solves these problems by forcing all widgets to use atoms to describe the data to be transferred.

Atoms

The ICCCM requires that the owner and destination use atoms to solve these categorization needs. In brief, an atom is a number that symbolizes a particular data category or a particular data transfer selection. For example, the ICCCM specifies that an atom called PIXMAP be used to symbolize the transfer of a pixmap. As another example, the ICCCM specifies that an atom called CLIPBOARD by used to symbolize a clipboard selection.

Atoms like PIXMAP that specify a data category are called target atoms or, more simply, targets. Atoms like CLIPBOARD that specify a kind of transfer mechanism are called selection atoms.

Target Atoms

Widgets can display many kinds of information. Most of the displayed information (and some of the nondisplayed information) should be transferrable. The widget represents each of these different kinds of transferrable information as a target atom.

For example, consider the XmLabel widget. As you might expect, XmLabel allows users to make copies of the displayed text or pixmap. What is somewhat surprising is that XmLabel can also transfer other kinds of information. For example, XmLabel can transfer information about its foreground and background colors. In fact, the relatively simple XmLabel widget can transfer over a dozen different targets.

Selection Atoms

Each selection is represented by an atom. The display contains only one selection of each type. It is owned by a client or by no one and, if owned, is attached to a window of the owning client. Any client may assert or remove ownership of a selection.

The data represented by the selection is internal to the client that owns the selection.

Motif uses the following selection atoms:

  • PRIMARY, which identifies the principal selection. This selection atom is defined by the ICCCM.

  • SECONDARY, which identifies a means of exchanging data without disturbing the primary selection. This selection atom is defined by the ICCCM.

  • CLIPBOARD, which is the selection often used to cut or copy data from one client and paste it into another. A client transfers data to the clipboard by asserting ownership of this selection. A client transfers data from the clipboard by requesting conversion of the selection. A separate client may also represent the clipboard. This client can notice when it loses the selection (because another client wants to transfer data to the clipboard), then request a conversion of the selection and finally reassert ownership. This selection atom is defined by the ICCCM.

  • _MOTIF_DROP, which is the selection used in a drag and drop operation. This selection atom is specific to Motif; that is, the ICCCM does not define the _MOTIF_DROP selection atom.

Defining Atoms

The Atom data type is used to declare an atom variable. Use the Xlib function XInternAtom to assign an atom value to your atom variable. For example, the following code creates an Atom variable named TEXT and associates the correct atom value with the TEXT variable.

Atom TEXT = XInternAtom(XtDisplay(w), "TEXT", False);

The second argument to XInternAtom is an atom string name. You can specify an atom string name from any of the following four categories:

  • An atom string name defined by the ICCCM; for example, TARGETS.

  • A Motif atom string name that is a redefinition of an ICCCM atom string name. Table 10-1 lists all these atom string names along with their ICCCM equivalents. You can assume that all of the Motif atom string names have the same meaning as their ICCCM equivalents.

  • A Motif atom string name that has no ICCCM equivalent.

  • An atom string name that you make up yourself.

We recommend that you avoid making up new atom string names whenever possible. If you do make up your own, the atom string names you pick should follow the naming conventions documented in the ICCCM.

You can specify an atom string name as a constant having the XmS prefix; for example:

Atom TARGETS = XInternAtom(XtDisplay(w), XmSTARGETS, False);

or you can specify an atom string name as a string without the XmS prefix; for example, the following line of code produces the same results as the previous line of code:

Atom TARGETS = XInternAtom(XtDisplay(w), "TARGETS", False);

Table 10-1. Motif Atoms That Are Equivalent to ICCCM Atoms

Atom String NameICCCM Equivalent
XmSCLIENT_WINDOWCLIENT_WINDOW
XmSCLIPBOARDCLIPBOARD
XmSCOMPOUND_TEXTCOMPOUND_TEXT
XmSDELETEDELETE
XmSFILEFILE
XmSFILE_NAMEFILE_NAME
XmSINCRINCR
XmSINSERT_PROPERTYINSERT_PROPERTY
XmSINSERT_SELECTIONINSERT_SELECTION
XmSLENGTHLENGTH
XmSLINK_SELECTIONLINK_SELECTION
XmSMULTIPLEMULTIPLE
XmSNULLNULL
XmSTARGETSTARGETS
XmSTEXTTEXT
XmSTIMESTAMPTIMESTAMP
XmSWM_STATEWM_STATE

As we mentioned earlier, Motif also provides some atom string names that have no ICCCM equivalent. Motif atom string names that have no ICCCM equivalent have a prefix of _MOTIF are as follows:

  • XmS_MOTIF_CLIPBOARD_TARGETS

  • XmS_MOTIF_COMPOUND_STRING

  • XmS_MOTIF_DEFERRED_CLIPBOARD_TARGETS

  • XmS_MOTIF_DESTINATION

  • XmS_MOTIF_DROP

  • XmS_MOTIF_ENCODING_REGISTRY

  • XmS_MOTIF_EXPORT_TARGETS

  • XmS_MOTIF_LOSE_SELECTION

  • XmS_MOTIF_RENDER_TABLE

  • XmS_MOTIF_SNAPSHOT

This chapter explains all of these unique Motif atom string names.

A Typical Data Transfer Sequence

The data transfer protocol defines a kind of dialog between the source widget and the destination widget. This dialog is somewhat different for each data transfer mechanism. For example, the dialog of a clipboard transfer is more complicated than that of a primary transfer. Nevertheless, we can describe a kind of idealized dialog that is general enough to be reasonably accurate for all four data transfer mechanisms.

This idealized data transfer sequence consists of the seven steps shown in Figure 10-1.

Figure 10-1. The Data Transfer Dialog

The user starts a data transfer (Step 1) by specifying the source widget of the data transfer. The user typically specifies the source widget by selecting some portion of a widget; for example, a line of displayed text. The user makes a selection through some keyboard or mouse event. For example, a user can select a line in an XmText widget by positioning the cursor anywhere on the line and then clicking the left mouse button three times. In some cases, Step 1 actually consists of two parts. In the first part, the user selects the data to be transferred. In the second part, the user enters some other event to specify the data transfer mechanism. For example, the user might hold down the middle mouse button to start a drag operation.

The user also indicates the destination widget (Step 2) of the data transfer. The user does this by changing focus to the destination widget and then entering some event. For example, the user specifies the destination widget of a drag and drop operation by moving the cursor to the destination widget and then releasing the middle mouse button.

After completing Steps 1 and 2, the user's role in data transfer is over. The remaining steps must all be implemented by the widget.

Don't forget that, at this stage, the destination widget has no idea what kind of data has been selected in the source widget. Therefore, at Step 3, the destination widget has to ask the source widget for a list of all the targets that the source widget can transfer. The destination widget does this by sending the source widget a message asking for its TARGETS list.

The source widget responds to the request for TARGETS (Step 4) by returning a list of all the atoms that it knows how to transfer. For example, in Figure 10-1, the source widget claims to know how to transfer the following targets:

  • STRING

  • COMPOUND_TEXT

  • LOCALE_ENCODING

  • MOTIF_COMPOUND_STRING

In practice, the returned list of TARGETS will be considerably longer than in this example.

The source widget does not place the atoms in the returned TARGETS list in any particular order. For example, the first atom in the TARGETS list is not necessarily the atom that the source widget wants most to transfer.

In Step 5, the destination widget examines the list of target atoms returned by the source widget. From this list, the destination widget chooses the atom that most closely meets its needs. In Figure 10-1, the destination widget selected COMPOUND_TEXT.

In Step 6, the source widget converts the data that the user has selected into the data representation that the destination widget has requested. For example, in Figure 10-1, the source widget has to convert the selected data to COMPOUND_TEXT. In some cases, the source widget does not have to do much work to convert the data. For example, perhaps the source widget stores text as COMPOUND_TEXT anyway. In other cases, the source widget might have to fight a considerable battle to convert the data into the proper representation. For example, perhaps the source widget stores text in XmString format. In this case, the source widget will have to convert the string from XmString format to COMPOUND_TEXT format.

A surprisingly high percentage of a data transfer widget is devoted to data conversion. When the source widget is finished converting the data, the source widget sends the converted data to the destination widget.

In Step 7, the destination widget pastes the received data into the appropriate portion of the widget.

You may be wondering what happens if the destination widget wants more than one kind of data from the source widget. For example, suppose that the destination widget wants the source widget to transfer both a pixmap and a colormap. There are two possible ways to handle this. One possibility is that Steps 5, 6, and 7 will be repeated. Another possibility is that, in Step 5, the destination widget can request a multiple data transfer, and, in Step 6, the source widget can honor that request by passing several different kinds of data back at the same time.

Overview of Implementing UTM

Suppose you want the widget you are writing to support data transfer. To implement data transfer by using UTM, you must do the following:

  1. Decide which data transfer mechanism(s) your widget will support. For example, perhaps your widget will support both the primary and clipboard transfers. Supply translations appropriate for these data transfer mechanisms.

  2. Write action methods for these translations; have these action methods call the appropriate UTM Xme set-up routines.

  3. Install the XmQTtransfer trait on your widget.

  4. Provide a convertProc trait method if your widget is to be a source for data transfer. Source widgets must support the XmNconvertCallback resource.

  5. Provide a destinationProc trait method if your widget is to be a destination for data transfer. Destination widgets must support the XmNdestinationCallback resource.

  6. Provide a transfer procedure if your widget is to be a destination for data transfer.

This section details each of these steps. Throughout this section and the remainder of the chapter, we will examine how the ExmStringTransfer widget implements UTM. The ExmStringTransfer demonstration widget is a subclass of ExmString. The only feature that ExmStringTransfer layers on top of ExmString is its support of the following three data transfer mechanisms:

  • Primary

  • Clipboard

  • Drag and drop

ExmStringTransfer gives us an excellent opportunity to isolate the code that implements UTM.

ExmStringTransfer has two important limitations. First, ExmStringTransfer does not let users transfer portions of the displayed text. In other words, a user may only transfer the entire contents of the ExmStringTransfer widget. By contrast, a more powerful widget such as XmText lets users copy a selected word, line, paragraph, and so forth.

Second, ExmStringTransfer only knows how to do copy operations; it cannot do a move operation. In fact, when ExmStringTransfer is a destination, the incoming string will overwrite any existing displayed string.

The sample application stored in directory demos/programs/Exm/app_in_c contains two ExmStringTransfer widgets. You might want to build this application and experiment with it. For example, try to copy data from one ExmStringTransfer widget to another. Then, try to copy text from a widget in another application to one of the ExmStringTransfer widgets.

Supply Appropriate Translations

You must decide what data transfer mechanisms your widget will support. Ideally, your widget should support as many data transfer mechanisms as possible. Writing a widget that supports multiple data transfer mechanisms is only slightly more time consuming than writing a widget that supports only one data transfer mechanism. Most of the data transfer code is independent of a particular form of data transfer.

You might be writing a read-only widget. In other words, the user is not allowed to transfer data into the widget. For these widgets, you need only supply the source side of data transfer; you can omit the destination side.

One way to help you decide what data transfer mechanisms your widget should support is to examine how the widgets in the Motif toolkit support data transfer. (By the way, as of Motif Release 2.0, all the widgets in the Motif toolkit use UTM to implement data transfer.) Table 10-2 summarizes the data transfer methods supported in the standard widget set. When an entry says "Source only" or "Drag only," it means that a user can copy data in this widget but cannot cut data out of it or paste data into it. For example, a user can copy text from an XmLabel to an XmText widget but cannot copy text from an XmText to an XmLabel widget.

Table 10-2. Data Transfer in the Standard Widget Set

WidgetPrimarySecondaryClipboardDrag and Drop
XmCascadeButtonYes (Source only)NoNoYes (Drag only)
XmCascadeButtonGadgetYes (Source only)NoNoYes (Drag only)
XmDrawnButtonYes (Source only)NoNoYes (Drag only)
XmContainerYesNoYesYes
XmLabelNoNoNoYes (Drag only)
XmLabelGadgetNoNoNoYes (Drag only)
XmListYes (Source only)NoYes (source only)Yes (Drag only)
XmPushButtonYes (Source only)NoNoYes (Drag only)
XmPushButtonGadgetYes (Source only)NoNoYes (Drag only)
XmScaleNoNoNoYes (Drag only)
XmTextYesYesYesYes
XmTextFieldYesYesYesYes
XmToggleButtonYes (Source only)NoNoYes (Drag only)
XmToggleButtonGadgetYes (Source only)NoNoYes (Drag only)

Users trigger data transfer by pressing keys or mouse buttons. Your widget must supply data transfer translations compatible with the Motif Style Guide.

Widgets typically provide two different kinds of translations to support data transfer.

The first kind of data transfer translation allows the user to select the data to be transferred. For example, the XmText widget provides several translations that allow a user to select different subsets of the displayed text. These subsets range from one character to the entire text. By contrast, XmLabel does not allow a user to select a portion of its displayed text; the user can only select the entire displayed text.

In addition to providing selection translations, your widget must also supply translations that let the user initiate and complete the data transfer. For example, if a widget is to support drag and drop, the widget must supply a translation that allows the user to mark the start of the drag operation. (Typically, the user starts a drag operation with a Button2 or Button2Press event.) The four different kinds of data transfer each require different translations.

The easiest way to supply appropriate Motif translations for your widget is to copy the data translations of the standard Motif widget that is most similar to your own. Translations for standard Motif widgets are documented in the Motif Programmer's Reference.

Write Actions for These Translations

Your widget must provide actions that respond to the user's data transfer request. These action methods have to call the appropriate UTM Xme set-up routine. The routine your action calls depends on the type of data transfer, and on whether the action method is for the source side of the transfer or the destination side of the transfer.

If you are writing an action method that represents the source side of a data transfer, the action method must call one of the following routines:

  • XmePrimarySource

  • XmeSecondarySource

  • XmeClipboardSource

  • XmeDragSource

For example, suppose a widget is to serve as a source for a clipboard transfer. This widget must respond to a selection translation by calling XmeClipboardSource.

Broadly speaking, each of the preceding Xme routines makes the following sequence of calls:

  1. It calls the application's XmNconvertCallback procedures, if any.

  2. It calls the widgets's convertProc trait method.

Actions in destination widgets must call one of the following routines:

  • XmePrimarySink

  • XmeSecondarySink

  • XmeClipboardSink

  • XmeDragSink

For example, suppose a widget is to serve as a destination for a clipboard transfer. When the user triggers a clipboard paste, the clipboard paste action must call XmeClipboardSink.

Broadly speaking, each of the preceding four routines trigger the following sequence of calls:

  1. It calls the widget's destination pre-hook procedure, if any.

  2. It calls the application's XmNdestinationCallback procedures, if any.

  3. It calls the widget's destinationProc trait method.

The procedures and callbacks are described later in this section.

Install The XmQTtransfer Trait

Your widget must install the XmQTtransfer trait in order to support UTM data transfers.

Your widget can install XmQTtransfer as it would install any trait; namely by calling XmeTraitSet. Typically, the call is made from a class_part_initialize method.

Widgets capable of acting as a source for data transfer must provide a convertProc trait method. Widgets capable of acting as a destination for data transfer must provide at least a destinationProc trait method. Frequently, a widget will be both a source and a destination. In this case, your widget must supply both trait methods. Destination widgets may optionally also provide a destinationPreHookProc trait method.

For example, the ExmStringTransfer widget installs the XmQTtransfer trait as part of its ClassPartInitialize method. The code that does the installation is as follows:

ClassPartInitialize(WidgetClass widgetclass)
{
  XmeTraitSet((XtPointer) widgetclass, XmQTtransfer, (XtPointer)
              &StringTrT);
}

where the StringTrT variable is declared earlier in the StringTrans.c file as follows:

static XmConstXmTransferTraitRec StringTrT = {
  0,                                           /* version */
  (XmConvertCallbackProc) ConvertProc,         /* convertProc */
  (XmDestinationCallbackProc) DestinationProc, /* destinationProc */
  NULL,                                        /*no destinationPreHookProc */
};

Supply a convertProc Trait Method

Source widgets must supply a convertProc (convert procedure) trait method. The convertProc method is one of the trait methods of the XmQTtransfer trait. (See Chapter 18 for complete syntactic details on the XmQTtransfer trait.) UTM automatically calls convertProc whenever an object (typically, a destination widget) requests a conversion from the widget.

The convertProc has the same prototype as any Intrinsics callback. Therefore, the third argument to convertProc is call_data. UTM will pass an XmConvertCallbackStruct to the call_data argument. The XmConvertCallbackStruct contains all the raw data required for the conversion. For example, the targets member of this structure contains the name of the target atom that the destination procedure wants converted.

Your convertProc must respond to the following two general kinds of requests:

  • A request for the list of targets supported by your widget. For example, the requestor may ask your convertProc for TARGETS.

  • A request to convert the selection to a specific target. For example, the requestor may ask your convertProc to convert the selection to a COMPOUND_TEXT format.

Later on in this chapter, we will discuss the specific targets your widget should support.

Supply a destinationProc Trait Method

Your widget must supply a destinationProc (destination procedure) trait method in order to serve as a destination widget in a data transfer. The destinationProc trait method is one of the trait methods of the XmQTtransfer trait. (See Chapter 18 for complete syntactic details on the XmQTtransfer trait.) UTM automatically calls destinationProc whenever a user requests that data be transferred to the destination widget. The destinationProc trait method is responsible for requesting data from the source widget and for pasting this data into the destination widget. However, the destinationProc trait method will ask a transfer procedure to take over some of these responsibilities.

UTM calls destinationProc after calling any XmNdestinationCallback procedures defined by the application. The toolkit passes destinationProc an XmDestinationCallbackStruct structure. This structure contains all the fields necessary to make requests to the source widget.

The destinationProc trait method should typically call XmTransferValue to request a conversion from the source widget XmTransferValue automatically calls the source widget's convertProc. When the convertProc finishes, XmTransferValue typically calls the destination widget's transfer procedure. The name of the transfer procedure is identified by the third argument to the XmTransferValue call.

The destinationProc trait method can terminate a data transfer by calling XmTransferDone.

XmTransferValue, together with XmTransferDone, form the UTM replacements for the following older calls:

  • XtGetSelectionValue

  • XtSetSelectionValue

  • XmClipboardStartRetrieve

  • XmClipboardRetrieve

  • XmClipboardEndRetrieve

  • XmDropTransferStart

  • XmDropTransferAdd

Supply a Transfer Procedure

If you are writing a destination widget, you will probably have to write one or more transfer procedures. UTM calls a transfer procedure when the convertProc trait method finishes the conversion. UTM passes the transfer procedure a pointer to an XmSelectionCallbackStruct. (See the XmTransferDone(3) reference page of the Motif Programmer's Reference for details on this structure.) The value member of XmSelectionCallbackStruct holds the selection converted to the target that the destination widget has requested. The target procedure will take that converted target and paste it into the destination widget.

UTM and the Application

So far, we have focused on the work that widgets do to implement a UTM data transfer. However, it is possible that a Motif application will implement some or even all of a UTM data transfer. UTM allows application programmers to register two kinds of callbacks:

  • XmNconvertCallback procedures to be registered on widgets that can be the source of a UTM data transfer.

  • XmNdestinationCallback procedures to be registered on widgets that can be the destination of a UTM data transfer.

In addition, an application's XmNdestinationCallback procedure can provide its own transfer procedure.

Conversion Routines

UTM automatically calls any XmNconvertCallback procedures immediately prior to calling the convertProc trait method of the source widget. UTM automatically passes an XmConvertCallbackStruct to the application's XmNconvertCallback procedures. These procedures are free to modify the fields of this structure. UTM will pass this structure, modifications and all, to your source widget's convertProc trait method.

If the application does not provide any XmNconvertCallback procedures, then UTM simply calls the convertProc trait method. If the application does provide XmNconvertCallback procedures, then UTM conditionally calls convertProc depending on the value in the status member of the XmConvertCallbackStruct. If the status member holds XmCONVERT_DONE or XmCONVERT_REFUSE, then UTM will not call the source widget's convertProc trait method. If the status member holds some other value, then UTM will call the source widget's convertProc trait method.

Throughout this chapter, we use the term "conversion routines" to refer to a combination of the XmNconvertCallback procedures of the application and the convertProc trait method of the source widget.

Destination Routines

UTM automatically calls any XmNdestinationCallback procedures immediately prior to calling the destinationProc trait method.

Throughout this chapter, we use the term "destination routines" to refer to a combination of the XmNdestinationCallback procedures of the application and the destinationProc trait method of the destination widget.

Application Interaction

Broadly speaking, an application's UTM callbacks can interact with your widget's UTM trait methods in any of the following three ways:

  1. The application might not provide any UTM callbacks.

  2. The application might provide UTM callbacks that do all the UTM transfer work. In this case, UTM will not call your widget's UTM trait methods.

  3. The application might provide UTM callbacks that do some, but not all, of the UTM transfer work.

In case 1, your widget must be prepared to do all the conversion and transfer work itself. Preparing for this case simply means that your widget's UTM trait methods must be self-sufficient. In other words, your widget must handle all the targets that your widget documentation says it handles.

In case 2, your widget's UTM trait methods never get called.

In case 3, your widget's UTM trait methods must react appropriately to data passed by the application's UTM callback procedures. Consider the following situation. Suppose you write a widget that displays text. Being a good widget writer, you code your widget so that its UTM trait methods support all the standard textual targets. However, suppose an application programmer decides to supplement your widget's list of targets by writing a UTM application callback that supports an additional textual target, _FORMATTED_TEXT. Therefore, the following things must happen:

  • When the destination widget asks for a list of TARGETS, the source widget must somehow provide a list of all of its original targets plus the _FORMATTED_TEXT target.

  • When the destination widget asks for a conversion to _FORMATTED_TEXT, the XmNconvertCallback procedures of the application must do the conversion.

  • When the destination widget asks for a conversion to any other target, the convertProc trait method must do the conversion.

Of the preceding three bulleted items, only the first requires extra coding by the widget writer. When an application adds a new target, the application must set the status member of the XmConvertCallbackStruct to XmCONVERT_MERGE. (Remember that UTM automatically passes an XmConvertCallbackStruct to your widget's convertProc trait method.) Therefore, your widget's convertProc trait method should always examine the status member to see if it holds XmCONVERT_MERGE. If it does, convertProc should call XmeConvertMerge. The following code illustrates how this is done:

if (cs -> status == XmCONVERT_MERGE) {
  XmeConvertMerge(value, type, format, length, cs);
  XtFree((char *) value);
}

The XmeConvertMerge routine merges two groups of list-like targets. In this case, XmeConvertMerge merges the sole target from the application _FORMATTED_TEXT with the list of targets already supported by your widget.

Primary Transfer Details

This section details primary transfers.

Primary Transfer: Step-by-Step

A primary transfer involves interaction among a user, an application, a source widget, and a destination widget. Following are the steps involved in a typical primary transfer:

  1. The user selects some portion of the source widget. The source widget probably highlights the selection.

  2. The source widget calls XmePrimarySource. XmePrimarySource establishes an internal function (we will call it FunctionP) that UTM will call later during the transfer.

  3. The user moves the cursor to the destination widget and presses some mouse button or key sequence to initiate a primary paste.

  4. The Intrinsics call the action method of the destination widget that is associated with the primary paste event. This action method calls XmePrimarySink.

  5. XmePrimarySink calls the destination widget's destinationPreHookProc trait method, if it exists.

  6. XmePrimarySink calls any XmNdestinationCallback procedures that the application has attached to the destination widget.

  7. XmePrimarySink conditionally calls the destinationProc trait method of the destination widget. We will use the term "destination routines" to refer to the combination of the XmNdestinationCallback application procedures and the destinationProc trait method. Typically, one of the destination routines will call XmTransferValue and request a list of the source widget's TARGETS.

  8. UTM calls FunctionP, which in turn calls the conversion routines of the source widget. The conversion routines are responsible for returning a list of the convertible targets.

  9. UTM returns control to the transfer procedure named by XmTransferValue. The transfer procedure examines the list of returned targets and picks the most appropriate one. The transfer procedure then calls XmTransferValue and asks for the chosen target. For this example, let us assume that the destinationProc picked the COMPOUND_TEXT target.

  10. UTM calls FunctionP, which in turn calls the conversion routines again. This time, FunctionP asks the conversion routines to convert the selection to COMPOUND_TEXT format. The conversion routine is responsible for setting the appropriate fields in the XmConvertCallbackStruct For example, the conversion routine should place the converted text into the value member of the XmConvertCallbackStruct. If a conversion routine can do the conversion, it sets the status member of the XmConvertCallbackStruct to XmCONVERT_DONE.

  11. UTM returns control to the transfer procedure of the destination widget. Assuming all went well, the converted text will be stored in the value member of the XmSelectionCallbackStruct. The transfer procedure need only paste the converted text into the appropriate place in the destination widget.

Sample Translations for a Primary Transfer

The ExmStringTransfer demonstration widget supports primary transfer. The following translations are relevant to the primary transfer:

<Btn1Down>:  ExmStringTransferMoveFocus()ExmStringTransferCopyPrimary()\n\
<Btn2Down>:  ExmStringTransferProcessDrag()\n\

A user starts a primary copy by pressing Btn1Down. However, the Btn2Down translation is far more puzzling because Btn2Down could mean either a primary paste or the start of a drag operation. ExmStringTransferProcessDrag solves this puzzle by determining whether or not ExmStringTransfer currently owns the primary selection. If it does, ExmStringTransferProcessDrag calls the ExmStringTransferPastePrimary routine.

Sample Copy Primary Action

The ExmStringTransfer widget contains the following relatively simple copy primary routine:

static void
ExmStringTransferCopyPrimary(
     Widget w,
     XEvent *event,
     String *params,
     Cardinal *num_params)
{
 Time time;
 ExmStringTransfer stw = (ExmStringTransfer) w;
  /* First we must obtain a timestamp. This is required for
     ICCCM compliance. XtLastTimestampProcessed holds the timestamp
     from the last event the Intrinsics saw with a timestamp. */
  time = XtLastTimestampProcessed(XtDisplay(w));

  /* Own the primary selection. Indicate this to the user by
     reversing the foreground and background in the text rendition
     (eventually) */
  stw->string_transfer.own_primary = True;

  /* Once we call XmePrimarySource,  the widget's transfer trait
     convert method will get called if a destination wishes to
     obtain the PRIMARY selection. */
  XmePrimarySource(w, time);

  /* Update the widget to show the new state */
  stw->core.widget_class->core_class.expose(w, NULL, NULL);
}

In the preceding code, stw->string_transfer.own_primary is a Boolean field. ExmStringTransfer sets this field to True when the widget holds the primary selection and False when the widget relinquishes the primary selection.

Sample Paste Primary Action

The most important part of the paste primary routine of ExmStringTransfer is its call to XmePrimarySink. Following is the complete ExmStringTransferPastePrimary routine:

static void
ExmStringTransferPastePrimary(
     Widget w,
     XEvent *event,
     String *params,
     Cardinal *num_params)
{
  Time time;
  unsigned int op;
  /* First we must obtain a timestamp. This is required for
     ICCCM compliant ownership of a selection. We use
     XtLastTimestampProcessed which holds the timestamp
     from the last event the Intrinsics saw with a timestamp. */
  time = XtLastTimestampProcessed(XtDisplay(w));

  /* We determine the right operation to perform by looking
     at the modifiers present */
  if (event -> xbutton.state & ShiftMask) {
    if (event -> xbutton.state & ControlMask)
      op = XmLINK;
    else
      op = XmMOVE;
  } else
    op = XmCOPY;

  /* Calling XmePrimarySink will start the process of requesting
     the PRIMARY selection to be pasted using the transfer trait
     destination callback for this widget class */
  XmePrimarySink(w, op, NULL, time);
}

The second argument to XmePrimarySink (called op in the preceding code) describes the kind of operation that the user has requested. UTM will copy the value of op into the operation member of the XmDestinationCallbackStruct.

Clipboard Transfer Details

This section explains some of the finer points of UTM clipboard transfer. As we shall see, it is quite a bit more complicated than UTM primary transfer. This section provides the following information:

  • A high-level overview of clipboard transfers

  • A description of deferred clipboard transfers and snapshots

  • A step-by-step detailed walk through of a UTM clipboard transfer

  • Sample code to implement a UTM clipboard transfer

Compatibility with Previous Releases

Motif provides a large set of XmClipboard routines. (See the Motif Programmer's Reference for details on each routine, or see the Motif Programmer's Guide for a summary of the routines.)

Although these routines are still supported, we recommend that you use UTM instead. In other words, you should use UTM as your interface to the clipboard.

High-Level Overview of Clipboard Transfers

A clipboard transfer really consists of two separate transfers. In the first transfer, the source widget transfers the data to the clipboard. The clipboard acts as a pseudo-destination widget for this first transfer. In the second transfer, the clipboard transfers the data to the destination widget. For this second transfer, the clipboard acts as a pseudo-source widget.

When the source widget transfers data to the clipboard, the source widget has no idea what target the destination widget might request. Therefore, the source widget typically converts the selection to several different targets and transfers each of these targets to the clipboard. For example, if the source widget displays text, the source widget might transfer the selected text to the clipboard one time for each of the five standard textual targets. When the destination widget requests the data from the clipboard, the destination widget will (hopefully) ask for one of the five standard textual targets. If it does, the clipboard can transfer the selection to the destination widget.

Clipboard transfer is inherently less efficient than primary transfer. However, Motif does provide a somewhat more efficient mechanism for clipboard transfer called "deferred" clipboard transfer.

Immediate Versus Deferred Clipboard Targets

The main problem with any clipboard transfer is that the source widget has to copy many different versions of the selection to the clipboard. Using deferred clipboard transfer does not really solve this problem; however, it does make the copying operations significantly cheaper. That is because the source widget need only copy "pointers" or references to the selection, rather than copying the selection itself.

UTM always asks your clipboard source widget to provide a list of its immediate clipboard targets (_MOTIF_CLIPBOARD_TARGETS) and its list of deferred clipboard targets (_MOTIF_DEFERRED_CLIPBOARD_TARGETS ). It is up to you to decide which clipboard targets fall into which category. It is acceptable to make all clipboard targets immediate, or to make them all deferred, or to use some combination of the two. One rule of thumb is that a clipboard target should be deferred if it is relatively expensive to copy it to the clipboard.

Snapshots

This section explains how you must use snapshots to implement deferred clipboard targets.

If your source widget is to act as a clipboard source, UTM will ask your convertProc trait method to convert the _MOTIF_DEFERRED_CLIPBOARD_TARGETS target. Your convertProc trait method should respond to this request as it would respond to any request for a list of targets; namely, by building a list of deferred clipboard targets. For example, suppose we want the list of deferred clipboard targets to consist of _MOTIF_COMPOUND_STRING and STRING. In this case, the following convertProc code should be sufficient:

ConvertProc(
     Widget w,
     XtPointer client_data,
     XtPointer call_data)
{
 XmConvertCallbackStruct *cs = (XmConvertCallbackStruct *) call_data;
 Atom _MOTIF_DEFERRED_CLIPBOARD_TARGETS = XInternAtom(XtDisplay(w),
                         "_MOTIF_DEFERRED_CLIPBOARD_TARGETS", False);
 Atom _MOTIF_COMPOUND_STRING = XInternAtom(XtDisplay(w),
                         "_MOTIF_COMPOUND_STRING", False);
 Atom STRING = XInternAtom(XtDisplay(w), "STRING", False);
 ...

  if (cs->target = _MOTIF_DEFERRED_CLIPBOARD_TARGETS)  {
    Atom *targs = (Atom *) XtMalloc(sizeof(Atom) * 2);
    int   n;
    n = 0;
    targs[n] = _MOTIF_COMPOUND_STRING; n++;
    targs[n] = STRING; n++;

    cs->value = (XtPointer)targs;
    cs->type = XA_ATOM;
    cs->length = n;
    cs->format = 32;
  }
  ...

Since the length member of the XmConvertCallbackStruct was non-zero, UTM will call the convertProc trait method again This time, UTM will ask your convertProc trait method to convert the _MOTIF_SNAPSHOT target. Your convertProc must respond to this target by doing the following:

  • Save a "snapshot" of the selection.

  • Generate a new atom that uniquely distinguishes this snapshot.

A snapshot is nothing more than a copy of the current clipboard selection in whatever format you decide to save it in. (UTM does not define how the snapshot must be stored.) If you want, you can go ahead and convert the selection to _MOTIF_COMPOUND_STRING format and to STRING format and then save the converted selection somewhere in your widget. However, it will probably be more efficient just to save the selection somewhere safe and to worry about doing the conversions later.

The distinguisher atom must be unique, so you are going to have to devise some algorithm for generating unique atom string names. One idea is to base the snapshot atom string name on the widget ID itself. For example:

static Atom
GenerateUniqueDistinguisherAtom(Widget w)
{
  char  distinguisher_atom_string[32];
  ExmMyWidget  my_widget = (ExmMyWidget)w;
   sprintf(distinguisher_atom_string, "_EXM_SNAP_%lX_%X", (long)w,
                 my_widget.snap_counter++);
   return (XInternAtom(XtDisplay(w), distinguisher_atom_string, False));
}

Then your convertProc trait method can convert the _MOTIF_SNAPSHOT request as follows:

 ...
 Atom *distinguisher_pointer;

 if (cs->target = _MOTIF_SNAPSHOT)  {
   /* Save the clipboard selection somewhere. */
     ...

   /* Now return the distinguisher atom. */
     distinguisher_pointer = (Atom *) XtMalloc(sizeof(Atom));

    *distinguisher_pointer = GenerateUniqueDistinguisherAtom(w);
     cs->value = (XtPointer)distinguisher_pointer;
     cs->type  = XA_ATOM;
     cs->length = 1;
     cs->format = 32;
 }
 ...

UTM stores this distinguisher atom on the clipboard. This one distinguisher atom symbolizes both the deferred clipboard targets ( STRING and _MOTIF_COMPOUND_STRING). Later, the destination widget may request one of the deferred clipboard targets; for example, STRING. If it does, UTM will call your widget's convertProc trait method, passing the following in the XmConvertCallbackStruct:

  • STRING as the target member

  • The distinguisher atom as the selection member

Your widget's convertProc trait method must respond to this request by finding the snapshot and converting it to STRING. When the conversion is completed, UTM automatically passes the converted data to the clipboard. Then, the clipboard automatically passes the converted data to the destination widget.

Clipboard Transfer: Step by Step

This section takes you step by step through a typical clipboard transfer. Remember that this is a typical transfer, but it is by no means the only way a clipboard transfer might happen. In this section, we use the term "conversion routines" to mean a combination of any XmNconvertCallback routines of the application and the convertProc trait method of the source widget.

Following are the steps:

  1. The user selects some portion of the source widget. The source widget will probably highlight the selection.

  2. The user presses some mouse button or key sequence to initiate a clipboard cut or a clipboard copy. The Intrinsics respond to the event by calling the appropriate action method in the source widget.

  3. The called action method itself calls XmeClipboardSource.

  4. XmeClipboardSource asks the conversion routines to provide a list of _MOTIF_CLIPBOARD_TARGETS.

  5. The conversion routine returns the list of immediate clipboard targets that it intends to place on the clipboard. The conversion routine should not include TARGETS in this list.

  6. XmeClipboardSource places the list of _MOTIF_CLIPBOARD_TARGETS on the clipboard.

  7. XmeClipboardSource calls the conversion routines one time for each target in the list of _MOTIF_CLIPBOARD_TARGETS. So, for example, if four targets were returned in _MOTIF_CLIPBOARD_TARGETS, XmeClipboardSource calls the conversion routines four times, each time asking the conversion routine to convert the selection to a different immediate target.

  8. XmeClipboardSource asks the conversion routines to provide a list of _MOTIF_DEFERRED_CLIPBOARD_TARGETS.

  9. Regardless of the number of deferred clipboard targets, XmeClipboardSource calls the conversion routines only once. This call asks the conversion routines to convert the selection to the _MOTIF_SNAPSHOT target.

  10. The conversion routines convert the _MOTIF_SNAPSHOT target by saving a snapshot of the data and by returning a distinguisher atom that uniquely identifies the snapshot.

  11. XmeClipboardSource copies each deferred clipboard target to the clipboard, but it does not copy any converted data to the clipboard. For each deferred clipboard target, XmeClipboardSource sets the selection member to the distinguisher atom.

  12. If the user had requested a clipboard cut operation, XmeClipboardSource asks the conversion routine to convert the selection to the DELETE target. The conversion routine responds to this target by deleting the selection.

  13. XmeClipboardSource establishes a callback that is automatically called whenever a request is made to convert data to a deferred clipboard target. We will call that callback CallbackD.

  14. The user moves the cursor to the destination widget and presses some mouse button or key sequence to initiate a clipboard paste.

  15. The Intrinsics call the action method of the destination widget that is associated with the clipboard paste event. This action method calls XmeClipboardSink.

  16. XmeClipboardSink calls the destination widget's destinationPreHookProc trait method, if any.

  17. XmeClipboardSink then calls the destination routines associated with the destination widget. The first destination routines called are the XmNdestinationCallback procedures that the application has associated with the destination widget. The next destination routine called is the destinationProc trait method of the destination widget. Typically, one of the destination routines will ask the clipboard to return its list of TARGETS.

  18. The clipboard responds to a TARGETS request by returning a list of all the targets in _MOTIF_CLIPBOARD_TARGETS and _MOTIF_DEFERRED_CLIPBOARD_TARGETS, plus the two additional targets TIMESTAMP and TARGETS.

  19. The destination routine examines the returned list of TARGETS and requests the most desirable one from the clipboard.

  20. The clipboard examines the request. If the requested target is an immediate clipboard target, the clipboard transfers the data back to the destination routine. Skip to Step 22.

  21. If the requested target is a deferred clipboard target, UTM calls CallbackD, which was the callback established by XmeClipboardSource to handle this kind of request.

  22. CallbackD asks the conversion routines to convert the requested target. CallbackD will set the selection member to the distinguisher atom.

  23. The conversion routine locates the snapshot data and converts it to the requested target.

  24. UTM transfers the converted data to the transfer procedure of the destination routine.

  25. When the snapshot data is no longer needed, CallbackD asks the conversion routines to convert the snapshot to the DONE target, using the distinguisher atom as the selection.

  26. UTM returns control to the transfer procedure associated with a destination routine. The transfer procedure must paste the transferred data into the appropriate place in the destination widget.

Sample Translations for a Clipboard Transfer

The ExmStringTransfer demonstration widget supports clipboard transfer. The following translations are relevant to clipboard transfer:

:<Key>osfPaste: ExmStringTransferPasteClipboard()\n\
:<Key>osfCut: ExmStringTransferCopyClipboard()\n\
:s <Key>osfInsert: ExmStringTransferPasteClipboard()\n\
:s <Key>osfDelete: ExmStringTransferCopyClipboard()\n\

To copy the text in an ExmStringTransfer widget to the clipboard, a user can either press the osfCut or osfDelete virtual keys. Despite their ominous names, these keys do not remove or alter any text in the ExmStringTransfer widget. A user pastes data from the clipboard to an ExmStringTransfer widget by pressing the Shift osfInsert or Shift osfDelete virtual keys.

A Sample Copy Clipboard Routine

The clipboard copy routine of ExmStringTransfer is shown as follows:

static void
ExmStringTransferCopyClipboard(
     Widget w,
     XEvent *event,
     String *params,
     Cardinal *num_params)
{
  Time time;

  /* First we must obtain a timestamp. This is required for
     ICCCM compliant ownership of a selection. We use
     XtLastTimestampProcessed which holds the timestamp
     from the last event the Intrinsics saw with a timestamp. */
  time = XtLastTimestampProcessed(XtDisplay(w));

  /* When we call XmeClipboardSource,  either the Motif clipboard
     will request the current selection data or an external
     clipboard manager will obtain the data. When the data is
     obtained, the CLIPBOARD selection will be owned by the
     data holder */
  XmeClipboardSource(w, XmCOPY, time);
}

A Sample Paste Clipboard Routine

The clipboard paste routine of the ExmStringTransfer widget is shown as follows:

static void
ExmStringTransferPasteClipboard(
     Widget w,
     XEvent *event,
     String *params,
     Cardinal *num_params)
{
  /* Calling XmeClipboardSink will start the process of requesting
     the CLIPBOARD selection to be pasted using the transfer trait
     destination callback for this widget class */
  XmeClipboardSink(w, XmCOPY, NULL);
}

Drag and Drop Transfer Details

Drag and drop is similar to primary transfer. Like primary transfer, drag and drop transfers the selection directly to another widget without going through a clipboard. Unlike primary transfer, drag and drop provides sophisticated visuals to help the user understand various details of the transfer. For example, the source icon of a drag and drop transfer symbolizes the kind of object being dragged.

As a widget writer, you are responsible for supplying the UTM underpinnings of a drag and drop operation.You may also provide various drag and drop icons; however, the application or user may override your choices. This chapter focuses on the UTM underpinnings of drag and drop. That is, we will focus on the kinds of activities that widget writers have to do in order to implement drag and drop in a widget. We will not focus on the large family of drag and drop visuals. (For information on these topics, see the Motif Programmer's Guide.)

Compatibility with Previous Releases

Drag and drop was introduced in Motif Release 1.2. If you wrote a drag and drop application or widget at Release 1.2, it should continue to work at Motif Release 2.0. However, if you are writing a widget at Motif Release 2.0, you should use UTM to implement any drag and drop features.

We recommend converting any existing pre-UTM drag and drop code to UTM.

Drag and Drop Transfers: Step by Step

This section takes you step by step through a typical drag and drop transfer. Remember that this is a typical transfer, but it is by no means the only way a drag and drop transfer might happen. In this section, we use the term "conversion routines" to mean a combination of any XmNconvertCallback routines of the application and the convertProc trait method of the source widget.

Following are the steps:

  1. The user selects some portion of the source widget. The source widget will probably highlight the selection.

  2. The user presses some mouse button or key sequence to initiate a drag operation. Typically, the user initiates a drag operation by pressing Btn2Down. The Intrinsics respond to the event by calling the appropriate action method in the source widget.

  3. The called action method itself calls XmeDragSource . The call to XmeDragSource can specify any XmDragContext resources. (XmeDragSource will ultimately instantiate an XmDragContext widget.)

  4. XmeDragSource asks the conversion routines to provide a list of _MOTIF_EXPORT_TARGETS.

  5. The conversion routines must respond to this request by returning a list of draggable targets. This list may or may not consist of all the targets that the conversion routines can convert. Typically, this list consists only of the targets that the destination widget is most likely to request.

  6. XmeDragSource calls XmDragStart, which in turn instantiates an XmDragContext. The XmDragContext displays the appropriate drag icons. XmeDragSource sets the XmNexportTargets resource of XmDragContext to the list of draggable targets. Similarly, XmeDragSource sets the XmNnumExportTargets resource of XmDragContext to the number of draggable targets.

  7. The user drags these icons towards the destination widget. The user typically does this by continuing to press Btn2Down while moving the mouse.

  8. When the user is over the destination widget, the user releases some mouse button or presses some key sequence to initiate a drop operation. Typically, the user initiates the drop operation by releasing the mouse button, generating Btn2Up.

  9. If the destination widget has not been registered as a drop site, UTM will reject the attempted drop and the transfer will end. If the destination widget has been registered as a drop site, the transfer will continue. A widget registers itself as a drop site by calling XmeDropSink. Note that the widget calls XmeDropSink when the widget is first instantiated, not as a response to the Btn2Up event. XmeDropSink is the UTM interface to the XmDropSite registry.

  10. UTM calls the destination widget's destinationPreHookProc trait method, if any. UTM passes an XmDestinationCallbackStruct as the call_data argument.

  11. UTM then calls the destination routines associated with the destination widget. The first destination routines called are the XmNdestinationCallback procedures that the application has associated with the destination widget. The next destination routine called is the destinationProc trait method of the destination widget. UTM passes a pointer to an XmDropProcCallbackStruct in the destination_data member of the XmDestinationCallbackStruct (The members of the XmDestinationCallbackStruct are documented in the XmDropSite reference page.)

  12. A destination routine typically calls XtGetValues on the XmNexportTargets resource of the XmDragContext widget.

  13. A destination routine examines the returned XmNexportTargets and calls XmTransferValue to request the most desirable one. If none of the targets in XmNexportTargets are desirable, a destination routine will call XmTransferValue and ask for the complete list of TARGETS. (Remember that XmNexportTargets does not necessarily hold a complete list of targets, but TARGETS does.)

  14. UTM asks the conversion routines of the source widget to convert the requested target.

  15. A conversion routine attempts to convert the selection to the desired target.

  16. When the conversion is complete, UTM transfers the converted data to the transfer procedure of the destination widget.

  17. The transfer procedure will find the converted selection in the value member of the XmSelectionCallbackStruct. The transfer procedure need only paste the converted selection into the appropriate place in the destination widget.

Sample Translations for a Drag and Drop Transfer

ExmStringTransfer provides only one drag and drop translation, which is as follows:

<Btn2Down>:             ExmStringTransferProcessDrag()\n\

Notice that there is no explicit action method associated with Btn2Up. In other words, ExmStringTransfer does not need to provide code that detects when the user is signalling to do a drop. Instead, ExmStringTransfer simply registers itself as a potential drop site. Once registered, the drag and drop system built into Motif will catch the Btn2Up event.

To register itself as a potential drop site, ExmStringTransfer has to specify a list of its import targets. That is, the widget must describe the kinds of targets that can be copied into it. The majority of the RegisterDropSite method establishes this import target list. The actual registration of the import targets as a drop site takes place through the XmeDropSink call.

A Sample Drag Action

The ExmStringTransferProcessDrag method initiates a drag operation by initializing the XmDragContext. (See the Motif Programmer's Guide for more information on XmDragContext.) The code for ExmStringTransferProcessDrag appears as follows:

static void
ExmStringTransferProcessDrag(
     Widget w,
     XEvent *event,
     String *params,
     Cardinal *num_params)
{
 ExmStringTransferWidget stw = (ExmStringTransferWidget) w;
 Arg args[4];
 Cardinal n;

  if (! stw -> string_transfer.own_primary) {
    ExmStringTransferPastePrimary(w, event, params, num_params);
    return;
  }

  /* Initialize DragContext resources. We want the drag icon to
     indicate text, and we support only the COPY operation. */
  n = 0;
  /* Normal drag and drop behavior will be to use the foreground
     and background of the widget when creating the cursor or
     or the pixmap drag icon */
  XtSetArg(args[n], XmNcursorBackground, stw->core.background_pixel);  n++;
  XtSetArg(args[n], XmNcursorForeground, stw->primitive.foreground);  n++;
  /* We use the default text drag icon, the same as used in
     the standard Motif widgets. */
  XtSetArg(args[n], XmNsourceCursorIcon, XmeGetTextualDragIcon(w));  n++;
  XtSetArg(args[n], XmNdragOperations, XmDROP_COPY); n++;
  (void) XmeDragSource(w, NULL, event, args, n);
}

XmeDragSource ultimately creates a DragIcon; the first three resources—XmNcursorBackground, XmNcursorForeground, and XmNsourceCursorIcon—control the appearance of that DragIcon. The convenience function XmeGetTextualDragIcon gets the standard Motif textual DragIcon If you are writing a widget that is dragging something other than text, then you would specify a different DragIcon.

Sample Action to Register a Drop Site

ExmStringTransfer can be a drop site. To be a drop site, the widget's Initialize method calls a routine named RegisterDropSite. RegisterDropSite builds a list of drop (import) targets and then calls XmeDropSink. The RegisterDropSite routine is as follows:

static void
RegisterDropSite(
     Widget w)
{
  Atom TEXT = XInternAtom(XtDisplay(w), "TEXT", False);
  Atom COMPOUND_TEXT = XInternAtom(XtDisplay(w), "COMPOUND_TEXT", False);
  Atom LOCALE_ATOM = XmeGetEncodingAtom(w);
  Atom MOTIF_C_S = XInternAtom(XtDisplay(w), "_MOTIF_COMPOUND_STRING", False);
  Atom targets[5];
  Arg args[2];
  int n, nt;

  /* Set up import targets. These are the targets from which we can
     generate a compound string when a drop is made. */
  nt = 0;
  targets[nt++] = MOTIF_C_S;
  targets[nt++] = COMPOUND_TEXT;
  targets[nt++] = LOCALE_ATOM;
  if (LOCALE_ATOM != XA_STRING) {
    targets[nt++] = XA_STRING;
  }
  if (LOCALE_ATOM != TEXT) {
    targets[nt++] = TEXT;
  }
  /* If you add any more targets, bump the array size. */

  n = 0;
  XtSetArg(args[n], XmNimportTargets, targets); n++;
  XtSetArg(args[n], XmNnumImportTargets, nt); n++;
  XmeDropSink(w, args, n);
}

According to the code, the user can drop any one of several different standard textual targets into the widget.

Writing a Conversion Routine

This section explains how to write a conversion routine that can convert various targets. In this section, we will examine how ExmStringTransfer implemented its convertProc trait method.

Although we have assumed that you are a widget writer working on a convertProc trait method, the conversion information in this section also applies to an application programmer writing an XmNconvertCallback procedure. Throughout this section, we use the term "conversion routine" to mean either a convertProc trait method or an XmNconvertCallback procedure.

When writing a conversion routine, you must do the following tasks:

  1. Declare Atom variables for every atom that the conversion routine uses.

  2. Provide code that converts requests for lists of targets supported by your widget. For example, your conversion routine must be able to return a list of TARGET atoms.

  3. Provide code that converts all the standard targets.

  4. If your widget can be the source of textual data, then your conversion routine should be able to convert the standard textual targets.

  5. Provide code that converts any other targets supported by your widget.

  6. Provide code that properly handles requests for targets not supported by your widget.

The remainder of this section explores all of the preceding tasks.

Declare Atoms

A conversion routine must declare Atom variables for every target referenced in the convert procedure. The ConvertProc trait method of ExmStringTransfer used XInternAtom as follows to create most of its Atom variables:

enum { CLIPBOARD, TEXT, COMPOUND_TEXT, FOREGROUND,
       BACKGROUND, TARGETS, MOTIF_DROP,
       MOTIF_C_S, MOTIF_EXPORT_TARGETS,
       CLIPBOARD_IMMEDIATE, LOSE_SELECTION,
       /* special values */ LOCALE_ATOM, NUM_ATOMS };
char* atom_names[] = {
    XmSCLIPBOARD, XmSTEXT, XmSCOMPOUND_TEXT, "FOREGROUND",
    "BACKGROUND", XmSTARGETS, XmS_MOTIF_DROP,
    XmS_MOTIF_COMPOUND_STRING, XmS_MOTIF_EXPORT_TARGETS,
    XmS_MOTIF_CLIPBOARD_TARGETS, XmS_MOTIF_LOSE_SELECTION };
Atom atoms[NUM_ATOMS];

assert(XtNumber(atom_names) == NUM_ATOMS - 1);
XInternAtoms(XtDisplay(w), atom_names, NUM_ATOMS - 1, False, atoms);
atoms[LOCALE_ATOM] = XmeGetEncodingAtom(w);

Wherever the code referenced one of these atoms (say "MOTIF_C_S"), that would be replaced by an index into the array ("atoms[MOTIF_C_S]"). The LOCALE_ATOM was the only exception; ConvertProc called XmeGetEncodingAtom as follows to get the LOCALE_ATOM:

Atom LOCALE_ATOM = XmeGetEncodingAtom(w);

Provide Target Lists

The ICCCM insists that source widgets respond to a TARGETS request by returning a list of all the target atoms that your widget can convert. If your widget serves as a source for a clipboard transfer, then your conversion routine must also be able to handle the request for a list of _MOTIF_CLIPBOARD_TARGETS and _MOTIF_DEFERRED_CLIPBOARD_TARGETS. (See Section 10.5 for more details on this target.) If your widget serves as a source for a drag and drop transfer, then your conversion routine must also be able to handle the request for a list of _MOTIF_EXPORT_TARGETS. (See Section 10.6 for more details on this target.)

To help handle the TARGETS request, Motif provides the XmeStandardTargets routine. This routine carves out enough dynamic memory to hold a list of all the standard and nonstandard targets your widget supports. Then, this routine fills the first slots of this dynamic memory with the names of the standard targets. It is up to your code to fill in the remaining slots with the names of the nonstandard targets.

For example, the ExmStringTransfer widget needs to respond to TARGETS by returning a list consisting of all the standard targets and all the standard textual targets. The following code fragment shows how to build this list of targets:

XmConvertCallbackStruct *cs = (XmConvertCallbackStruct *) call_data;
...
 if (cs->target == TARGETS) {
   /* We convert the standard targets, plus up to five additional
      standard textual targets. */
   Atom *targs = XmeStandardTargets(w, 5, &n);

   targs[n] = MOTIF_C_S; n++;
   targs[n] = COMPOUND_TEXT; n++;
   targs[n] = LOCALE_ATOM; n++;
   if (LOCALE_ATOM != XA_STRING) {
     targs[n] = XA_STRING; n++;
   }
   if (LOCALE_ATOM != TEXT) {
     targs[n] = TEXT; n++;
   }

   value = (XtPointer) targs;
   type = XA_ATOM;
   length = n;
   format = 32;
   ...
 }

(ConvertProc will eventually assign the values of the value, type, length, and format variables to their respective XmConvertCallbackStruct member fields.)

Although TARGETS should include all the standard targets, the more specialized target list requests (such as _MOTIF_EXPORT_TARGETS) do not have to. If you do not want the standard targets to be part of a targets list, use XtMalloc instead of XmeStandardTargets; for example:

if (cs -> target == MOTIF_EXPORT_TARGETS) {
  Atom *targs = (Atom *) XtMalloc(sizeof(Atom) * 7);

Convert Standard Targets

All Motif source widgets should be able to convert requests for the following standard targets:

BACKGROUND 

Your widget transfers the value of Core's XmNbackground resource as type PIXEL.

CLASS 

Your widget finds the first shell in its widget hierarchy that has a WM_CLASS property and transfers the contents as text in the current locale.

CLIENT_WINDOW 

Your widget finds the first shell in the widget hierarchy and transfers its window as type WINDOW.

COLORMAP 

Your widget transfers the value of Core's XmNcolormap resource as type COLORMAP.

FOREGROUND 

Your widget transfers the value of Primitive's or Manager's XmNforeground resource as type PIXEL.

NAME 

Your widget finds the first shell in the widget hierarchy that has a WM_NAME property and transfers the contents as text in the current locale.

TARGETS 

Your widget transfers, as type ATOM, a list of all the targets your widget can provide.

TIMESTAMP 

Your widget transfers the timestamp used to acquire the selection as type INTEGER.

_MOTIF_RENDER_TABLE 

Your widget transfers the value of its render table resource if one exists, or else the default render table for the widget, as type STRING.

_MOTIF_ENCODING_REGISTRY 

Your widget transfers the source widget's encoding registry as type STRING. The value is a list of NULL separated items in the form of tag encoding pairs. This target symbolizes the transfer target for the Motif Segment Encoding Registry. Widgets and applications can use this registry to register text encoding formats for specified render table tags. Applications access this registry by calling XmRegisterSegmentEncoding and XmMapSegmentEncoding. A destination widget can request the _MOTIF_ENCODING_REGISTRY target when transferring an XmString between two applications.

Fortunately, your conversion routine does not have to write conversion code for most of these targets. Your conversion routine can call XmeStandardConvert, which automatically converts 9 of the 10 standard targets. XmeStandardConvert cannot convert TIMESTAMP; however, the Intrinsics can. Even though XmeStandardConvert can convert TARGETS, your own conversion routine should typically take responsibility for doing this conversion. That is, XmeStandardConvert will convert TARGETS by returning a list of all the standard targets only. This returned list will be insufficient if your conversion routine can also convert some nonstandard targets.

For example, the following code from the ConvertProc trait method of ExmStringTransfer demonstrates how to convert the standard targets:

XmConvertCallbackStruct *cs = (XmConvertCallbackStruct *) call_data;
  ...
  XmeStandardConvert(w, NULL, cs);
  if (cs -> value == NULL) /* could not convert the target */
    cs -> status = XmCONVERT_REFUSE;
  else /* successfully converted the target */
       cs -> status = XmCONVERT_DONE;

Convert Standard Textual Targets

Textual widgets vary a lot in the richness of the text they support. Some textual widgets only store the ASCII values of each character. Other textual widgets store a great wealth of information about the text, such as the fonts or the locale encoding. When a user tries to transfer data between two different kinds of textual widgets, there is no guarantee that the two widgets will share the same level of text "richness." For example, perhaps the source widget stores font information about its text, and the destination widget simply stores the ASCII value of each character.

The source widget must be able to supply the destination widget with text at the richest level that the source widget supports. In addition, the source widget must also be able to supply text at all poorer levels beneath that richest level. Here are the levels of text, from poorest to richest:

STRING 

The text includes only characters in ISO8859-1 plus TAB and NEWLINE. It is known as the C locale.

LOCALE_ENCODING 

This is a string in the specified locale. The value of LOCALE_ENCODING in the C locale is STRING.

TEXT 

This target returns either LOCALE_ENCODING or COMPOUND_TEXT.

COMPOUND_TEXT 

This is an encoding the X Windows System supplies to deal with strings that have multiple encodings.

_MOTIF_COMPOUND_STRING 

This is text in XmString format.

For example, a widget that displays text in XmString format must be able to convert requests from destination widgets for all five textual targets.

A selection owner can use XmbTextListToTextProperty or XwcTextListToTextProperty to convert text in its own locale to a text property. The type of the property is determined by the composition of the text and by the encoding style passed to XmbTextListToTextProperty. Encoding styles exist for converting text to STRING, COMPOUND_TEXT, and the encoding of the locale. Another encoding style specifies conversion to STRING if all the characters in the text can be so converted, or otherwise to COMPOUND_TEXT.

A Motif application that has text in compound strings can use XmCvtXmStringToCT to convert a compound string to compound text The application can then place the compound text in the requestor's property by using type COMPOUND_TEXT.

STRING, COMPOUND_TEXT, and the locale encoding can also be selection targets.To obtain a text selection in its own locale, an application can request conversion to one of these targets and can then call XmbTextPropertyToTextList or XwcTextPropertyToTextList to convert the returned property to text in the current locale. An application can also request conversion to TEXT, but there is no guarantee that it can convert the returned property to text in the current locale.

One possible strategy is first to request conversion to TARGETS. If one of the returned targets is the encoding of the current locale (as determined by a call to XmbTextListToTextProperty with an encoding style of XTextStyle), the application can request conversion to that target. Otherwise, if one of the returned targets is COMPOUND_TEXT, the application can request conversion to that target. If neither the locale encoding nor COMPOUND_TEXT is one of the returned targets, the application can request conversion to STRING or TEXT if the selection owner supports one of those targets.

A Motif application that has text in compound strings can request conversion of a selection to COMPOUND_TEXT and can then use XmCvtCTToXmString to convert the returned property to a compound string.

The ConvertProc trait method of ExmStringTransfer does not do these conversions itself. Instead, ConvertProc calls the ConvertCompoundString routine as follows to handle the text conversions:

if (cs->target == MOTIF_C_S ||
    cs->target == COMPOUND_TEXT || cs->target == TEXT ||
    cs->target == LOCALE_ATOM   || cs->target == XA_STRING) {
  /* Convert the compound string to the appropriate target. */
  cstatus = ConvertCompoundString(w, cstring, cs -> target, &value,
                                  &type, &format, &length, &nchars);

Targets with Side Effects for the Owner

Some targets have side effects for the owner. Among these targets are the following:

DELETE 

The owner deletes the selection and, if successful, returns a zero-length property of type NULL.

INSERT_SELECTION 

The requestor places in its specified window property a pair of atoms that names a selection and a target. The owner requests conversion of the specified selection to the specified target and places the result at the location of the selection named in the INSERT_SELECTION request. The owner then returns a zero-length property of type NULL. For example, the Motif Text widget uses this target with the destination selection when it asks the owner of the destination selection to insert the secondary selection at the destination.

INSERT_PROPERTY 

The requestor places in its specified window property some data to be inserted at the location of the selection named in the request. The owner then returns a zero-length property of type NULL.

_MOTIF_LOSE_SELECTION 

UTM sends this atom to the former owner of the selection when that selection is lost. Upon receiving this message, your widget is responsible for changing the visuals of the formerly selected region to indicate that it is no longer selected.

Summary of Target Conversion

The following fragment suggests the framework for an idealized convertProc trait method:

static void
ConvertProc(Widget w,
            XtPointer client_data,
            XtPointer call_data)
{
 /* Cast call_data to an XmConvertCallbackStruct. */
   XmConvertCallbackStruct *cs = (XmConvertCallbackStruct *)call_data;

 /* Declare Atom variables for all targets used in converProc */
   Atom TEXT = XInternAtom(XtDisplay(w), "TEXT", False);
   ...

 /* Handle requests for TARGETS by building a list of all standard
    targets and all X nonstandard targets. */
   nonstandard_targets = 5;
   if (cs->target == TARGETS)
     Atom *targs = XmeStandardTargets(w, nonstandard_targets, &n);

 /* If your widget supports CLIPBOARD transfer, handle requests for
    _MOTIF_CLIPBOARD_TARGETS and _MOTIF_DEFERRED_CLIPBOARD_TARGETS. */
   if (cs->target == _MOTIF_CLIPBOARD_TARGETS)
     XtMalloc(...) /* generate list of immediate clipboard targets */
   else if (cs->target == _MOTIF_DEFERRED_CLIPBOARD_TARGETS)
     XtMalloc(...) /* generate list of deferred clipboard targets */

 /* If your widget supports drag, handle requests for _MOTIF_EXPORT_TARGETS. */
   if (cs->target == _MOTIF_EXPORT_TARGETS)
     XtMalloc(...) /* generate list of draggable targets */

 /* If a previous conversion routine has supplied a list of some kind of
    target, merge it with the list your conversion routine has created. */
   if (cs->status == XmCONVERT_MERGE)
     XmeConvertMerge(cs->value, cs->type, cs->format, cs->length, cs);
     ...

 /* Handle requests for nonstandard targets by converting to the requested
    target. */
   ...

 /* Handle requests for standard targets by calling XmeStandardConvert. */
   XmeStandardConvert(w, NULL, ConvertCallbackStruct);

 /* Return the appropriate status. */
   if (conversion was not successful)
     cs->status = XmCONVERT_REFUSE;
   else if (conversion was successful)
     cs->status = XmCONVERT_DONE;
}

Writing a Destination Routine

A destinationProc trait method is usually a lot simpler to write than a convertProc trait method. A destinationProc need only do the following:

  • Accept or reject the user's request.

  • If the request is accepted, call XmTransferValue. For certain situations, the destinationProc may do a little analysis prior to calling XmTransferValue.

  • If the request is rejected, call XmTransferDone.

The remainder of this section examines how ExmStringTransfer implemented its destinationProc trait method.

The prototype of a destinationProc trait method is the same as for any Motif callback. In the call_data argument, Motif passes an XmDestinationCallbackStruct. Therefore, the opening lines of the DestinationProc of ExmStringTransfer are as follows:

static void
DestinationProc(
     Widget w,
     XtPointer client_data,
     XtPointer call_data)
{
 XmDestinationCallbackStruct *ds = (XmDestinationCallbackStruct *) call_data;

As in a convertProc trait method, a destinationProc trait method must declare Atom variables for all the targets it needs, for example:

Atom XA_MOTIF_DROP = XInternAtom(XtDisplay(w), "_MOTIF_DROP", False);
Atom TARGETS = XInternAtom(XtDisplay(w), "TARGETS", False);

The ds->operation field contains the name of the requested operation (link, move, or copy). ExmStringTransfer does not support the link operation, so DestinationProc uses the following code to reject a link request:

if (ds -> operation == XmLINK) {
  /* We don't support links. */
  XmTransferDone(ds -> transfer_id, XmTRANSFER_DONE_FAIL);
  return;
}

ExmStringTransfer can be a drop site. If the user has requested a drop, UTM will pass a pointer to an XmDropProcCallbackStruct in the destination_data member of the XmDestinationCallbackStruct; for example:

if (ds -> selection == XA_MOTIF_DROP) {
  XmDropProcCallbackStruct *cb =
          (XmDropProcCallbackStruct *) ds -> destination_data;

For most transfer mechanisms, a destinationProc needs to ask the source widget for its list of targets. However, this is not always necessary in a drag and drop operation because the DragContext holds this list in its XmNexportTargets resource. Therefore, DestinationProc gets the target list as follows:

n = 0;
XtSetArg(args[n], XmNexportTargets, &targets); n++;
XtSetArg(args[n], XmNnumExportTargets, &num_targets); n++;
XtGetValues(cb -> dragContext, args, n);

DestinationProc examines the returned list and picks the most desirable target. (See the next section for more details.) Then DestinationProc calls XmTransferValue to ask the source widget to convert the target.

XmTransferValue(ds -> transfer_id, target,
                (XtCallbackProc) TransferProc, NULL, 0);

If the user has requested a transfer other than drag and drop, DestinationProc needs to ask the source widget for its list of targets. DestinationProc does this by calling XmTransferValue as follows:

XmTransferValue(ds -> transfer_id, TARGETS,
                (XtCallbackProc) TransferProc, NULL, 0);

Writing a Transfer Procedure

The destination procedure is rather short; most of the serious work on the destination side is done in the widget's transfer procedure. The transfer procedure of ExmStringTransfer is called TransferProc. UTM calls TransferProc when the source has finished converting a target. TransferProc must do the following:

  • If the source widget has returned a list of TARGETS, then TransferProc must pick the most desirable target and then request it.

  • If the source widget has returned the specified target, then TransferProc must paste the new string into the ExmStringTransfer widget.

A transfer procedure has the same function prototype as any Motif callback. Motif passes the transfer procedure an XmSelectionCallbackStruct pointer as the client_data argument. The targets member of this callback structure holds the name of the target converted by the source widget. For example, TransferProc calls XmTransferValue as follows to request a particular target:

XmTransferValue(ss -> transfer_id, target,
                (XtCallbackProc) TransferProc, NULL, 0);

When the source widget finishes the conversion, UTM calls TransferProc again.

When the source widget finishes converting the desired target, TransferProc must paste the text into the ExmStringTransfer widget. To simplify things, TransferProc overwrites whatever text was previously displayed. For more sophisticated widgets, you will have to determine how to insert the transferred text without overriding the previous text.

ExmStringTransfer displays the contents of the ExmNcompoundString resource. This resource holds an XmString value. Therefore, TransferProc must take the text that the source widget transferred and convert it into XmString format. If the source widget transferred the text as an _MOTIF_COMPOUND_STRING target, then TransferProc will have an easy time converting to XmString. If the source widget has transferred the text as some other target, then the conversion will be more difficult. For example, the following code from TransferProc handles the situation where the source widget transferred the text as COMPOUND_TEXT:

if (ss -> type == COMPOUND_TEXT) {
    /* Convert compound text to a compound string.
     * Note that XmCvtCTToXmString does not convert a list of compound text
     * strings, so we will get only the first if there's more than one.
     * XmCvtCTToXmString expects a NULL-terminated compound text string,
     * so add a trailing NULL. */
    char *string;
    string = XtMalloc(ss -> length + 1);
   (void) memcpy(string, ss -> value, ss -> length);
    string[ss -> length] = ' ';
    cstring = XmCvtCTToXmString(string);
    XtFree(string);
    transferred = True;
}

TransferProc contains similar code that converts text in other targets to XmString format. Eventually, TransferProc uses XtSetValues to assign the transferred XmString to the ExmNcompoundString resource as follows:

if (transferred) {
    /* We have a compound string. Use it as the new value of
     * ExmNcompoundString. */
    Arg args[1];
    Cardinal n;
    n = 0;
    XtSetArg(args[n], ExmNcompoundString, cstring);  n++;
    XtSetValues(w, args, n);
}

Preferred Textual Targets

In a typical text transfer, the source widget is capable of converting multiple textual targets. The destination widget in a textual transfer needs to pick the richest of these textual targets. The routine in ExmStringTransfer that does this is called PreferredTarget. DestinationProc and TransferProc call PreferredTarget when the source widget returns its list of supported targets.

PreferredTarget uses the following algorithm to determine which textual target to pick. If the locale atom is present, the precedence order is as follows:

  1. _MOTIF_COMPOUND_STRING

  2. TEXT

  3. COMPOUND_TEXT

  4. locale atom

  5. STRING

If the locale atom is not present, PreferredTargets uses this order:

  1. _MOTIF_COMPOUND_STRING

  2. COMPOUND_TEXT

  3. STRING

The code in PreferredTarget that implements the precedence is as follows:

static Atom
PreferredTarget(
     Widget w,
     Atom *targets,
     Cardinal num_targets)
{
  ...
  int n;
  int cs_index = -1;
  int ct_index = -1;
  int locale_index = -1;
  int string_index = -1;
  int text_index = -1;
 /* Which targets can the source convert? Examine the returned targets. */
  for (n = 0; n < num_targets; n++) {
    if (targets[n] == MOTIF_C_S) cs_index = n;
    if (targets[n] == COMPOUND_TEXT) ct_index = n;
    if (targets[n] == TEXT) text_index = n;
    if (targets[n] == LOCALE_ATOM) locale_index = n;
    if (targets[n] == XA_STRING) string_index = n;
  }

 /* If the source supports the locale atom, specify the
    precedence order as follows. */
  if (locale_index >= 0) {
    if (cs_index >= 0) return targets[cs_index];
    if (text_index >= 0) return targets[text_index];
    if (ct_index >= 0) return targets[ct_index];
    if (locale_index >= 0) return targets[locale_index];
    if (string_index >= 0) return targets[string_index];
  } else {
 /* If the source does not support the locale atom, specify the
    precedence order as follows. */
    if (cs_index >= 0) return targets[cs_index];
    if (ct_index >= 0) return targets[ct_index];
    if (string_index >= 0) return targets[string_index];
  }

  return None;
}

Timestamps

Most UTM routines expect a time argument. To get that time argument, your widget should call XtLastTimestampProcessed. If you specify either CurrentTime or 0, UTM will automatically change the call to XtLastTimestampProcessed.

With one exception, each UTM conversion request must contain a different timestamp. The one exception is that conversion requests for a multiple transfer can share the same timestamp.

Transferring Multiple Targets

The data transfers we have looked at so far have all assumed that the destination widget wanted only one conversion from the source widget. For example, the destination might ask for the selection to be converted to TEXT or to _MOTIF_COMPOUND_STRING but not to both. However, there are some cases where the destination widget might want the selection converted to several different targets and then transferred. If the user tries to transfer a pixmap, the destination might want the source to transfer both a PIXMAP and a COLORMAP. For such cases, the destination could call XmTransferValue several times, each time asking for a new conversion. However, UTM does provide the following routines to implement a faster multiple transfer:

  • XmTransferStartRequest

  • XmTransferSetParameters

  • XmTransferSendRequest

The calling sequence to transfer multiple values is as follows:

  1. Call XmTransferStartRequest once to initiate the multiple transfer.

  2. Call XmTransferSetParameters if a subsequent call to XmTransferValue will transfer a value containing a parameter.

  3. Call XmTransferValue every time you need to transfer a value. For example, call XmTransferValue twice to transfer two values.

  4. Call XmTransferSendRequest once to mark the end of the multiple transfer.

The following code fragment demonstrates how a transfer procedure might request both a PIXMAP and a COLORMAP:

XmSelectionCallbackStruct  *scs;
...
XmTransferStartRequest(scs->transfer_id);
XmTransferValue(scs->transfer_id, PIXMAP,   TransferProc,
                     NULL, XtLastTimestampProcessed);
XmTransferValue(scs->transfer_id, COLORMAP, TransferProc,
                     NULL, XtLastTimestampProcessed);
XmTransferSendRequest(scs->transfer_id, XtLastTimestampProcessed);
...