This chapter describes a way for the application to
interact with other applications. Data is placed on the clipboard,
where it can be accessed by other windows on the desktop regardless of
the applications with which they are associated.
Imagine a group of people in a room; the only way
for them to communicate is by writing messages on paper, placing the
paper on a clipboard, and passing the clipboard around. A single person
acts as the moderator and holds the clipboard at all times. If someone
wants to post a note, she writes the message on a slip of paper and
hands the message to the moderator. The note is now available for
anyone to read. However, those who read the message do not remove the
message from the clipboard; rather, they copy what was written. There
is no guarantee that anyone will want to look at any particular
message, but it is there nonetheless and will remain there until
someone writes a new one.
This scenario is the concept behind the Motif
clipboard: a data transfer mechanism that enables widgets to make data
available for other widgets, including those in separate applications.
Information of any size or type can be passed using the clipboard
interface. The most common example of this data transfer model is
cut and paste, a method by which the user can move or copy text
between windows. Here, the user interacts with a Text widget that
contains some text that she wishes to transfer to another Text widget.
The user first selects the text she wants to transfer by
clicking the left mouse button and dragging it across the entire area
to be copied. Then, she moves the pointer to the target widget and
pastes the text by clicking the middle mouse button. This is the
default cut and paste user model; the user may override it using
resources or keyboard equivalents. The actual method for performing
this task is not the point of discussion here.
This action causes the text to appear to be copied
to the new window. However, the text does not actually move; it is
copied to the clipboard, from which the second widget then copies it
into its own window. The original data may have been changed or
destroyed since it was sent to the clipboard, but that is of no concern
to the second widget.
An object that wishes to place data on the clipboard
or read data from it is called a client of the clipboard (one of
the people in our imaginary room). Since only one client may access the
clipboard at a time, whether it is storing or retrieving data,
requesting access to the clipboard implies "locking" it. If another
widget already has locked the clipboard, the client must wait and ask
for it again later (after the current holder has "unlocked" it).
Now, imagine that the people in the room have all
sorts of items besides text messages they wish to make available for
copy. Some may have pictures, records, tapes--anything. Their "cargo"
must be deliverable by the moderator to anyone who requests it. To deal
with this situation, the moderator must know what type of cargo she
will be handling. Therefore, certain information must be registered
with the moderator before cargo may be sent or received through the
clipboard mechanism. Once a particular cargo type is registered, anyone
may post or request such cargo to or from the moderator.
In the Motif toolkit, different types of cargo are
referred to as formats. With respect to the X server and client
applications, text messages are the most commonly used format of
clipboard messages and are therefore registered by default. There are
also other types that are automatically registered, such as integers. A
complete list is given in Section #sclipformat. Application-specific
data structures must be registered separately, perhaps on a
per-application basis. Once a new data type is registered, even clients
that exist on other computer architectures where data is not
represented identically (e.g., due to byte swapping) can use that data
type, since the clipboard registration handles the proper data
conversion.
There are some situations where it is impractical
to place complete information on the clipboard. Some people's cargo may
be "too heavy" for the clipboard to hold indefinitely. Other people may
have perishables that don't last very long. Still others may have
information that varies with the state of the world. For these cases,
the person with the special cargo may choose to leave only some
information about their cargo rather than the cargo itself. This
information might include its weight, type, name and/or reference
number, for example. Potential recipients may then examine the
clipboard and inquire about the cargo without having to get it or even
look at it. Only in the event that someone else wishes to obtain the
cargo is the original owner called upon to provide it.
In the Motif world, this scenario describes
clipboard data that is available by name. For example, if a
client wishes to place an entire file on the clipboard, it might choose
to register the file by name without providing the actual contents
unless someone requests it. This may save a lot of time and resources,
since it's possible that no one will request it. Referencing data this
way is very cheap and is not subject to expiration or obsolescence.
When posting messages by name, the client must
provide the clipboard with a callback function that returns the actual
data. This callback function may be called by the Motif toolkit at any
time, provided another client requests the data. If the data is
time-dependent or subject to other criteria (someone removed or changed
the file), the callback routine may respond accordingly.
The Motif clipboard functions are based on X's
Inter-Client Communications Conventions Manual (ICCCM). Knowledge of
these conventions will aid greatly in your understanding of how these
functions are implemented. However, knowledge of the implementation is
not required in order to understand the concepts involved here or to be
able to use the clipboard effectively through Motif's application
interface. This chapter does not address many of the issues involved
with the ICCCM and the lower-level Xlib properties that implement them.
Rather, it only addresses the highest level of interaction provided by
the Motif toolkit.
Also note that the clipboard is one of three
commonly used mechanisms to support interclient communication. There
are also the primary and secondary selections, which are
similar in nature, but are handled differently at the application and
user level. The Motif toolkit supports convenience routines that
interact with clipboard selections only. To use the other selection
mechanisms, you must use X Toolkit Intrinsics functions that were
discussed in Volume Four, X Toolkit Intrinsics Programming Manual
. Note, however, that the Text widget supports both mechanisms.
To introduce the application programmer's interface
(API) for the clipboard functions, we demonstrate how to handle simple
copy and retrieval of text. The cut and paste functions provided by the
Text widgets handle copy and retrieval from the clipboard in the manner
we are about to describe; they also support interaction with the
primary and secondary selection mechanisms. However, as pointed out in
Chapter 14, Text Widgets, these functions are usually reserved
for interactive actions taken by the user. Fortunately, Motif provides
many convenience functions that facilitate the task of dealing with the
clipboard for Text widgets. This section discusses the techniques used
by the Text widget when it interacts with the clipboard.
Let's begin with the short program in the source
code This program creates two PushButtons that have complementary
callback routines: to_clipbd() copies text to the clipboard
and from_clipbd() retrieves text from the clipboard. For this
example, the text copied to the clipboard is arbitrary; we happen to
use a string that represents the number of times the Copy to
Clipboard button is pressed. XtSetLanguageProc() is only
available in X11R5; there is no corresponding function in X11R4.
XmStringCreateLocalized() is only available in Motif 1.2;
XmStringCreateSimple() is the corresponding function in Motif 1.1.
/* copy_retrieve.c -- simple copy and retrieve program. Two * pushbuttons: the first places text in the clipboard, the other * receives text from the clipboard. This just demonstrates the * API involved. */ #include <Xm/CutPaste.h> #include <Xm/RowColumn.h> #include <Xm/PushB.h> static void to_clipbd(), from_clipbd(); main(argc, argv) int argc; char *argv[]; { Widget toplevel, rowcol, button; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); /* Initialize toolkit, application context and toplevel shell */ toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); /* manage two buttons in a RowColumn widget */ rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel, NULL); /* button1 copies to the clipboard */ button = XtVaCreateManagedWidget ("button1", xmPushButtonWidgetClass, rowcol, XtVaTypedArg, XmNlabelString, XmRString, "Copy To Clipboard", 18, /* strlen() + 1 */ NULL); XtAddCallback (button, XmNactivateCallback, to_clipbd, "text"); /* button2 retrieves text stored in the clipboard */ button = XtVaCreateManagedWidget ("button2", xmPushButtonWidgetClass, rowcol, XtVaTypedArg, XmNlabelString, XmRString, "Retrieve From Clipboard", 24, /* strlen() + 1 */ NULL); XtAddCallback (button, XmNactivateCallback, from_clipbd, NULL); /* manage RowColumn, realize toplevel shell and start main loop */ XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* copy data to clipboard. */ static void to_clipbd(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { unsigned long item_id = 0; /* clipboard item id */ int status; XmString clip_label; char buf[32]; static int cnt; Display *dpy = XtDisplayOfObject (widget); Window window = XtWindowOfObject (widget); char *data = (char *) client_data; sprintf (buf, "%s-%d", data, ++cnt); /* make each copy unique */ clip_label = XmStringCreateLocalized ("to_clipbd"); /* start a copy -- retry till unlocked */ do status = XmClipboardStartCopy (dpy, window, clip_label, CurrentTime, NULL, NULL, &item_id); while (status == ClipboardLocked); XmStringFree (clip_label); /* copy the data (buf) -- pass "cnt" as private id for kicks */ do status = XmClipboardCopy (dpy, window, item_id, "STRING", buf, (long) strlen (buf)+1, cnt, NULL); while (status == ClipboardLocked); /* end the copy */ do status = XmClipboardEndCopy (dpy, window, item_id); while (status == ClipboardLocked); printf ("Copied } static void from_clipbd(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { int status, private_id; char buf[32]; Display *dpy = XtDisplayOfObject (widget); Window window = XtWindowOfObject (widget); do status = XmClipboardRetrieve (dpy, window, "STRING", buf, sizeof (buf), NULL, &private_id); while (status == ClipboardLocked); if (status == ClipboardSuccess) printf ("Retrieved }The program uses the header file <Xm/CutPaste.h> to include the appropriate function declarations and various constants. Don't let the name of the file confuse you. CutPaste.h is derived from the phrase "cut and paste," which historically has been used to describe clipboard-type operations. The to_clipbd() callback routine uses the following clipboard functions to copy data to the clipboard:
XmClipboardStartCopy() XmClipboardCopy() XmClipboardEndCopy()Copying data to the clipboard is a three-phase process. Each of the functions locks the clipboard so that other clients cannot access it. Since locking the clipboard is done on a per-window basis, the object that locks the clipboard should have an associated window, which means that gadgets may not work. Gadgets happen to work in some cases because of their window-based widget parents. However, some of the clipboard functions use XtWindow() rather than XtWindowOfObject() to get the window of an object. These functions do not work for gadgets. When the clipboard is locked, only requests from objects with the same window ID can access the clipboard. Each time an object requests a lock on the clipboard, a counter is incremented so that matching unlock requests can be honored.
XmClipboardStartCopy() sets up internal
storage for the copy to take place, XmClipboardCopy() sends
the data to the clipboard, and XmClipboardEndCopy() frees the
internal supporting structures. When copying data to the clipboard,
including copies by name, all three functions must be used.
The from_clipbd() callback routine uses
XmClipboardRetrieveCopy() to retrieve data from the clipboard.
Only a single call is needed for the retrieval of short items, as in
this example. However, a three-step process similar to that for copying
data to the clipboard is required for the incremental retrieval of
large amounts of data. We will cover these functions shortly.
The syntax of the functions that copy data to the
clipboard is outlined below. Due to the intricacies involved in
providing data to the clipboard, these functions take a larger number
of parameters than you might expect from the simple examples given so
far. Later examples should clarify the intended usage of these
functions and their corresponding parameters. Each of the routines
takes a pointer to the Display and the Window
associated with the object making the clipboard request. These
parameters may be derived from any widget or gadget using
XtDisplayOfObject() and XtWindowOfObject().
XmClipboardStartCopy() takes the following
form:
int XmClipboardStartCopy(display, window, label, timestamp, widget, callback, item_id) Display *display; Window window; XmString label; Time timestamp; Widget widget; XmCutPasteProc callback; long *item_id;The widget and callback parameters are only used when registering data by name (see Section #sclipbyname). Although the label parameter is currently unused, its purpose is to label the data so that certain applications can view the contents of the clipboard. The timestamp identifies the server time when the cut took place (CurrentTime is the typical value). The item_id parameter is filled in by the toolkit and is returned to the client for use in subsequent clipboard function calls. This value identifies the item's entry in the clipboard.
XmClipboardCopy() has the following form:
int XmClipboardCopy(display, window, item_id, format_name, buffer, length, private_id, data_id) Display *display; Window window; long item_id; char *format_name; XtPointer buffer; unsigned long length; int private_id; int *data_id;
XmClipboardCopy() copies the data in
buffer to the clipboard. The format of the data is described by
the format_name parameter. This value is not a type,
but a string describing the type. For example, "STRING"
indicates that the data is a text string. The length
parameter is the size of the data. Text strings, can use strlen
(data).
The item_id parameter is the ID
returned by XmClipboardStartCopy(). The data_id
parameter returns the format ID. You may pass NULL for this
parameter if you are not interested in the value, however you may need
it for other functions. For example, you will need it if you wish to
withdraw an item from the clipboard. We will discuss this issue later
when we talk about registration by name. The private_id
parameter is an arbitrary number that is application-defined. The value
is passed back to various functions, including those that handle
calling by name, so we will address it further in Section #sclipbyname.
When copying is done, XmClipboardEndCopy()
is called to free the internal data structures associated with the
clipboard item. The routine takes the following form:
int XmClipboardEndCopy(display, window, item_id) Display *display; Window window; long item_id;The item_id parameter is the ID returned by the call to XmClipboardStartCopy().
The clipboard copy functions return one of three
status values: ClipboardSuccess, ClipboardLocked, or
ClipboardFail. If the client is successful in gaining access to the
clipboard, the routine returns ClipboardSuccess. If another
client is already accessing the clipboard, the clipboard is locked and
the client can loop repeatedly to attempt to gain access.
Motif keeps a stack of items that have been placed
on the clipboard using any of the clipboard functions. As of Release
1.1, the stack depth is set to two. If a third item is added, the older
of the other two is removed. Once a copy to the clipboard is complete,
you can undo it using XmClipboardUndoCopy(), which takes the
following form:
int XmClipboardUndoCopy(display, window) Display *display; Window window;Calling XmClipboardUndoCopy() twice undoes the last undo. Thus, undoing a copy simply swaps the two elements on the clipboard stack. You can remove an item that you have placed on the clipboard using XmClipboardWithdrawFormat(). This routine is discussed in Section #sclipbyname.
In the source code we retrieved the data stored on
the clipboard using the function XmClipboardRetrieve(). This
function takes the following form:
int XmClipboardRetrieve(display, window, format_name, buffer, length, num_bytes, private_id) Display *display; Window window; char *format_name; char *buffer; unsigned long length; unsigned long *num_bytes; int *private_id;When using XmClipboardRetrieve(), you must provide buffer space to retrieve the data. In our example, we know that the data is not very large, so we declared buffer to have 32 bytes, which is more than adequate. The length parameter tells the clipboard how much space is available in buffer. The num_bytes parameter is the address of an unsigned long variable. This value is filled in by XmClipboardRetrieve() to indicate how much data it gave us. The private_id parameter is the address of an int; its value is the same as the private_id parameter passed to XmClipboardCopy(). You can pass NULL as this parameter if you are not interested in it.
If the routine is successful in retrieving the data,
it returns ClipboardSuccess. If the clipboard is locked, the
function returns ClipboardLocked. A rare internal error may
cause the function to return ClipboardFail. If the routine
does not succeed, you can choose to loop repeatedly to attempt to
retrieve data.
One problem with XmClipboardRetrieve()
occurs when there is more data in the clipboard than buffer space to
contain it. In this case, the function copies only length
bytes into buffer and sets num_bytes to
the number of bytes it copied, which should be the same value as
length if not enough space is available. If this situation
arises, the function returns ClipboardTruncate to indicate
that it did not copy everything that is available. Since we cannot just
arbitrarily specify a larger data space without knowing how much data
there is, we have two choices: query the clipboard to find out how much
data there is or copy the data incrementally. There are advantages and
disadvantages to each method. Let's start by discussing incremental
retrieval.
To do an incremental retrieval, we need to introduce
two functions: XmClipboardStartRetrieve() and
XmClipboardEndRetrieve(). These functions are similar to the start
and end copy functions discussed earlier.
XmClipboardStartRetrieve() takes the following form:
int XmClipboardStartRetrieve(display, window, timestamp) Display *display; Window window; Time timestamp;This function locks the clipboard and notes the timestamp . Data placed on the clipboard after this time is considered invalid and the function returns ClipboardFailed. The constant CurrentTime is typically used as this value. It is also common to provide the timestamp found in an event structure when available. This technique is typically used when the clipboard retrieval is initiated as a result of an action or callback routine where an event structure is available. XmClipboardStartRetrieve() also allocates internal data structures to support the incremental retrieval operation. Once the function is called, multiple calls to XmClipboardRetrieve() can be made until it returns ClipboardSuccess. While the routine returns ClipboardTruncate , more data needs to be read and you should continue to call the function. Be careful to save the data that has already been retrieved before the next call to the function, or you may overwrite the old data and lose information.
Once all of the data has been retrieved, call
XmClipboardEndRetrieve(), which takes the following form:
int XmClipboardEndRetrieve(display, window) Display *display; Window window;This function unlocks the clipboard and frees the internal data structures. the source code shows a callback routine that retrieves data from the clipboard incrementally. The from_clipbd_incr() routine could replace the from_clipbd() callback routine in Example 17-1.
static void from_clipbd_incr(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { int status; unsigned total_bytes; unsigned long received; char *data = NULL, buf[32]; Display *dpy = XtDisplayOfObject (widget); Window window = XtWindowOfObject (widget); do status = XmClipboardStartRetrieve (dpy, window, CurrentTime); while (status == ClipboardLocked); /* initialize data to contain at least one byte. */ data = XtMalloc (1); total_bytes = 1; do { /* retrieve data from clipboard -- if locked, try again */ status = XmClipboardRetrieve (dpy, window, "STRING", buf, sizeof (buf), &received, NULL); /* reallocate data to contain enough space for everything */ if (!(data = XtRealloc (data, total_bytes + received))) { XtError ("Can't allocate space for data"); break; /* XtError may or may not return */ } /* copy buf into data. strncpy() does not NULL terminate */ strncpy (&data[total_bytes-1], buf, received); total_bytes += received; } while (status == ClipboardTruncate); if (data) data[total_bytes] = 0; /* NULL terminate */ if (status == ClipboardSuccess) printf ("Retrieved status = XmClipboardEndRetrieve (dpy, window); }The callback routine works regardless of the amount of data held by the clipboard. If the client placed an entire file on the clipboard, the routine would read all of it in 32-byte increments. It is probably wise to use a larger block size when retrieving data incrementally; the constant BUFSIZ is a good default choice. BUFSIZ is defined in <stdio.h>.
The primary advantage of using the incremental
retrieval method is that you do not need to allocate a potentially
large amount of memory at one time. By segmenting memory, you can reuse
some of it, or even discard it as each increment is read. This
technique is especially useful if you are scanning for specific data
and you have no intention of actually saving everything that you
retrieve.
The problem with incremental retrieval is that numerous round trips
to the server may be necessary in order to obtain the entire contents
of the clipboard. If you intend to save every bit of information you
retrieve, the most economical way to handle the retrieval is by reading
everything in one fell swoop. A single call to
XmClipboardRetrieve() is more convenient than the three-step
process involving locking the clipboard.
However, as pointed out earlier, we have a problem
since we do not know how much data there is to read. The solution to
the problem is to determine exactly how much data there is by using
XmClipboardInquireLength(). This routine has the following form:
int XmClipboardInquireLength(display, window, format_name, length) Display *display; Window window; char *format; unsigned long *length;The function returns the amount of data being held by the clipboard under the specified format_name. In Example 17-3, we are looking for data in the "STRING" format. If any data on the clipboard is in this format, the function returns ClipboardSuccess and the length parameter is set to the number of bytes being held. If there is no data on the clipboard in the specified format, the function returns ClipboardNoData. If length is not set to a value other than 0, the data cannot be read from the clipboard.
If XmClipboardInquireLength() is
successful, then the number of bytes specified by length
can be allocated and the data can be retrieved in one call to
XmClipboardRetrieve(). the source code shows a callback routine
that retrieves data from the clipboard after querying the size of the
data. The from_clipbd_query() routine could replace the
from_clipbd() callback routine in Example 17-1.
static void from_clipbd_query(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { int status, recvd, length; char *data; Display *dpy = XtDisplayOfObject (widget); Window window = XtWindowOfObject (widget); do status = XmClipboardInquireLength (dpy, window, "STRING", &length); while (status == ClipboardLocked); if (length == 0) printf ("No data on clipboard in specified format.0); data = XtMalloc (length+1); do status = XmClipboardRetrieve (dpy, window, "STRING", data, length+1, &recvd, NULL); while (status == ClipboardLocked); if (status != ClipboardSuccess || recvd != length) { printf ("Failed to receive all clipboard data0); XtFree (data); } else printf ("Retrieved }
As discussed earlier, there are cases where data
should not be copied to the clipboard until it is requested. It is
possible to copy data by name, so that the owner of the data is
notified through a callback function when the data is needed by the
clipboard. Since copying large amounts of data may be expensive,
time-consuming, or even impossible due to other constraints in an
application, copying data by name may be the only option available. The
technique is especially advantageous if the data is never requested,
since time and resources are saved.
The procedure for copying data by name is quite
similar to the procedure for normal copying. The application first
calls XmClipboardStartCopy(), but unlike a normal copy
operation, the callback and widget
parameters are specified. These values indicate that the data is to be
copied by name. The callback parameter specifies the
routine that is called when the data is requested by another client.
The widget parameter specifies the widget that receives
the messages requesting the data. Since the toolkit handles the
messages, any valid widget ID can be used.
XmClipboardCopy() is then called with a
buffer value of NULL. XmClipboardEndCopy()
is called as usual. When a client requests the data from the clipboard,
the callback routine provided to XmClipboardStartCopy() is
called and the application provides the actual data using
XmClipboardCopyByName().
You can use the convenience function
XmClipboardBeginCopy() instead of XmClipboardStartCopy().
The only difference between the two routines is that the convenience
function does not take a timestamp parameter; it simply
uses CurrentTime as the timestamp value.
The program shown in the source code demonstrates
copying data to the clipboard by name.
/* copy_by_name.c -- demonstrate clipboard copies "by-name". * Copying by name requires that the copy *to* clipboard * functions use the same window as the copy *from* clipboard * functions. This is a restriction placed on the API by the * toolkit, not by the ICCCM. */ #include <Xm/CutPaste.h> #include <Xm/RowColumn.h> #include <Xm/PushB.h> static void to_clipbd(), from_clipbd(); Widget toplevel; main(argc, argv) int argc; char *argv[]; { Widget rowcol, button; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); /* Initialize toolkit, application context and toplevel shell */ toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); /* manage two buttons in a RowColumn widget */ rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel, NULL); /* button1 copies to the clipboard */ button = XtVaCreateManagedWidget ("button1", xmPushButtonWidgetClass, rowcol, XtVaTypedArg, XmNlabelString, XmRString, "Copy To Clipboard", sizeof (char *), NULL); XtAddCallback (button, XmNactivateCallback, to_clipbd, NULL); /* button2 retrieves text stored in the clipboard */ button = XtVaCreateManagedWidget ("button2", xmPushButtonWidgetClass, rowcol, XtVaTypedArg, XmNlabelString, XmRString, "Retrieve From Clipboard", sizeof (char *), NULL); XtAddCallback (button, XmNactivateCallback, from_clipbd, NULL); /* manage RowColumn, realize toplevel shell and start main loop */ XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } static void copy_by_name(widget, data_id, private_id, reason) Widget widget; int *data_id; int *private_id; int *reason; { Display *dpy = XtDisplay (toplevel); Window window = XtWindow (toplevel); static int cnt; int status; char buf[32]; printf ("Copy by name called0reason: %s, private_id: %d, data_id: %d0, *reason == XmCR_CLIPBOARD_DATA_REQUEST? "request" : "delete", *private_id, *data_id); if (*reason == XmCR_CLIPBOARD_DATA_REQUEST) { sprintf (buf, "stuff-%d", ++cnt); /* make each copy unique */ do status = XmClipboardCopyByName (dpy, window, *data_id, buf, strlen (buf)+1, *private_id = cnt); while (status != ClipboardSuccess); } } /* copy data to clipboard */ static void to_clipbd(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { unsigned long item_id = 0; /* clipboard item id */ int status; XmString clip_label; Display *dpy = XtDisplay (toplevel); Window window = XtWindow (toplevel); clip_label = XmStringCreateLocalized ("to_clipbd"); /* start a copy. retry till unlocked */ do status = XmClipboardBeginCopy (dpy, window, clip_label, widget, copy_by_name, &item_id); while (status == ClipboardLocked); /* copy by name by passing NULL as the "data", copy_by_name() as * the callback and "widget" as the widget. */ do status = XmClipboardCopy (dpy, window, item_id, "STRING", NULL, 8L, 0, NULL); while (status == ClipboardLocked); /* end the copy */ do status = XmClipboardEndCopy (dpy, window, item_id); while (status == ClipboardLocked); } static void from_clipbd(widget, client_data, call_data) Widget widget; XtPointer client_data; XtPointer call_data; { int status; unsigned total_bytes; unsigned long received; char *data = NULL, buf[32]; Display *dpy = XtDisplay (toplevel); Window window = XtWindow (toplevel); do status = XmClipboardStartRetrieve (dpy, window, CurrentTime); while (status == ClipboardLocked); /* initialize data to contain at least one byte. */ data = XtMalloc (1); total_bytes = 1; do { buf[0] = 0; /* retrieve data from clipboard -- if locked, try again */ status = XmClipboardRetrieve (dpy, window, "STRING", buf, sizeof (buf), &received, NULL); if (status == ClipboardNoData) { puts ("No data on the clipboard"); break; } /* reallocate data to contain enough space for everything */ if (!(data = XtRealloc (data, total_bytes + received))) { XtError ("Can't allocate space for data"); break; /* XtError may or may not return */ } /* copy buf into data. strncpy() does not NULL terminate */ strncpy (&data[total_bytes-1], buf, received); total_bytes += received; } while (status == ClipboardTruncate); data[total_bytes-1] = 0; /* NULL terminate */ if (status == ClipboardSuccess) printf ("Retrieved data, total_bytes); status = XmClipboardEndRetrieve(dpy, window); }Just as in Example 17-1, the function to_clipbd() is used to initiate copying data to the clipboard. However, rather than passing actual data, we use:
status = XmClipboardBeginCopy (dpy, window, clip_label, widget, copy_by_name, &item_id);Passing a valid widget and a callback routine indicates that the copy-by-name method is being used. Here, the data is provided through the given callback routine when it is requested, rather than being provided immediately. The item_id parameter is filled in by the clipboard function to identify the particular data element. The parameter is then used in the call to copy data:
status = XmClipboardCopy (dpy, window, item_id, "STRING", NULL, 8L, 0, NULL);Passing NULL as the data also indicates that the data is passed by name. The value 8L is passed as the size parameter to indicate how much data will be sent if the data is requested. This value is important in case other clients query the clipboard to find out how much data is available to copy.
The callback function copy_by_name() is
called either when someone requests the data from the clipboard or when
another client copies new data (by name or with actual data) to the
clipboard. In the first case, the data must be copied to the clipboard;
in the second case, the clipboard is telling the client that it can now
free its data. The callback function is an XmCutPasteProc,
which takes the following form:
typedef void (*XmCutPasteProc) (Widget, * int, * int, * int) Widget widget; int *data_id; int *private_id; int *reason;The widget parameter is the same as that passed to XmClipboardStartCopy(). The data_id arguemnt is the ID of the data item that is returned by XmClipboardCopy(), and private_id is the private data passed to XmClipboardCopy(). The reason parameter takes the value XmCR_CLIPBOARD_DATA_REQUEST, which indicates that the data must be copied to the clipboard, or XmCR_CLIPBOARD_DATA_DELETE , which indicates that the client can delete the data from the clipboard. Although the last three parameters are pointers to integer values, the values are read-only and changing them has no effect.
The purpose of the function is either to send the
appropriate data to the clipboard or to free the data. The value of
reason determines which action is taken. Since no data is passed to
the clipboard until this callback function is called, either the data
must be stored locally (in the application) or the function must be
able to generate it dynamically. The example makes no assumptions or
suggestions about how to create the data, since it is entirely subject
to the nature of the data and/or the application.
Once the data is obtained, it is sent to the
clipboard using XmClipboardCopyByName(). This function does
not need to lock the clipboard since the clipboard is already being
locked by the window that called XmClipboardRetrieve(). At
this point in time, both routines are accessing the clipboard. If the
same application is both retrieving the data and copying the data, the
XmClipboardRetrieve() and XmClipboardCopyByName() routines
must use the same window for their respective window
parameters because otherwise deadlock will occur and the application
will hang. There may be cases where you should copy data to the
clipboard incrementally. The data may be large enough that allocating
one large data space to handle the entire copy is unreasonable; its
size may warrant sending it in smaller chunks. Moreover, data may be
generated by a slow mechanism such as a database library. If the
database only returns data in specific block sizes, then you need not
buffer them all up and send to the clipboard with one call; you can
send each block as it comes through.
Incremental copying requires multiple calls to
XmClipboardCopyByName(). Since XmClipboardCopyByName()
does not lock the clipboard, you need to do that yourself by calling
XmClipboardLock(). However, you only need to call it once no matter
how much data is transferred. When you are through copying the data,
you need to call XmClipboardUnlock(). In some cases, you may
need to stop sending data before the copy is complete. For example, if
the database is not responding to your application or there are other
extenuating circumstances, you may want to terminate the copy operation
using XmClipboardCancelCopy(), which has the following form:
int XmClipboardCancelCopy(display, window, item_id) Display *display; Window window; long item_id;When using XmClipboardCancelCopy, you should not unlock the clipboard using XmClipboardUnlock().
If you have copied data by name to the clipboard
under a specific data format, you may withdraw it by calling
XmClipboardWithdrawFormat(). The function takes the following form:
int XmClipboardWithdrawFormat(display, window, data_id) Display *display; Window window; int data_id;Despite the name of the procedure, its main purpose is not to remove a format specification, but to remove a data element in that format from the clipboard. The data_id parameter is the same value that is returned by XmClipboardCopy() when the data is initially copied by name. If the specified window holds the clipboard data but it is in a different format than that specified by data_id, then the data is not removed from the clipboard.
As discussed in the introduction, the clipboard can
contain data in arbitrary formats. While the most commonly used format
is text, other formats include integers, pixmaps, and arbitrary data
structures. Since all applications on the desktop have access to the
clipboard, any of them may register a new format and place items of
that type on the clipboard.
When registering a new format, you must also
register a corresponding format name and the format length in bits (8,
16, and 32). Determining the type of data on the clipboard is much
easier when there is a descriptive name associated with it. The length
allows applications to send and receive data without suffering from
byte-swapping problems due to differing computer architectures.
To register a new format, use
XmClipboardRegisterFormat(), which takes the following form:
int XmClipboardRegisterFormat(display, format_name, format_length) Display *display; char *format_name; unsigned long format_length;The function may return ClipboardBadFormat if the format name is NULL or the format length is other than 8, 16, or 32. The format length may be specified as 0, in which case Motif will attempt to look up the default length for the given name. shows the format lengths for some predefined format names.
tab(@), linesize(2); l | l lfCW | nfCW . Format
Name@Format Length
_
"TARGETS"@32 "MULTIPLE"@32 "TIMESTAMP"@32 "STRING"@8
"LIST_LENGTH"@32 "PIXMAP"@32 "DRAWABLE"@32 "BITMAP"@32 "FOREGROUND"@32
"BACKGROUND"@32 "COLORMAP"@32 "ODIF"@8 "OWNER_OS"@8 "FILE_NAME"@8
"HOST_NAME"@8 "CHARACTER_POSITION"@32 "LINE_NUMBER"@32
"COLUMN_NUMBER"@32 "LENGTH"@32 "USER"@8 "PROCEDURE"@8 "MODULE"@8
"PROCESS"@32 "TASK"@32 "CLASS"@8 "NAME"@8 "CLIENT_WINDOW"@32
_ Although these format names are known, they are not necessarily
registered automatically with the server; you may still need to
register the one(s) you want to use. If you are specifying your own
data structure as a format, you should choose an appropriate name for
it and use 32 as the format size.
The following code fragment shows how you can
register a data format and then copy data in that format to the
clipboard:
unsigned long item_id; int data_id; Display *dpy = XtDisplay (widget); Window window = XtWindow (widget); XmString label = XmStringCreateLocalized ("my data"); /* register our own data structure with clipboard. */ XmClipboardRegisterFormat (dpy, "MY_DATA_STRUCT", 32); /* use the copy-by-name method to transfer data to clipboard */ do status = XmClipboardStartCopy (dpy, window, label, CurrentTime, my_data_callback, widget, &item_id); while (status == ClipboardLocked); XmStringFree (label); /* don't need this anymore */ /* MY_DATA_SIZE is presumed to be the amount of data to transfer */ do status = XmClipboardCopy (dpy, window, item_id, "MY_DATA+STRUCT", NULL, MY_DATA_SIZE, 0, &data_id); /* save the data_id! */ while (status == ClipboardLocked); do status = XmClipboardEndCopy (dpy, window, item_id); while (status == ClipboardLocked);Once the "MY_DATA_STRUCT" format has been registered with the server, we follow the standard procedure for copying data to the clipboard. Here, we chose to use the copy-by-name method discussed earlier. Note that we save the value of the data_id returned by XmClipboardCopy(). This value is used so that we may withdraw the data later using XmClipboardWithdrawFormat() if necessary. Note that formats are never removed from the clipboard; only data can be removed from the clipboard. Once a particular format is registered with the clipboard, it is there until the server goes down. If you plan on retrieving data held by the clipboard, you may wish to inquire about the format of the data it is holding. To do so, you must use two functions together: XmClipboardInquireCount() and XmClipboardInquireFormat(). They take the following form:
int XmClipboardInquireCount(display, window, count, max_length) Display *display; Window window; int *count; int *max_length;
int XmClipboardInquireFormat(display, window, index, format_name_buf, buffer_len, copied_len) Display *display; Window window; int index; char *format_name_buf; unsigned long buffer_len; unsigned long *copied_len;XmClipboardInquireCount() returns the number of formats the clipboard knows about for the data item it is currently holding. Also returned is the string length of the longest format name. You can iterate through the formats starting from 1 (one) through count by calling XmClipboardInquireFormat(). The iteration number is passed as the index parameter. You should use this value to ensure that you can read all the format types in your search for the desired format.
Although there is only one data item stored on the
clipboard at any one time, that item may have multiple formats
associated with it. While this is unusual, it is possible to handle
this case by providing different formats to successive calls to
XmClipboardCopy() or XmClipboardCopyByName().
Since text is the most commonly used format in the clipboard, there
is a natural interaction between the clipboard and windows that contain
text. In most situations, it is usual (even expected) that when the
user selects text, the selection should be placed on the clipboard,
which is known as a copy operation. Retrieving text from the clipboard
and placing it in another window is known as a paste operation. In some
cases, after the data is pasted from the clipboard, the original window
deletes the data it copied, which is classified as a cut operation. The
clipboard uses what is commonly referred to as the cut and paste model.
The low-level implementation of the clipboard
mechanism uses the X Toolkit selection mechanism. This model has
additional properties that provide for more detailed communication
between the clients involved. For example, cutting text from a Text
widget and placing it in another widget involves more communication
between the widgets than that of the clipboard copy and retrieval
mechanism. When the text that was selected in the first widget is
pasted in the other, the first widget may be notified to delete the
selected text. This type of communication can be handled either
automatically by the Text widgets or through low-level X calls where
the corresponding windows of the widgets send real events called client
messages to one another.
In most cases, you should not need to access the clipboard functions
to perform simple text copy and retrieval (cut and paste) for Text
widgets. If you need to access the clipboard above and beyond the
normal selection mechanisms provided by the Text widgets, there are a
number of convenience routines that deal with selections automatically.
We present a brief overview of these functions here; see Chapter 14,
Text Widgets, for detailed information.
The XmTextCut(), XmTextCopy(), and
XmTextPaste() routines handles cutting, copying, and pasting
operations for the Text widget. There are also corresponding functions
for the TextField widget. XmTextCut() and XmTextCopy()
take the following form:
Boolean XmTextCut(widget, time) Widget widget; Time time;
Boolean XmTextCopy(widget, time) Widget widget; Time time;If there is text selected in the Text widget referred to by the widget parameter, the selected text is placed on the clipboard. For XmTextCut(), the selected text is also deleted from the Text widget, while for XmTextCopy() it is not. The functions return True if all of these things happen successfully. If False is returned, it is usually because the Text widget does not have any selected text.
The time parameter controls when the
operation takes place and may be set to any server timestamp value. For
example, if you are calling this function from a callback routine, you
may wish to use the time field from the event pointer
in the callback structure provided by the Motif toolkit. The value
CurrentTime can also be used, but there is no guarantee that this
value will prevent any race conditions between other clients wanting to
use the clipboard. Although race conditions are not likely, the
possibility does exist. The result of the race condition is that one
widget may appear to have cut or copied selected text to the clipboard
when in fact another Text widget got there first.
XmTextPaste() takes the following form:
Boolean XmTextPaste(widget) Widget widget;XmTextPaste() gets the current data from the clipboard and places it in the Text widget. The routine returns False if there is no data on the clipboard.
XmTextCut() and XmTextCopy() only
work if there is a current selection in the specified Text widget,
which may be dependent on whether or not the user has made a selection.
However, you can force a selection in a Text widget using
XmTextSetSelection(). This routine takes the following form:
void XmTextSetSelection(widget, first, last, time) Widget widget; XmTextPosition first; XmTextPosition last; Time time;XmTextSetSelection() selects the text between the specified positions in the Text widget. Once the text has been selected, either XmTextCut() or XmTextCopy() may be called to place the selection on the clipboard.
Although XmTextGetSelection() does not deal
with the clipboard directly, it provides a convenient way to get the
current selection from the corresponding Text widget. This routine
takes the following form:
char * XmTextGetSelection(widget) Widget widget;Note that the text returned by the routine is allocated data and must be freed by the caller using XtFree(). The function returns NULL if the specified widget does not own the text selection.
To deselect the current selection in a Text widget,
you can use XmTextClearSelection(), which takes the following
form:
void XmTextClearSelection(widget, time) Widget widget; Time time;
Sometimes, if you have a large number of Text
widgets, you may need to know which of the widgets has the text
selection. You can determine this by using the Xlib function
XGetSelectionOwner():
Window XGetSelectionOwner(display, selection) Display *display; Atom selection;The display parameter can be taken from any widget using XtDisplay(). The selection argument represents the Atom associated with the kind of selection you are looking for. For example, you can determine the Text widget that has the current clipboard selection with the following calls:
Display *dpy = XtDisplay (widget); Atom clipboard_atom = XmInternAtom (dpy, "CLIPBOARD", False); Window win = XGetSelectionOwner (dpy, clipboard_atom); Widget text_w = XtWindowToWidget (dpy, win);
The Motif clipboard mechanism relies on an
underlying X mechanism referred to as properties. As you know, windows
are data structures maintained by the X server; each window can have an
arbitrary list of properties associated with it. Each property consists
of a name (called an atom), an arbitrary amount of data, and a format.
Property formats are not at all the same thing as the higher-level
Motif formats--they simply indicate whether the data is a list of
8-bit, 16-bit, or 32-bit quantities, so that the server can perform
byte-swapping, if appropriate. Properties are the underlying mechanism
for all interclient communication, including interaction between
applications and window managers, and interapplication interaction such
as the transfer of selections.
In order to simplify communication over the network,
property names are not passed as arbitrary-length strings, but as
defined integers known as atoms. A number of standard properties (such
as those used for communication between applications and window
managers) are predefined and interned, or made known to, and
cached by the server. However, application-defined atoms can also be
interned with the server by calling the Xlib function XInternAtom()
or the Motif function XmInternAtom(). Atoms are not only used
to name properties, but to name any string data that may need to be
passed back and forth between a client and the server.
We started this chapter with the analogy that the
Motif toolkit is the moderator of the clipboard. In reality, the
clipboard itself is a property (called CLIPBOARD) that is automatically
maintained by the X server. A property is uniquely identified by both
an atom and a window, which means that it is possible for there to be
multiple copies of a given property. However, there should be only one
CLIPBOARD property active at one time, based on conventions about the
use of properties set forth in a document called the Inter-Client
Communication Conventions Manual (ICCCM) and followed by the deeper
layers of X software. Reprinted as Appendix L of Volume Zero, X
Protocol Reference Manual. Among these conventions are that certain
properties should only be set by application top-level windows and that
only one window should own the CLIPBOARD property at any one time. When
an application makes a call to XmClipboardCopy(), the data is
actually stored in the CLIPBOARD property of the window that was
identified in the call to XmClipboardCopy().
The format of the data stored in a property is
defined by another property. The standard formats are based on those
recommended by the ICCCM. For example, the FONT format might suggest
that an application wants the font that the data string is rendered in,
rather than the data string itself. At present, Motif does not support
this functionality. You have to remember that formats (or targets, as
they are referred to in the ICCCM) are not really things that have any
functionality. They are simply names that are translated into integer
atoms. The meaning of the formats to an application depends entirely on
convention. At present, most applications only support the STRING
format. But eventually, conventions will doubtless be articulated for
doing far more with the selection mechanism.
A further complication that needs some mention is
how the Motif clipboard implementation relates to the underlying X
Toolkit implementation of selections. The ICCCM actually defines three
separate properties that can be used for selections: PRIMARY,
SECONDARY, and CLIPBOARD. Standard Xt applications, including all of
the clients distributed by MIT, use the PRIMARY property for storing
selections.
The SECONDARY property is designed for quicker, more
transient selections. An application that makes use of this property
usually copies data directly to another window instantly when the owner
finishes copying data to the property. The Motif Text widget uses the
SECONDARY property when the META key is down while the middle button is
clicked and dragged. As soon as the selection is complete, the selected
data is immediately sent to the window that has the input focus, which
may be the same window.
In the standard MIT implementation, the CLIPBOARD
property is used by an independent client called xclipboard.
Keep in mind that a property stays around only as long as the window
with which it is associated. When you terminate a client and close its
windows, any data stored in a property on one of the client's windows
is lost. If the CLIPBOARD property is associated with a client that is
kept around between invocations of other applications, it embodies a
consistent repository for information to be passed between
applications.
The ICCCM blesses the use of both the PRIMARY and
CLIPBOARD selection properties. However, you should be aware that the
difference between the Motif use of the CLIPBOARD property and the use
of the PRIMARY selection property by other Xt applications makes
interoperability questionable, unless you take care to handle the
PRIMARY selection in your application. The X Toolkit mechanisms for
handling selections are described in Volume Four, X Toolkit
Intrinsics Programming Manual. The Motif Text widgets support both
the Xt mechanism, which uses the PRIMARY selection, and the Motif
clipboard, depending on the interaction. You should probably do the
same for your application.
While you can manipulate properties and atoms
directly using Xlib, the higher-level API provided by Motif and Xt
should insulate you from many of the details and ensure that your
applications interoperate well with others. Eventually, toolkits and
applications will doubtless support numerous extensions of the current
clipboard and selection mechanisms.
The clipboard provides a convenient mechanism that
allows applications to interact with one another in a way that is
independent of the application, operating system, and system
architecture. The clipboard is one of two common mechanisms used to
handle data transfer between objects. The primary selection is still
regarded as the most common method for data transfer between
applications, mostly because it is the standard cut and paste method
used to move textual data between terminal emulators like xterm.
A secondary selection method is also available, but is not very widely
used.
The Motif toolkit tries to compensate for the de
facto standard use of the primary selection method by integrating
both the primary and clipboard selections into the same set of
functions. Although users seem to be oblivious to the differences, this
technique has the unfortunate side effect of confusing programmers.