X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation |
In this chapter:
Chapter: 23
The Uniform Transfer Model
This chapter describes the new features introduced in Motif 2.0 for transferring application data in a consistent and uniform manner.In Motif 1.2, whenever widget data needed to be transferred in the application, a distinct set of code had to be written depending upon the nature of the transfer. Since Motif widgets support several styles of data transfer - to the primary or secondary X Selection, to the Motif Clipboard, or to another widget through the Drag and Drop mechanisms - the task of providing a fully featured interface could be somewhat detailed and repetitive in the implementation. This was repetitive because the data to be transferred might in fact be the same in each case. It was certainly detailed, because several implementations would be required, one for each of the supported data transfer methods.
From Motif 2.0, the Uniform Transfer Model makes it possible to transfer data using any of the transfer methods using a single programming interface. The UTM is designed to allow applications to use common code for all the supported data transfer requirements. It is also designed to ease the way in which new transfer targets can be added to the system. All of the existing pre-Motif 2.0 transfer methodologies have been re-written to utilize the Uniform Transfer Model internally where appropriate.
The Motif data transfer utilities have also been interwoven with the Trait mechanisms. In the absence of transfer code written by the programmer, the Trait system steps in and provides default data transfer capability for a large range of built-in data types. Although the details of Traits are beyond the scope of this book, belonging as they do much more to a widget authors manual, suffice it to say that because of this default Trait behavior, the programmer only needs to consider writing Uniform Transfer Model code in order to transfer data in a new and application-specific format. The Traits will transfer everything else automatically, under the assumption that it is the current widget selection (whatever that may mean) which is the data to be transferred. The set of available built-in target types is given in Table 22-1 of Chapter 22, Drag and Drop, although not all built-in target types are transferrable in every widget context: what a widget can export or import by default is widget class specific.
Overview
The Uniform Transfer Model is implemented through two new callback resources, the XmNconvertCallback, and the XmNdestinationCallback, and a new procedure, the Transfer procedure, which can also be considered as just another callback, although it is not specified using widget resources. In essence, the XmNconvertCallback is responsible for exporting the data, the XmNdestinationCallback requests the import format, the Transfer procedure performs the actual import of the arriving data. The XmNdestinationCallback routine negotiates with the XmNconvertCallback to find the best data format, and then sets up the Transfer callback to do the work. Data is transferred to and from the source and destination through elements in the callback structure supplied to each callback. In addition to transferring data, the callbacks can also transfer protocol when and if required. The destination of the data transfer can request a list of target types in which the source is prepared to export data, as well as requesting data in a particular and specific target format. The source in turn can inform the destination of the available export data types, as well as actually delivering the data itself. There is thus a two-way communication between the source and destination: each callback may in fact fire more than once as the two ends of the transfer communicate and negotiate the preferred format in which the data is to be transferred. The XmNconvertCallback in particular may be called multiple times. The simple case, however, need only concern itself with a single set of transactions: the destination requests data in a specific format, and the source provides it.The XmNconvertCallback
The XmNconvertCallback resource is implemented into the Primitive widget class, and is inherited by all sub-classes. It is also implemented in the DrawingArea, Scale, and Container widget classes. The resource is thus available in all widget contexts where the user directly interacts - the widgets which the user can actually use, as opposed to a Manager which simply lays out other components. The callback is not available for Gadget classes.The XmNdestinationCallback
The XmNdestinationCallback is directly implemented into the List, Container, DrawingArea, Text and TextField widget classes. It may seem surprising that the callback is not implemented into a wider range of classes, but if we consider what it really means to have an XmNdestinationCallback in a widget class, the matter becomes clearer.Recall that the UTM encapsulates three distinct methods of data transfer: X selections, the Clipboard, and Drag-and-Drop. The destination of an X Selection transfer is via the X server, and this is also indirectly true for the Motif Clipboard, which is really a higher level view onto the X selection mechanisms. The destination of a Drag-and-Drop, however, is an application widget. In other words, the toolkit only needs to implement the XmNdestinationCallback into widget classes which are not only likely to be required as a drop site, but also in which it makes sense to allow foreign (non-toolkit) targets as the type of the data transfer.
This is not to say that data cannot be transferred to other widget classes using drag-and-drop: Motif implements some Trait mechanisms into other widget classes as well, and these implement transfer of the built-in Motif target types. For example, the Label (and derived classes) and Scale widgets handle data transfer using drag-and-drop by these default Trait mechanisms. Again, the details of the Trait implementation are beyond the scope of this book.
Exporting the Data
Each XmNconvertCallback is passed a pointer to an XmConvertCallbackStruct, which is defined in the header file <Xm/Transfer.h>. This must be included either directly or indirectly by the programmer; it can be loaded indirectly simply by including the general Motif header <Xm/Xm.h>, as this includes the header internally. The XmConvertCallbackStruct is defined as follows:At first sight, the structure is somewhat daunting. However, because not all of the elements of the structure are applicable in any given context (the structure unifies the requirements of three distinct data transfer types, and some of the elements are used by the toolkit to communicate with itself), programming the callback structure is not as complex as it looks. We will start by looking at the basic common elements before looking at specific data transfer types.typedef struct { int reason; XEvent *event; Atom selection; Atom target; XtPointer source_data; XtPointer location_data; int flags; XtPointer parm; int parm_format; unsigned long parm_length; Atom parm_type; int status; XtPointer value; Atom type; int format; unsigned long length; } XmConvertCallbackStruct;Firstly, we can deduce the type of the current data transfer if we need to by inspecting the selection element. This will be one of CLIPBOARD, _MOTIF_DROP, PRIMARY, or SECONDARY, expressed as an Atom. In other words, all the possible types of data transfer which the UTM supports.
Secondly, we place the data to transfer into the value element. At this address, we need to dynamically allocate memory to hold the transfer data, but we need not concern ourselves with freeing up the memory at the right point in the transfer process: the toolkit does this for us. It is assumed that value points to an allocated array of items of some kind - the number of such items in the array should be specified using the length element. The size of each element in the array is specified using the format element. This is not a simple sizeof (data) result, but a logical size: if you are transferring character-based data, format should be set to 8. For a list of short quantities, format should be set to 16. A list of long values is expressed by setting format to 32. A format of zero is reserved to mean that value contains no data. The logical type of the data being transferred, expressed as an Atom, is placed into the type element. If we define a new application-specific data transfer, we need to define a logical (and hopefully unique) name for it, and express this in the type field.
Thirdly, we need to deduce the actual data to transfer (although we already know that it is placed into the value element when we do export it). Normally this will be related to the widget selection in some way - the default Trait transfer mechanisms assume this - or to some application specific data. But it need not be. Depending upon the nature of the transfer, we may be simply asked to convert given data of one type into data of another: this XmNconvertCallback instance really is a converter. For example, the various negotiations between a source and destination might result in the destination asking the source to convert specific supplied data in a specific format. We will know if this has happened if the location_data field is not NULL. The format of the data to convert can be deduced from the target field. In principle, all we have to do is convert the data at location_data into the requested target format, and place the result into the value field, also specifying the size and length of the data at value using the format and length elements, as specified in the previous paragraph. In practice, the location_data will most likely be NULL: the interpretation of location_data is generally widget-class specific in any case. Whether converting supplied data or providing our own, this holds true: we export our data in the format as requested by the target field.
There is a special case. The destination may be requesting of us, the source, the list of available data formats in which we are prepared to export our application data. In this case, the target element will variously be the value TARGETS, _MOTIF_EXPORT_TARGETS, _MOTIF_CLIPBOARD_TARGETS, or _MOTIF_DEFERRED_CLIPBOARD_TARGETS expressed as an Atom. In response, we construct a list of Atoms representing the set of available export formats, and return this in the value field. The length field is set to the number of such Atoms, and the format field becomes 32, because Atoms are long quantities. Hopefully what happens next is that the destination will choose a preferred export target, and communicate this back to the source, whereupon the XmNconvertCallback is invoked again, this time with a specific target chosen from our supplied list. This part of the negotiation is described below in the Requesting the Data Format Section.
Exporting to the Clipboard
When transferring data to the Motif Clipboard, the parm, parm_format, parm_length, and parm_type elements come into play. This is the case when the target element is set to the Atoms representing _MOTIF_CLIPBOARD_TARGETS or _MOTIF_DEFERRED_CLIPBOARD_TARGETS. The parm element contains an array of Clipboard-specific elements specifying the type of clipboard transfer required. The elements will each be one of XmCOPY, XmMOVE, XmLINK. The param_length field specifies the number of such data elements, the param_format field gives the logical size of such elements. Again, 8 means char-sized array items, 16 means short, 32 means long. The param_type element specifies the logical type of the parm data. Much of all this is over-engineering: in practice, the parm_type field will be the constant Atom XA_INTEGER. These fields do however give a consistency to the data transfer process: they replicate the logic of the value/format/length/type fields so that incoming and outgoing data is transferred in a similar fashion, albeit in different elements of the callback structure. All of these parm fields are read-only in any case, and pretty much toolkit internal.Exporting to a Widget (Drag and Drop)
When exporting data in response to a drag-and-drop operation, the source_data element is operative. It points to an XmDragContext object, which gives the programmer a handle whereby you might specify any customised drag visuals you wish to associate with the data transfer. Drag Context resources are covered in Chapter 22, Drag and Drop. For other types of data transfer, the source_data element of the callback structure is simply NULL.The status field is also probably a touch of over-engineering, used for drag-and-drop transfers. Recall that there are also widget Traits which know how to convert built-in Motif target types. These are called if no XmNconvertCallback is specified by the programmer. They may, however, be called even if the programmer does supply a callback. Whether this is the case or not depends on the status element.
If status is XmCONVERT_MERGE, the Trait mechanisms are invoked after the callback, merging any data so produced with the results of the callback. This can be useful: if the destination requests data in a built-in format, we can leave the value element as NULL and set status to XmCONVERT_MERGE, so that the toolkit does all the work.
If status is XmCONVERT_DONE, we tell the toolkit not to invoke the Trait mechanisms: the result of the conversion is only the data we supply to the value field. For efficiency reasons, we should use this if the target type is application-specific, otherwise the Traits will go to the bother of working out that they don't know how to convert the data: it is much better if they are never called in the first place.
If status is XmCONVERT_REFUSE, we tell the toolkit that the conversion process is to terminate immediately after this callback without invoking any Traits. But then neither is data transfer effective. This is the transfer failure signal - we cannot transmit data as requested for whatever reason.
The default value of status is XmCONVERT_DEFAULT, which works out to have the same behavior as XmCONVERT_MERGE. Since the whole point of the Uniform Transfer Model is that the programmer really only needs to add callbacks when transferring new application-specific data types, you probably want to change this for a start. On the other hand, the default value as it stands makes the built-in behavior work, so we can excuse the implementation.
As an example, the following is a convert procedure which exports a date in the form of three integers. The procedure also exports the list of supported targets when requested.
void convert_callback ( Widget widget, XtPointer client_data, XtPointer call_data) { XmConvertCallbackStruct *cptr = (XmConvertCallbackStruct *) call_data; Atom TARGETS, EXPORTS, CB_TARGETS, DATE_TARGET; Atom *targets; Display *display = XtDisplay (widget); /* Intern the atoms */ TARGETS = XInternAtom (display, "TARGETS", False); EXPORTS = XInternAtom (display, "_MOTIF_EXPORT_TARGETS", False); CB_TARGETS = XInternAtom (display, "_MOTIF_CLIPBOARD_TARGETS", False); DATE_TARGET = XInternAtom (display, "APPLICATION_DATE", False); /* If the destination has requested the list of targets */ /* we return this as the convert data */ if ((cptr->target == TARGETS) || (cptr->target == CB_TARGETS) || (cptr->target == EXPORTS)) { /* A request from the destination for the supported */ /* data types we are willing to handle */ targets = (Atom *) XtMalloc ((unsigned) sizeof (Atom)); targets[0] = DATE_TARGET; cptr->type = XA_ATOM; cptr->value = (XtPointer) targets; cptr->length = 1; cptr->format = 32; /* Merge the target with the toolkit Trait-supported targets */ /* If we only wanted to export our own format, we would return */ /* XmCONVERT_DONE at this point */ cptr->status = XmCONVERT_MERGE; } else { /* A request from the destination for a specific data type */ if (cptr->target == DATE_TARGET) { /* An unspecified routine to fetch the date */ /* Presumably the current widget is a date field */ /* So we pass the widget parameter to fetch this */ void GetDate (Widget, short *, short *, short *); short *date; /* We don't worry about freeing this - the toolkit does it */ date = (short *) XtMalloc ((unsigned) 3 * sizeof (short)); GetDate (widget, &date[0], &date[1], &date[2]); cptr->value = (XtPointer) date; cptr->length = 3; cptr->type = cptr->target; /* As requested */ cptr->format = 16; /* 8 = char, 16 = short, 32 = long */ /* No point in calling the Trait mechanisms */ /* Since we have already handled the target */ cptr->status = XmCONVERT_DONE; } else { /* Presumably a Trait-supported built-in target */ /* Again, if not interested, return XmCONVERT_DONE */ cptr->status = XmCONVERT_MERGE; } } }
Requesting the Data Format
Turning to the destination site, each XmNdestinationCallback is passed a pointer to an XmDestinationCallbackStruct, which is defined in the <Xm/Transfer.h> header file. The structure is defined as follows:Just as not all elements of the XmConvertCallbackStruct are applicable in any given data transfer type, so the various fields of the XmNdestinationCallback become operative at different times.typedef struct { in reason; XEvent *event; Atom selection; XtEnum operation; int flags; XtPointer transfer_id; XtPointer destination_data; XtPointer location_data; Time time; } XmDestinationCallbackStruct;Firstly, the selection element specifies the type of data transfer, and it may have the values CLIPBOARD, PRIMARY, SECONDARY, or _MOTIF_DROP, expressed as an Atom.
Secondly, the flags element indicates whether the source of the data transfer is the same as the destination. Possible values are XmCONVERTING_SAME and XmCONVERTING_NONE, the latter value indicating different source and destination locations.
The transfer_id element is simply a unique identifier for the current transaction. This field is read-only. As far as application programming is concerned, it serves as a hook or identifier which is passed to further transfer functions which are discussed in the Transfer Routines section below.
The time field is a server timestamp, and is read-only and internal to the toolkit.
The location_data field specifies where the data is to be transferred. The interpretation of this field is widget-class specific. For example, if the destination is a Container, location_data points to an XPoint structure which describes the x, y coordinates of the transfer. If location_data is NULL, this should be interpreted as a request to deposit the transfer data at the current cursor location within the destination widget concerned.
The destination_data element provides information concerning the destination of the transfer operation. If the selection is _MOTIF_DROP, the callback has been invoked by an XmDropProc at a drop site, and the destination_data element points to an XmDropProcCallbackStruct. If selection is SECONDARY, destination_data contains an Atom which represents the format to which the selection owner believes that the data should be converted. Otherwise, for clipboard or primary selection, the element is NULL.
The operation field indicates the type of data transfer; for a clipboard transfer, it can have the values XmCOPY, XmLINK, or XmMOVE. Otherwise, the value is XmOTHER, and destination_data will indicate the type of transfer required where appropriate.
Transfer Routines
As far as application programming of drag-and-drop is concerned, much of the above information is irrelevant, and can be treated as toolkit internal. The average destination callback simply performs the following tasks:it either asks the source of the data to provide a list of available export targets, or asks for a specific export target,
it sets up a further routine which will perform the actual data import.
It does both of these things using the routine XmTransferValue(), which is defined as follows:
The transfer_id parameter is simply passed through from the transfer_id element of the XmDestinationCallbackStruct passed to the destination callback itself. The target parameter specifies the format in which the source XmNconvertCallback should export the data. Specially, it can be a request for the available list of export targets, and this is performed by passing TARGETS expressed as an Atom. The transfer_routine is a further procedure which will perform the actual data transfer; any application-specific data which is to be passed to the routine is specified in the client_data parameter. Lastly, the timestamp parameter is a unique server timestamp for the current transfer. The function XtLastTimestampProcessed() should be used to construct this value.void XmTransferValue ( XtPointer transfer_id, Atom target, XtCallbackProc transfer_routine, XtPointer client_data, unsigned long timestamp)As an example, the following code fragment illustrates an XmNdestinationCallback which requests the list of available targets from the source.
Despite the apparent complexity surrounding the various callback structures associated with the Uniform Transfer Model, and the very dry paragraphs which were required to describe them, when it boils down to a real example, the required application code turns out to be extremely simple. We ignore almost every field of the XmDestinationCallbackStruct passed to us: only the current transfer_id is of any real interest. We construct an Atom in order to query the list of targets at the source of the data, we specify a procedure to perform the actual data transfer, and finally send the request off to the source using the UTM routine XmTransferValue(). The procedure to perform the data transfer, transfer_procedure, is described in the following section. Whether requesting a list of available targets or asking for a specific data format, the code is essentially the same: only the Atom changes.void destination_callback ( Widget w, XtPointer client_data, XtPointer call_data) { extern void transfer_procedure (Widget, XtPointer, XtPointer); XmDestinationCallbackStruct *dptr = (XmDestinationCallbackStruct *) call_data; Atom TARGETS = XInternAtom (XtDisplay (w), "TARGETS", False); XmTransferValue ( dptr->transfer_id, TARGETS, transfer_procedure, NULL, XtLastTimestampProcessed (XtDisplay (w))); }
Importing the Data
A Transfer Procedure has to be prepared to perform two tasks. Its primary role is to collect the exported data passed to it from callback structure fields, and process this in an appropriate fashion. However, there is a second possibility: the source of the data may have sent a list of available export formats in response to a destination callback request, and it is also the responsibility of the transfer procedure to choose from the available options.Note that it is not strictly necessary to have separate destination and transfer procedures: a single callback could in fact perform all the destination side tasks, in which case the transfer procedure as specified through XmTransferValue() would be the destination callback itself. This however makes the destination callback unnecessarily complex, you would have to discriminate between the various states of the transfer using the callback reason field, and maintain a separate block of code in the callback for each case. Since logically the XmNdestinationCallback and the transfer procedure are performing separate tasks, we prefer to split the destination side into separate destination and transfer callbacks, and recommend that you do the same. In any case, the callback structure passed to a transfer and a destination callback is not the same: you would have to perform some suitable casting of the callback call_data to each type if you insist on maintaining a single destination side routine.
Each transfer procedure (as registered through XmTransferValue()) is passed an XmSelectionCallbackStruct when invoked. Again, the structure is defined in the header file <Xm/Transfer.h>. The definition is as follows:
The interpretation of each of the elements is exactly the same as the corresponding fields in the XmConvertCallbackStruct and XmDestinationCallbackStruct definitions. The value, length, and format fields specify the incoming data from the source of the transfer. The type of the transfer is expressed in the target element: this will either be a specific export data target, or TARGETS if the incoming data is a reply to a request for the supported range of export types.typedef struct { int reason; XEvent *event; Atom selection; Atom target; Atom type; XtPointer transfer_id; int flags; int remaining; XtPointer value; unsigned long length; int format; } XmSelectionCallbackStruct;The following example is a transfer procedure which is hoping to receive a date, as exported in the code of Example 23-1. On receipt of the date, it simply displays it under the assumption that this destination is a TextField widget.
The example is straight forward. The top half of the transfer_callback() returns to the source the preferred import target. The list of available options has been sent to the routine in the value element of the callback structure. All the routine has to do is pick from the list, and go back to the source asking for the preferred data format. The bottom half of the routine is the case where the source has sent the data in the preferred format. The callback only has to extract the data from the value field, and process it. The only other issue is how the callback terminates the transfer, whether if there has been an error, or if the data has all been processed. It does this through the routine XmTransferDone(), which is specified as follows:void transfer_callback ( Widget w, /* The destination widget */ XtPointer client_data, XtPointer call_data) { XmSelectionCallbackStruct *sptr = (XmSelectionCallbackStruct *) call_data; Atom TARGETS, EXPORTS, CB_TARGETS, DATE_TARGET; Display *display = XtDisplay (w); Atom *targets, choice; int i; choice = (Atom) 0; TARGETS = XInternAtom (display, "TARGETS", False); EXPORTS = XInternAtom (display, "_MOTIF_EXPORT_TARGETS", False); CB_TARGETS = XInternAtom (display, "_MOTIF_CLIPBOARD_TARGETS", False); DATE_TARGET = XInternAtom (display, "APPLICATION_DATE", False); if ((sptr->type == XA_ATOM) && ((sptr->target == TARGETS) || (sptr->target == CB_TARGETS) || (sptr->target == EXPORTS))) { /* The source has sent us a list of available data formats */ /* in which it is prepared to export the data */ /* We get to choose one */ /* The value field contains the list of available targets */ targets = (Atom *) sptr->value; for (i = 0; i < sptr->length; i++) { if (targets[i] == DATE_TARGET) { /* We like this: its our own preferred format */ choice = targets[i]; } } /* If the source is not prepared to export in a format */ /* of our choice, we can either let the toolkit handle it */ /* under the assumption its a built-in data type */ /* or we can signal that the transfer is no good */ /* Lets assume we are only interested in our own data transfer */ if (choice == (Atom) 0) { XmTransferDone (sptr->transfer_id, XmTRANSFER_DONE_FAIL); return; } /* On the other hand, if we have chosen a target */ /* We simply go back to the source asking for it */ XmTransferValue (sptr->transfer_id, choice, transfer_callback, /* Round we go again */ NULL, XtLastTimestampProcessed (display)); } else { /* The source has sent us a specific data format */ /* It **ought** to be DATE_TARGET, but better check... */ if (sptr->target == DATE_TARGET) { /* Three integers, we assume */ /* We really ought to check sptr->length */ short *date = (short *) sptr->value; char buf[32]; /* Not pretty, but it will do for here */ (void) sprintf (buf, "%d/%d/%d", date[0], date[1], date[2]); XmTextFieldSetString (w, buf); XmTransferDone (sptr->transfer_id, XmTRANSFER_DONE_SUCCEED); } else { /* We should not be here. Someone has written a */ /* UTM procedure which is out of step. */ XmTransferDone (sptr->transfer_id, XmTRANSFER_DONE_FAIL); } } }The transfer_id parameter is the unique handle on the current transfer, and is passed through from of the relevant callback structure element. The status field indicates why the transfer is terminated. Possible values are:void XmTransferDone (XtPointer transfer_id, XmTransferStatus status)XmTRANSFER_DONE_SUCCEED, XmTRANSFER_DONE_FAIL are self-evident. The value XmTRANSFER_DONE_DEFAULT requests that the Trait mechanisms take over to handle the current transfer target. XmTRANSFER_DONE_CONTINUE is complex to describe, involving as it does Motif _MOTIF_SNAPSHOT operations, which are part of the implementation of deferred clipboard data transfer. We are going to treat all this as entirely toolkit internal.XmTRANSFER_DONE_SUCCEED XmTRANSFER_DONE_FAIL XmTRANSFER_DONE_CONTINUE XmTRANSFER_DONE_DEFAULT
Batched Data Transfer
If we need to send multiple separate pieces of information as part of the data transfer, we can in principle simply call XmTransferValue() multiple times from within the UTM callbacks. This is not necessarily efficient or transfer-order safe. For example, if a source declares that it can support export multiple targets, the destination might choose not simply to pick one from the list, but decide to ask for more than one of the available target types. Each data type will involve a separate invocation of the convert and transfer callbacks at each end, so that many messages will go back and forth. A certain degree of synchronization is required to make all this work successfully.To get round this problem, there are two routines available which batch up XmTransferValue() requests. To initiate a new batch, call the routine XmTransferStartRequest(). Then call XmTransferValue() as needed. To end the batch, use the routine XmTransferSendRequest(). These routines are defined as follows:
These are extremely simple to use: the transfer_id is taken from the current callback data structure, and the time is specified using XtLastTimestampProcessed().void XmTransferStartRequest (XtPointer transfer_id) void XmTransferSendRequest (XtPointer transfer_id, Time time)
An Example
Example 23-4 creates two components: a SpinBox configured to display the date, and a TextField. The SpinBox we will treat as the source of the transfer, and the TextField as the destination.
The output of the example is as given in Figure 23-1./* utm.c: transfers data between a specimen SpinBox and ** a TextField using the Uniform Transfer Model */ #include <X11/Intrinsic.h> #include <Xm/Transfer.h> #include <Xm/RowColumn.h> #include <Xm/SpinB.h> #include <Xm/TextF.h> #include <Xm/Label.h> /* ** GetDate(): fetches the various elements of a date from multiple ** SpinBox TextField children. */ void GetDate (Widget text, short *day, short *month, short *year) { int d, m, y; Widget spinb = XtParent (text); XtVaGetValues (XtNameToWidget (spinb, "days"), XmNposition, &d, 0); XtVaGetValues (XtNameToWidget (spinb, "months"), XmNposition, &m, 0); XtVaGetValues (XtNameToWidget (spinb, "years"), XmNposition, &y, 0); *day = (short) d; *month = (short) m; *year = (short) y; } /* ** convert_callback(): exports data in the format requested ** by the destination of the data transfer. */ void convert_callback ( Widget widget, XtPointer client_data, XtPointer call_data) { XmConvertCallbackStruct *cptr = (XmConvertCallbackStruct *) call_data; Atom TARGETS, EXPORTS, CB_TARGETS, DATE_TARGET; Atom *targets; Display *display = XtDisplay (widget); /* Intern the atoms */ TARGETS = XInternAtom (display, "TARGETS", False); EXPORTS = XInternAtom (display, "_MOTIF_EXPORT_TARGETS", False); CB_TARGETS = XInternAtom (display, "_MOTIF_CLIPBOARD_TARGETS", False); DATE_TARGET = XInternAtom (display, "APPLICATION_DATE", False); /* If the destination has requested the list of targets */ /* we return this as the convert data */ if ((cptr->target == TARGETS) || (cptr->target == CB_TARGETS) || (cptr->target == EXPORTS)) { /* A request from the destination for the supported */ /* data types we are willing to handle */ targets = (Atom *) XtMalloc ((unsigned) sizeof (Atom)); targets[0] = DATE_TARGET; cptr->type = XA_ATOM; cptr->value = (XtPointer) targets; cptr->length = 1; cptr->format = 32; cptr->status = XmCONVERT_MERGE; } else { /* A request from the destination for a specific data type */ if (cptr->target == DATE_TARGET) { short *date; date = (short *) XtMalloc ((unsigned) 3 * sizeof (short)); GetDate (widget, &date[0], &date[1], &date[2]); cptr->value = (XtPointer) date; cptr->length = 3; cptr->type = cptr->target; /* As requested */ cptr->format = 16; /* 8 = char, 16 = short, 32 = long */ cptr->status = XmCONVERT_DONE; } else { /* Presumably toolkit built-in type */ /* Let the Traits take over */ cptr->value = (XtPointer) 0; cptr->length = 0; cptr->format = 0; cptr->type = cptr->target; cptr->status = XmCONVERT_MERGE; } } } /* ** transfer_callback(): performs the import of the data at ** the destination site. */ void transfer_callback ( Widget w, /* The destination widget */ XtPointer client_data, XtPointer call_data) { XmSelectionCallbackStruct *sptr = (XmSelectionCallbackStruct *) call_data; Atom EXPORTS, TARGETS, CB_TARGETS, DATE_TARGET; Display *display = XtDisplay (w); Atom *targets, choice; int i; choice = (Atom) 0; TARGETS = XInternAtom (display, "TARGETS", False); EXPORTS = XInternAtom (display, "_MOTIF_EXPORT_TARGETS", False); CB_TARGETS = XInternAtom (display, "_MOTIF_CLIPBOARD_TARGETS", False); DATE_TARGET = XInternAtom (display, "APPLICATION_DATE", False); if ((sptr->type == XA_ATOM) && ((sptr->target == TARGETS) || (sptr->target == CB_TARGETS) || (sptr->target == EXPORTS))) { /* The source has sent us a list of available data formats */ /* in which it is prepared to export the data */ /* We get to choose one */ /* The value field contains the list of available targets */ targets = (Atom *) sptr->value; for (i = 0; i < sptr->length; i++) { if (targets[i] == DATE_TARGET) { /* We like this: its our own preferred format */ choice = targets[i]; } } /* If the source is not prepared to export in a format */ /* of our choice, we can either let the toolkit handle it */ /* under the assumption its a built-in data type */ /* or we can signal that the transfer is no good */ /* Lets assume we are only interested in our own data transfer */ if (choice == (Atom) 0) { XmTransferDone (sptr->transfer_id, XmTRANSFER_DONE_FAIL); return; } /* On the other hand, if we have chosen a target */ /* We simply go back to the source asking for it */ XmTransferValue (sptr->transfer_id, choice, transfer_callback, /* Round we go again */ NULL, XtLastTimestampProcessed (display)); } else { /* The source has sent us a specific data format */ /* It **ought** to be DATE_TARGET, but better check... */ if (sptr->target == DATE_TARGET) { /* Three integers, we assume */ /* We really ought to check sptr->length */ short *date = (short *) sptr->value; char buf[32]; /* Not pretty, but it will do for here */ (void) sprintf (buf, "%d/%d/%d", date[0], date[1], date[2]); XmTextFieldSetString (w, buf); XmTransferDone (sptr->transfer_id, XmTRANSFER_DONE_SUCCEED); } else { /* We should not be here. Someone has written a */ /* convert procedure which is out of step. */ XmTransferDone (sptr->transfer_id, XmTRANSFER_DONE_FAIL); } } } /* ** destination_callbvack: simply asks the source for the ** set of formats it is prepared to send the data in, ** and sets up a transfer procedure to import the data when sent. */ void destination_callback ( Widget w, XtPointer client_data, XtPointer call_data) { XmDestinationCallbackStruct *dptr = (XmDestinationCallbackStruct *) call_data; Atom TARGETS = XInternAtom (XtDisplay (w), "TARGETS", False); XmTransferValue (dptr->transfer_id, TARGETS, transfer_callback, NULL, XtLastTimestampProcessed (XtDisplay (w))); } main (int argc, char *argv[]) { Widget toplevel, spin, rowcol; Widget day, month, year, text; XtAppContext app; Arg args[8]; int n; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); rowcol = XmCreateRowColumn (toplevel, "rowcol", NULL, 0); /* Create the SpinBox */ spin = XmCreateSpinBox (rowcol, "spin", NULL, 0); /* Create the Days field */ n = 0; XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++; XtSetArg (args[n], XmNcolumns, 2); n++; XtSetArg (args[n], XmNeditable, FALSE); n++; XtSetArg (args[n], XmNminimumValue, 1); n++; XtSetArg (args[n], XmNmaximumValue, 31); n++; XtSetArg (args[n], XmNposition, 1); n++; XtSetArg (args[n], XmNwrap, TRUE); n++; day = XmCreateTextField (spin, "days", args, n); XtManageChild (day); /* Create the Months field */ n = 0; XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++; XtSetArg (args[n], XmNcolumns, 2); n++; XtSetArg (args[n], XmNeditable, FALSE); n++; XtSetArg (args[n], XmNminimumValue, 1); n++; XtSetArg (args[n], XmNmaximumValue, 12); n++; XtSetArg (args[n], XmNposition, 1); n++; XtSetArg (args[n], XmNwrap, TRUE); n++; month = XmCreateTextField (spin, "months", args, n); XtManageChild (month); n = 0; XtSetArg (args[n], XmNspinBoxChildType, XmNUMERIC); n++; XtSetArg (args[n], XmNcolumns, 4); n++; XtSetArg (args[n], XmNeditable, FALSE); n++; XtSetArg (args[n], XmNminimumValue, 1900); n++; XtSetArg (args[n], XmNmaximumValue, 2100); n++; XtSetArg (args[n], XmNposition, 2000); n++; XtSetArg (args[n], XmNwrap, TRUE); n++; year = XmCreateTextField (spin, "years", args, n); XtManageChild (year); /* The destination of the data transfer */ n = 0; XtSetArg (args[n], XmNeditable, FALSE); n++; text = XmCreateTextField (rowcol, "drop-site", args, n); XtManageChild (text); /* Now program the UTM */ XtAddCallback (day, XmNconvertCallback, convert_callback, NULL); XtAddCallback (month, XmNconvertCallback, convert_callback, NULL); XtAddCallback (year, XmNconvertCallback, convert_callback, NULL); XtAddCallback (text, XmNdestinationCallback, destination_callback, NULL); XtManageChild (spin); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
It should be possible to transfer the formatted SpinBox date to the lower TextField by any of the supported transfer methods.
selecting any SpinBox TextField, pressing the COPY key, moving the cursor over the lower TextField, then press the PASTE key.
drag-and-drop out of one of any SpinBox TextField into the lower TextField
select data in any SpinBox TextField, then press the middle mouse button over the lower TextField.
Summary
The Uniform Transfer Model rationalises the various means of data transfer in the Motif toolkit into a coherent single methodology. The model is simple to understand: a single callback resource at the source of the data, a single callback resource at the destination. The destination callback negotiates with the source in order to determine the best import data format. It then sets up a transfer procedure to perform the actual data import. While a first glance at the callback structures associated with each of these procedures seems to indicate an unwarranted degree of complexity, in actual practice many of the data elements are internal to Motif, and are the means by which it unifies the various types of data transfer in the toolkit. The actual tasks we need to perform in order to transfer data turn out to be quite simple.
|