Motif Programming Manual (Volume 6A)

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer

X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation





In this chapter:



Chapter: 22

Drag and Drop



This chapter describes the drag and drop mechanism provided by the Motif toolkit. Drag and drop can be used to transfer data within and between applications on the desktop. Although not marked as deprecated, from Motif 2.0 onwards many of the routines described within this chapter are subsumed into the Uniform Transfer Model, which is described in Chapter 23. The Uniform Transfer Model is concerned primarily with data transfer; it does not concern itself directly with the visuals associated with a transfer. You should still read this chapter if you are interested in providing customised feedback during mouse-based data transfer operations, and if you want to gain an understanding of the mechanisms which now partly sit underneath the Uniform Transfer Model.

A graphical user interface provides objects that the user can manipulate and actions that can be performed on those objects. The drag and drop mechanism for transferring data is a natural one for a GUI, as drag and drop allows the user to transport data within and between applications by dragging an iconic representation of the data from one location to another.

An important question that a developer needs to consider is whether or not drag and drop is appropriate for a particular application. You need to think about the data that is manipulated by the application, the actions that can be performed on the data, and whether the drag and drop metaphor makes sense in this context. This decision involves figuring out if drag and drop allows you to enhance the usability of your application by making it easier for the user to perform various tasks.

For example, an electronic mail application might allow the user to drag messages that have been received into folders for storage or into a text editor for composing a response. Perhaps the most common use of drag and drop functionality is for desktop-style applications. These programs allow the user to manipulate files in the directory structure and run other applications by dragging objects around on the desktop.

Using Drag and Drop

From the user's perspective, drag and drop involves choosing a data source, dragging the data around on the desktop, and dropping the data on a new location. The mechanism is the same no matter what type of data is being manipulated. In most cases, the data is moved or copied to the new location. However, an application can also allow the user to drag an object and drop it to invoke an action. For example, dropping a file on a printer icon could cause the file to be printed.

The Motif Style Guide specifies that the middle mouse button is used for drag and drop. The user starts a drag and drop transfer by pressing the second button over the data, which is referred to as the drag source. While the user is dragging the data, the pointer shape is changed to a drag icon which is a picture that represents the type of data being dragged. The drag icon is meant to provide the user with feedback about the current data transfer, so different drag icons can be used to represent textual data and graphical data, for example.

The user can drag the data to another location within the same application or to a location within another application by moving the pointer with the middle button pressed. The data can be dropped in any location that has been registered as a dropsite. The drop occurs when the user releases the mouse button. Figure 22-1 shows the conceptual model of drag and drop.

Figure  22-1 Drag and drop conceptual model

A drag and drop transfer can result in the data being moved, copied, or linked. A move operation copies the data to the drop site and then removes it from the drag source, while a copy operation copies the data to the drop site without removing it. A link operation allows the drop site access to the data in the drag source without copying it.

The default operation depends on the type of data that is being manipulated. In an editable text area, the default operation might be a move, while in a read-only area, the default operation should be a copy. A drag source can support multiple operations, in which case the user should be able to select the operation that is used. The Motif Style Guide specifies that the SHIFT key selects a move operation, the CTRL key selects a copy operation, and CTRL-SHIFT selects a link operation.

The user can cancel a drag at any time by pressing the ESCAPE key. The user can also request help on a drop site by pressing the HELP or F1 key before dropping the data. The help information should tell the user what will happen if the data is dropped in the drop site.

Besides representing the type of data being manipulated, the drag icon can also indicate the current operation and whether the pointer is over a valid drop site, over an invalid drop site, or not over a drop site at all. For a drop site to be valid, the drag source and the drop site must understand at least one common data format. If a drag source only provides graphical data and a drop site only understands text, the data transfer cannot succeed.

The drag icon may change as it enters and leaves drop sites to provide this state information; these changes are called drag-over visuals. For example, the drag icon could be displayed without any modification when it is over a valid drop site, but be superimposed with a do-not-enter symbol when the drop site is invalid. A drop site may also change its appearance when the drag icon is within it; these effects are known as drag-under visuals. A "garbage can" drop site might use animation to show the lid opening when a drag icon moves into the drop site. When the user performs a drop, the drag icon melts into the drop site if the data transfer is successful or springs back to the drag source if the transfer fails.

The Drag and Drop Model

The Motif implementation of drag and drop introduces a number of new programming constructs. The interaction between the different components is complex, so it may be difficult to understand just what needs to be done to implement drag and drop functionality. Since you need to understand all of the different components before you can see what your application may need, we've decided to describe all of the components of drag and drop in a somewhat abstract way before we present any examples. Although this material may be a bit dry, we think that this approach works better than presenting an example early and then having to jump around a lot to explain all of its parts. Hopefully, once you see the big picture, it will be easier to understand the different pieces more fully.

From the programmer's perspective, providing drag and drop functionality in an application can be as simple as using the Motif widgets that support drag and drop. In Motif, the Text, TextField, and List widgets are all drag sources, which means that the textual data they contain can be dragged. The Label widget and its subclasses are also drag sources for both textual and pixmap data1. The Text and TextField widgets are registered as drop sites, which means that textual data can be dropped in them. When you use these widgets in an application, you do not have to do any extra programming to provide their drag and drop capabilities since the functionality is built into the widgets.

The drag and drop capabilities provided by the Motif toolkit are highly customizable, so an application can also implement custom drag and drop transfers. Drag source and/or drop site functionality can be added to any widget. An application can provide custom drag icons and implement custom drag-under effects, such as animated drop sites. Drag and drop can be made to handle any type of data. The amount of programming required to implement custom drag and drop features varies depending on the degree of customization that is desired. While it is relatively easy to provide a new drop site for textual information, supporting drag and drop for graphical objects requires quite a bit of work.

The Motif toolkit layers the implementation of drag and drop on top of the selection mechanisms provided by the X Toolkit Intrinsics. If you are simply using the built-in drag and drop functionality, the implementation details are completely invisible. However, if you are customizing drag and drop in any way, you need to understand the underlying selection mechanisms because the drag and drop implementation is not a complete abstraction over the Xt mechanisms. For example, an application that uses custom drag sources and drop sites must provide certain selection conversion and transfer procedures in order for the data transfer to occur.

Since the Xt selection mechanisms are based on X's Inter-Client Communications Conventions Manual (ICCCM), the Motif implementation of drag and drop also adheres to the ICCCM. Data is transferred using properties on the server, where properties are referenced using atoms. Drag sources and drop sites also use atoms to specify the data formats, or targets, that they support. The ICCCM suggests a list of possible target types so that applications can understand each other. These targets and their meanings are shown in Table 22-1. You can also define your own targets, but unless you document them, other applications will not necessarily be able to communicate with your application using these targets.



Table  22-1   Target Types Defined by ICCCM 
Atom
Type
Meaning

TARGETS

ATOM

List of valid target atoms

MULTIPLE

ATOM_PAIR

Multiple conversion requests

TIMESTAMP

INTEGER

Timestamp used to acquire selection

STRING

STRING

ISO Latin 1 text

COMPOUND_TEXT

COMPOUND_TEXT

Text in compound text encoding

TEXT

TEXT

Text in owner's encoding

LIST_LENGTH

INTEGER

Number of disjoint parts of selection

PIXMAP

DRAWABLE

Pixmap ID

DRAWABLE

DRAWABLE

Drawable ID

BITMAP

BITMAP

Bitmap ID

FOREGROUND

PIXEL

Pixel value

BACKGROUND

PIXEL

Pixel value

ODIF

TEXT

ISO Office Document Interchange Format

OWNER_OS

TEXT

Operating system of owner

FILE_NAME

TEXT

Full path name of a file

HOST_NAME

TEXT

Host name of machine of owner

CHARACTER_POSITION

SPAN

Start and end of selection in bytes

LINE_NUMBER

SPAN

Start and end line numbers

LENGTH

INTEGER

Number of bytes in selection

USER

TEXT

Name of user running owner

PROCEDURE

TEXT

Name of selected procedure

MODULE

TEXT

Name of selected module

PROCESS

INTEGER, TEXT

Process ID of owner

TASK

INTEGER, TEXT

Task ID of owner

CLASS

TEXT

Class of owner (WM_CLASS)

NAME

TEXT

Name of owner (WM_NAME)

CLIENT_WINDOW

WINDOW

Top-level window of owner

DELETE

NULL

True if owner deleted selection

INSERT_SELECTION

NULL

Insert specified selection

INSERT_PROPERTY

NULL

Insert specified property

Motif uses some new objects to encapsulate information about various aspects of a drag and drop transfer. These objects act like widgets, in that they are created by the programmer, they have resources that can be set and retrieved, and they interact with the application using callbacks. However, they are unlike traditional widgets in that they are not visible components of the user interface. The DragContext object is used to store information during a drag, while the DropTransfer object keeps track of information during a drop. The DragIcon object is used to represent the pointer shape that is used during a drag and drop transfer. The DropSite object maintains information about all of the drop sites in an application. The Display and Screen objects also provide resources that control the behavior of drag and drop, although they are not specifically part of drag and drop.

The following sections describe all the components of a drag and drop transfer and present the Motif objects that are used to implement drag and drop. As we describe the objects, we mention many of their resources, callbacks, and related functions so that you can see how everything fits together. We describe each of the objects in much greater detail later in the chapter when we talk about how they can be used to customize different aspects of drag and drop. However, this chapter does not attempt to describe all of the possible ways in which drag and drop can be customized. We present some common situations and leave you to explore all of the details on your own. For complete information about each Motif object used to implement drag and drop, see the appropriate reference pages in Volume 6 B, Motif Reference Manual.


The Drag Source

The widget that contains the data being manipulated with drag and drop is known as the drag source. When the user starts a drag, the application that contains the drag source is considered the initiator of the transfer. The data provided by a drag source depends on the type of object the source represents. For example, a Text widget provides textual data, while a DrawingArea could provide some form of graphical data.

A drag source can be designed to support and transfer any type of data. There can even be multiple formats for a given piece of data if appropriate. A drag source also specifies the operations (move, copy, or link) that it allows. The type of data, and in some cases the widget that contains the data, affects the operations that are supported. For example, the List widget only supports copy operations because it is a read-only component.

In order for a drag and drop transfer to work, the drag source and the drop site need to understand the same type of data. The drag source announces the data targets it can supply to the drop site. A drag source that supports textual data might offer the data using COMPOUND_TEXT, STRING, and TEXT targets, while a graphical drag source could provide PIXMAP, FOREGROUND, and BACKGROUND targets. When the drop occurs, the drop site can request the data in any of the targets supported by the drag source, so the drag source needs to know how to convert between supported types.

In order for a widget to be a drag source, the widget must be able to recognize a ButtonPress event for the second mouse button. Essentially, you need to set up a translation and action or an event handler for this event that invokes a function that starts the drag. The following code fragment shows the definition of a translation and an action for a drag source:

static char dragTranslations[] = "#override <Btn2Down>: StartDrag()";
static XtActionsRec dragActions[] = {
    {"StartDrag", (XtActionProc) StartDrag}
};
Just as with any translation and action, the application code needs to call XtParseTranslationTable() and XtAppAddActions(). The parsed translation table can be used to set the XmNtranslations resource for the drag source widget.

The Motif toolkit uses the DragContext object to store information about a drag source once a drag has started. This object also keeps track of state information about the transfer as it is happening. The routine that starts a drag calls XmDragStart() to create the DragContext and get things rolling. The DragContext object has resources that need to be set at creation time to provide information about the drag source. The XmNdragOperations resource specifies the operations supported by the drag source, while XmNexportTargets and XmNnumExportTargets indicate the data targets that are supported.

The DragContext also has a number of resources that control the visual effects used during the drag. Many of these resources specify various attributes of the drag icon for the transfer. For example, the XmNsourceCursorIcon, XmNoperationCursorIcon, and XmNstateCursorIcon resources indicate the images that are used for different parts of the drag icon.If these resources are not specified, the DragContext uses default icons. There are also resources that allow you to specify different foreground and background colors for the drag icon. We describe the drag icon in more detail later in this section.

The DragContext also provides callback routines that can be used to monitor the drag and provide custom visual effects. All of the routines use special callback structures that provide information about the current state of the drag. The Working With Drag Sources Section provides more information about these callbacks.

The XmNconvertProc is a procedure that must be specified when a DragContext is created. This procedure is used to convert the drag source data to the format requested by the drop site when the drop occurs. The convert procedure is either an XtConvertSelectionProc or an XtConvertSelectionIncrProc, depending on whether or not the drag source is using incremental transfer. If the XmNincremental resource is set to True, the data is transferred incrementally. Both of these procedures are part of the underlying Xt selection mechanism that is not completely hidden by the Motif drag and drop abstraction. See Volume 4, X Toolkit Intrinsics Programming Manual, for more information on these procedures.

The following code fragment shows the creation of a DragContext object with a minimal set of resources:

Atom       exportList[1];
Widget     widget, dc;
Arg        args[5];
int        n;
Boolean    ConvertProc(Widget, Atom *, Atom *, Atom *, XtPointer *, 
                        unsigned long *, int *);
XEvent     *event;
...
n = 0;
exportList[0] = COMPOUND_TEXT;
XtSetArg (args[n], XmNexportTargets, exportList);               n++;
XtSetArg (args[n], XmNnumExportTargets, XtNumber (exportList)); n++;
XtSetArg (args[n], XmNdragOperations, XmDROP_COPY);             n++;
XtSetArg (args[n], XmNconvertProc, ConvertProc);                n++;
dc = XmDragStart (widget, event, args, n);
In the Working With Drag Sources Section, we present an example that creates a custom drag source, and we describe the source code in detail.

Once a DragContext has been created, the Motif toolkit for the initiating application assumes control of the drag, so the application itself doesn't have to do anything during the drag. If any of the DragContext callbacks have been specified, they are called automatically by the Motif toolkit at the appropriate time.

When the drop occurs, the drop site determines whether or not the data transfer can succeed based on the operations and targets supported by the drag source and the drop site. If the transfer can succeed, the drop site initiates the transfer, which causes the XmNconvertProc to be called for each data target that the drop site has requested. This routine converts the data into the requested format and passes it back to the drop site, so the drop site can do whatever it needs with the data.


The Drop Site

Once the user starts a drag and drop transfer, the data can be dropped in any location that has been registered as a drop site, and if the drop site understands the data, the transfer will succeed. The application that contains the drop site where data is dropped is the receiving client in a drag and drop transfer. A drop site is always associated with a widget. Like a drag source, a drop site supports particular types of data, depending on the type of object it is.

A drop site can be designed to handle any type of data, or even multiple types if appropriate. A drop site also specifies the operations that it supports. The standard operations are to move, copy, and link data. However, a drop site can instead invoke an action as the result of a drop. For example, a "send message" drop site could send an electronic mail message when text is dropped in it.The type of object that functions as the drop site also has an effect on the supported operations. In most cases, it only makes sense for writable components to act as drop sites, since read-only components like Lists and Labels cannot be modified by the user.

Motif stores information about all of the drop sites in an application using DropSite objects. An application registers a widget as a drop site by calling XmDropSiteRegister() for the widget. The DropSite object uses resources to keep track of information about the drop site. This information can be set when the drop site is registered, or it can be specified later using XmDropSiteUpdate(); the values of the resources can be retrieved using XmDropSiteRetrieve(). Since a widget is being used as the handle to the drop site, you cannot use XtVaSetValues() and XtVaGetValues() to set and retrieve drop site information, as these routines manipulate the widget's resources.

Just as a drag source specifies the data types that it can process, a drop site also needs to provide this information. The XmNimportTargets and XmNnumImportTargets resources specify this information, while the XmNdropSiteOperations resource specifies the operations supported by the drop site.

A drop site provides visual effects when the drag icon passes through it; these effects are called drag-under effects. By default, the widget is highlighted. Other simple effects, such as a shadow border or a special pixmap, can be specified using the XmNanimationStyle resource. All of these effects are handled automatically by the toolkit on the initiating side once the resource is set. For more sophisticated effects, such as animation, a drop site must register an XmNdragProc. This callback is invoked whenever there is any drag activity in the drop site, so the application can do whatever it likes in terms of drag-under effects.

While the XmNdragProc is optional, every drop site must have a XmNdropProc registered. This routine is called when a drop occurs in the drop site. The procedure is responsible for determining whether the drop is successful or not, based on the targets and operations supported by the drag source and the drop site. The following code fragment shows how a widget that can handle compound text is registered as a drop site:

Arg          args[10];
int          n;
Widget       label;
Atom         importList[1];
void         HandleDrop(Widget, XtPointer, XtPointer);
...
n = 0;
importList[0] = COMPOUND_TEXT;
XtSetArg (args[n], XmNimportTargets, importList);               n++;
XtSetArg (args[n], XmNnumImportTargets, XtNumber (importList)); n++;
XtSetArg (args[n], XmNdropSiteOperations, XmDROP_COPY);         n++;
XtSetArg (args[n], XmNdropProc, HandleDrop);                    n++;
XmDropSiteRegister (label, args, n);
When a drop occurs in the drop site, the XmNdropProc is called automatically by the Motif toolkit. This routine must call XmDropTransferStart() whether or not the drop is successful. XmDropTransferStart() creates a DropTransfer object that maintains information about the data transfer. When The DropTransfer object is created, the XmNtransferStatus resource must be set to indicate the success or failure of the drop. If the resource is set to XmTRANSFER_FAILURE, XmDropTransferStart() does not transfer any data and merely cleans up after the drag and drop transfer.

If XmNtransferStatus is set to XmTRANSFER_SUCCESS when the DropTransfer object is created, some other resources must also be specified to cause the data to be transferred. The XmNdropTransfers and XmNnumDropTransfers resources specify the data targets to be processed, while XmNtransferProc indicates the procedure that receives the converted data from the drag source. This transfer procedure is of type XtSelectionCallbackProc. Once the data transfer has started, the routine XmDropTransferAdd() can be used to request the processing of additional data targets. In the Working With Drop Sites Section, we discuss in detail the tasks involved in creating a drop site.

When a drop takes place, visual effects are used to indicate the status of the transfer. Unlike the different drag effects, these visuals are not customizable. When the drop occurs, the pointer shape is changed back to the standard cursor, while the drag icon sits over the drop site. If the drop succeeds, the icon melts into the drop site. If the transfer fails or is cancelled by the user, the icon snaps back to the drag source.

A drop site is normally the size and shape of the widget with which it is associated. However, a drop site can also be shaped. The XmNdropRectangles and XmNnumDropRectangles resources control this feature. Drop sites can also be nested, so that a manager widget can be a drop site and can also contain children that are drop sites. The XmNdropSiteType resource controls whether the drop site is a simple drop site or a compound drop site. Drop sites have a stacking order, which means that they can overlap. When drop sites overlap, the drop site on the top of the stack obscures the drop sites beneath it, as you would expect. An application can control the stacking order of drop sites using the toolkit convenience routines XmDropSiteQueryStackingOrder() and XmDropSiteConfigureStackingOrder().


The Drag Icon

During a drag, the pointer shape is changed into a dragicon that represents the data that is being dragged. One of the purposes of the special icon is to make it clear that a drag and drop transfer is in progress. The drag icon can also change during a drag to indicate the current status of the transfer. These visual effects are called drag-over visuals. Typical effects include changing the shape and changing the color of the icon.

A drag icon can be composed of three distinct parts, each of which is really a separate icon.The source icon represents the type of data that is being dragged; this icon is the only necessary component of a drag icon. The source icon for a drag that manipulates files might be an image of a piece of paper, for example. The state icon indicates whether the pointer is over a valid drop site, over an invalid drop site, or not over a drop site. The operation icon specifies the current operation. The source icon in a drag icon is static, while the state and operation icons can be dynamic. Figure 22-2 shows the components of a drag icon.

Figure  22-2 A drag icon

The Motif toolkit provides default icons for all of the different drag icon components. In Motif 2.0 and later, there are two sets of default icons; which set is displayed depends upon the value of the XmDisplay object XmNenableDragIcon resource. If this is False, the default icons are consistent with Motif 1.2 behavior, otherwise an alternative set is displayed. The default source icons for textual data and for generic data are shown in Figure 22-3.

Figure  22-3 Default source icons

The default state icon for all of the different states is an arrow, as shown in Figure 22-2, while the default operation icons for the move, copy, and link operations are shown in Figure 22-4.

Figure  22-4 Default operation icons

Motif uses DragIcon objects to represent the parts of a drag icon. In order to use a custom image, you need to create each part of the icon using XmCreateDragIcon(). The XmNblendModel resource of the DragContext for a drag and drop transfer specifies the different pieces that are blended together to create the actual drag icon.

A drag icon is essentially a pixmap, and the DragIcon object encapsulates all of the information about the image. When you create a DragIcon, you specify resources that describe the image. The XmNpixmap and XmNmask resources represent the actual pixmap and its mask if you use one. Other resources include XmNheight, XmNwidth, and XmNdepth for specifying those attributes of the image, as well as XmNhotX and XmNhotY for indicating the x, y coordinate of the hotspot for the icon. The XmNattachment, XmNoffsetX, and XmNoffsetY resources specify how the icon is attached to the other parts of a drag icon.

There are a number of ways in which you can customize the drag icons that are used for drag and drop. You can specify default icons for all drag and drop transfers that start from your application by setting various resources of the Screen object. When you change the default drag icons for the Screen, the toolkit handles the drag-over effects using the icons, as we discuss in the Customizing Built-in Drag and Drop Section. An application can also specify custom drag icons for a particular drag and drop transfer by setting resources on its DragContext object. In this case, the application has to manage the drag-over visuals using the different DragContext callback routines.


Protocols

For drag and drop to work, the initiating and receiving applications must be able to talk to each other. The Motif toolkit supports two different mechanisms by which clients can communicate with each other during drag and drop. The main information that needs to be passed back and forth during a drag concerns the location of the drag icon relative to drop sites in the receiving application. The dynamic drag protocol requires messaging between the two applications, while the preregister drag protocol does not. During the drop, the Xt selection protocol is used to transfer the data from one application to the other.


Drag Protocols

An application can quite easily support both the dynamic and preregister drag protocols, although it can just support one or neither of the protocols if necessary. If an application does not support either of the protocols, it can still participate in drag and drop, but it does not provide any visual effects during the drag. The best approach is to support both protocols so that users can specify the protocol that is used based on their needs. By default the toolkit supports both protocols, so it is easy for an application to support both as well. The code for the initiating client is the same for both protocols, while the code for the receiver is the same except for an additional procedure that can be specified for use under the dynamic protocol.

With the preregister protocol, information about all of the drop sites in an application is stored in a database. This database is kept in a property on the top-level window of the application (or on each top-level window, if there is more than one) so that it can be read by an initiating application. During a drag, the initiator uses information in the database to manage both drag-over and drag-under visuals. Drop sites in the receiving application can set some resources to control the style of drag-under effect used, but the receiver does not participate directly in the drag.

One benefit of the preregister protocol is that it does not require dynamic communication between the initiating and receiving applications, so the performance of drag and drop does not suffer if the network is heavily loaded. However, a receiving application cannot provide sophisticated drag-under effects when the preregister protocol is being used. Under this protocol, the server is grabbed during the drag, which means that the drag icon can be any size (the size is not limited to the largest cursor size, as it is for the dynamic protocol).

Under the dynamic protocol, when the drag icon moves into a receiving application's window, the initiator sends a message to the application. Based on this message, the toolkit on the receiving side determines whether or not the drag icon is in a valid drop site. The toolkit also initializes state and operation information for the receiver, although the receiving application can update this information using its XmNdragProc. Based on the movement of the drag icon, the initiator receives the updated message back in one of its drag-related callbacks.

The benefit of the dynamic protocol is that the receiving application can provide sophisticated drag-under effects and drag processing using its XmNdragProc. However, the application does not have to provide these effects, as the toolkit provides some basic effects by default. The dynamic protocol also has some drawbacks. One drawback is that the messaging is expensive in terms of network traffic and may lead to unacceptable performance if the network is heavily loaded. Another limitation is that the image used for the drag icon can only be as large as the largest cursor supported by the system running the application.

The Display object provides two resources that can be set to indicate which protocol the toolkit should use when an application is the initiating or the receiving application in a drag and drop transfer. These resources are XmNdragInitiatorProtocolStyle and XmNdragReceiverProtocolStyle. An application can set the resources if it needs to specify a particular protocol, or they can be set by the user in a resource file. We describe the different values for the resources and how the actual protocols are determined in the Customizing Built-in Drag and Drop Section.


Drop Protocol

The protocol that is used to transfer data when the drop occurs encompasses the Xt incremental and non-incremental selection protocols. The DropTransfer object created by the receiving application handles the drop protocol. When the DropTransfer object is created using XmDropTransferStart(), the receiver specifies resources that indicate the list of desired targets, as well as an XmNtransferProc that handles the data once it has been converted by the initiator. The toolkit processes the requests one at a time by calling the XmNconvertProc of the initiating client. This procedure processes the request and passes the data back to the XmNtransferProc.

The DragContext and DropTransfer objects both have XmNincremental resources that specify whether or not the data transfer is incremental. Incremental transfers are used when the data is too large for a single X protocol request. No matter how the two resources are set, the toolkit handles the transfer of data using the underlying Xt selection mechanisms. Both the initiator and the receiver are informed about the completion of the entire transfer once all of the subtransfers are done, if there are any.


The Programming Model

If you review what we've just covered and put all of the pieces together, it creates a complex picture from the programmer's perspective. Fortunately, unless you are trying to do something really complicated, you can ignore many of the pieces and only use what you need. This section describes the complete picture by laying out the responsibilities of both the initiating and receiving applications for each step of a drag and drop operation. Figure 22-5 shows the steps graphically.

Even though most applications contain both drag sources and drop sites, it makes sense to think about the two roles separately, as the programming requirements for each are separate.

Figure  22-5 Drag and drop programming model

If the initiator and receiver are in the same application, then the same toolkit is used by both parties. Otherwise, each application is using a separate toolkit.


Before a Drag Starts

During the initialization and setup of the user interface, the initiating application needs to create any custom drag icons that it wants to use for drag-over visual effects. The initiator also needs to set up translations or event handlers to deal with ButtonPress events for the second mouse button. The initiator (or the user) can specify the drag protocol if necessary.

The receiving application needs to register widgets as drop sites. For each drop site, the receiver must specify the valid data targets and the XmNdropProc that takes over when a drop occurs in the dropsite. The receiver can also specify an XmNdragProc to handle special processing during the drag and custom drag-under visuals for the drop site. The receiver can query and modify the stacking order of drop sites, as well as update information about drop sites while the application is running.


When a Drag Starts

When the user starts a drag operation, the toolkit on the initiating side takes control. The application needs to create a DragContext by calling XmDragStart(). It must specify the valid targets for the operation and the XmNconvertProc that processes data transfer requests from the receiving client. The application can also specify callbacks that are invoked at various points during the drag, custom drag-over visual effects, and a drop callback that is called when the drop occurs. Receiving clients are not involved in this step.


During the Drag

By default, the toolkit on the initiating side handles all of the drag-over and drag-under visuals under both the preregister and dynamic protocols. If the preregister protocol is being used, the receiving client is not involved during the drag, but the initiating application can provide custom drag-over effects. These effects are handled by the various callbacks that can be specified fora DragContext. At any point during the drag, the initiator can cancel the transfer by calling XmDragCancel().

Under the dynamic protocol, the initiating application sends messages to receiving clients to get drop site information. The toolkit on the receiving side handles these messages. If the receiver has registered an XmNdragProc, it is invoked each time a message is sent to the receiver. This routine can provide custom drag-under visuals and other special processing. After the XmNdragProc is finished, information about the drop site is passed back to the initiator, and the DragContext callbacks are invoked, so the initiator can still perform any special processing and provide custom drag-overvisuals.


When a Drop Occurs

When the user drops the data, the toolkit on the receiving side takes over from the toolkit on the initiating side. The XmNdropProc for the drop site determines what action the user has requested.If the user has requested help, the receiving application should display a help dialog and see if the user wants to proceed. If the user cancels the transfer, the drop does not proceed. Otherwise, the XmNdropProc determines if the transfer is possible by checking the targets supported by drag source.

If the drop is valid, the receiving client starts the transfer of data by calling XmDropTransferStart(). If the transfer is not valid, the routine still needs to be called to clean up the operation. If the initiator has registered an XmNdropStartCallback on its DragContext, it is invoked now. Other than this callback, the initiating client plays no role when the drop occurs.


During the Data Transfer

When the receiver calls XmDropTransferStart(), it must specify a list of data and target formats that it wants from the initiating application.The routine creates a DropTransfer object that can be updated during the transfer. The receiver must also specify an XmNtransferProc to handle the data once it has been converted by the initiator.The receiver can cancel the transfer at any point.

For each target requested by the receiver, the XmNconvertProc of the initiator is called to convert the data to the specified format. The formatted data is passed back to the receiver's XmNtransferProc. Once the entire data transfer is complete, the XmNdropFinishCallback and XmNdragDropFinishCallback callbacks of the initiating client's DragContext are invoked, if they have been specified.

Customizing Built-in Drag and Drop

The Text and TextField widgets, the List widget, and the Label widget and its subclasses all support drag and drop functionality by default2. When you use these widgets in an application, they provide built-in drag and drop capabilities. All of the widgets are drag sources for textual data, while just the Text and TextField widgets are registered as drop sites for text.

With a Label widget or a button, the user can drag the entire text string of the component by starting a drag in the component. The Label widget and its subclasses are also drag sources for graphical data, but there are no built-in drop sites for graphical data. However, when these components are in a menu, they do not function as drag sources. These components are not drop sites because they are meant to be read-only components in a user interface. Most applications would not want the user to be able to change the label on a button by dropping text on it. However, if you want to provide this type of functionality, it is easy to register a Label or a button as a drop site using the technique we describe in the Working With Drop Sites Section.

The user can drag the text of either a single item or the current selection in a Listwidget. If the pointer is over a selected item when the drag is started, the text of the selected item is used for the drag. If multiple items are selected, the text of all of the selected items is used, where the items are separated by newlines. If the drag is started over an unselected item, the text of that item is transferred by drag and drop. The List widget is not a drop site because its items are not meant to be modified by the user. If you want to allow the user to modify a List by dropping items in it, however, you can register the widget as a drop site.

In Motif 1.2, only the Text and TextField widgets have built-in drop site functionality. The user can drop textual data from any drag source in these widgets. The widgets also function as drag sources, so the user can move and copy the current selection within and between Text and TextField widgets.

In Motif 2.0 and later, the set of widgets supporting built-in drop site capability is expanded to include the Container. In this instance, the data being transferred is widget-based, and comes from the Container itself: it allows the user to move IconGadget children within the same Container by dragging with the mouse.

Applications that simply use the built-in drag and drop capabilities of the Motif widgets can still customize various aspects of the functionality. This section explores the different types of customization that are possible.


Specifying the Drag Protocol

Motif supports two different protocols for communication between applications during a drag. The dynamic protocol passes messages between the two applications about the location of drop sites, while the preregister protocol keeps track of drop site information in a database. Since the preregister protocol does not require communication between applications, it can provide better performance on a heavily-loaded network. However, the dynamic protocol offers the advantage of sophisticated drag-under visual effects.

The programmer or the user can specify the drag protocol for an application by setting the XmNdragInitiatorProtocolStyle and XmNdragReceiverProtocolStyle resources defined by the Display object. Motif creates a Display object automatically for an application when it creates the first shell on a particular display. If an application uses multiple displays, it has a Display object for each one. An application can retrieve the Display object for a specified display using XmGetXmDisplay().

The XmNdragInitiatorProtocolStyle and XmNdragReceiverProtocolStyle resources indicate the preferred drag protocol for an application when it is acting as an initiator and as a receiver, respectively, in a drag and drop transfer. Each resource can be set to one of the following values:


XmDRAG_PREREGISTER

This value means that the application can only support the preregister drag protocol.


XmDRAG_DYNAMIC
This value indicates that the application can only support the dynamic drag protocol.


XmDRAG_NONE
This value means that drag and drop is disabled for the application.


XmDRAG_DROP_ONLY
This value specifies that the application does not support either drag protocol, but it does support drag and drop transfers. The user can transfer data using drag and drop, but there are no visual effects during the drag.


XmDRAG_PREFER_DYNAMIC
This value means that the application supports both the preregister and dynamic protocols, but it prefers to use the dynamic protocol.


XmDRAG_PREFER_PREREGISTER
This value means that the application supports both drag protocols, but it prefers to use the preregister protocol. The value is the default value for XmNdragReceiverProtocolStyle.


XmDRAG_PREFER_RECEIVER
This value indicates that the application supports both the preregister and dynamic protocols, but it defers to the preference of the receiving application. The value can only be specified for the XmNdragInitiatorProtocolStyle resource, and it is the default value for the resource.

The actual protocol that is used during a drag and drop transfer is based on the preferences specified by the initiating and receiving applications. The protocol can change during a drag as the drag icon enters and leaves top-level windows. Table 22-2 shows how the protocol is resolved based on the preferred protocols for the initiator and the receiver.


Table  22-2   Drag Protocol Resolution 
Initiator
Protocol Style
Receiver Protocol Style
Preregister
Prefer Preregister
Prefer Dynamic
Dynamic

Preregister

Preregister

Preregister

Preregister

Drop Only

Prefer Preregister

Preregister

Preregister

Preregister

Dynamic

Prefer Receiver

Preregister

Preregister

Dynamic

Dynamic

Prefer Dynamic

Preregister

Dynamic

Dynamic

Dynamic

Dynamic

Drop Only

Dynamic

Dynamic

Dynamic

If two applications cannot find an agreeable protocol style, the XmDRAG_DROP_ONLY style is used. In this case, there are no drag-over or drag-under visuals except for the initial drag icon. An application can also explicitly set the protocol resources to XmDRAG_DROP_ONLY, in which case the application does not provide any visual effects during the drag.

If an application sets the resources XmNdragInitiatorProtocolStyle or XmNdragReceiverProtocolStyle to XmDRAG_NONE, the application does not participate in drag and drop as an initiator or a receiver, respectively. This value is useful for disabling drag and drop functionality, as we discuss in the next section.

The actual protocol used for a drag and drop transfer controls the visual effects that the user sees during the drag. Under the preregister protocol, the server is grabbed so the drag icon can be a pixmap of arbitrary size. The drag icon uses the depth and colormap of the drag source widget, so it can be a color image. When the dynamic protocol is used, the drag icon is implemented using the X cursor, so it must be a bitmap and is limited in size (use XQueryBestCursor() to determine the largest size for a particular hardware configuration).

An application should support both the dynamic and preregister protocols so that the user can select the protocol based on his needs. Since the toolkit supports both protocols by default, an application can easily support both as well. The code for handling drag sources is the same under both protocols. Drop sites can specify an optional XmNdragProc routine that is invoked under the dynamic protocol and can be used to provide sophisticated drag-under effects.

The only reason that you should specify the XmNdragInitiatorProtocolStyle and XmNdragReceiverProtocolStyle resources in application code is if your application is going to support only one of the drag protocols. In this case, you should set the resources to force the application to use the supported protocol. You can retrieve the Display object for the application using XmGetXmDisplay() and then use XtVaSetValues() to specify the resources. You can also use XtVaGetValues() to check the values of the protocol resources.

If your application supports both drag protocols, you can specify the protocol resources in an app-defaults file to indicate the application's preferred protocol. By default, an application uses the preregister protocol because XmNdragInitiatorProtocolStyle is set to XmDRAG_PREFER_RECEIVER and XmNdragReceiverProtocolStyle is set to XmDRAG_PREFER_PREREGISTER. If you have implemented custom drag-under visuals with an XmNdragProc, you should set the protocol resources to XmDRAG_PREFER_DYNAMIC so that the dynamic protocol is used whenever possible. You can set these resources in an app-defaults file as follows:

*DragInitiatorProtocolStyle: DRAG_PREFER_DYNAMIC
*DragReceiverProtocolStyle:  DRAG_PREFER_DYNAMIC
If you set the protocol resources in an app-defaults file, users can specify their own values in a resource file. Users that want to ensure good performance should specify a preference for the preregister protocol, while users that want sophisticated drag-under effects should indicate a preference for the dynamic protocol.


Turning Off Drag and Drop Functionality

If you do not want to provide drag and drop in an application, you can turn off the functionality in a number of ways. The most effective way to turn off the functionality is to set both XmNdragInitiatorProtocolStyle and XmNdragReceiverProtocolStyle to XmDRAG_NONE. These settings completely disable drag and drop for the application. You can also set just one of the resources to this value to prevent an application from participating in drag and drop as either an initiator or a receiver.

You can also selectively turn off individual drag sources in an application. To prevent a widget from providing its default drag source functionality, you need to override the translation for the second mouse button for the widget, as shown in the following code fragment:

static char dragTranslations[] = "#override <Btn2Down>: DoNothing()";
static XtActionsRec dragActions[] = {
    {"DoNothing", (XtActionProc) DoNothing}
};
True to its name, the DoNothing() action routine does nothing. Once you parse the translation table and add the actions to the application, you can use the translation to set the XmNtranslations resources of all of the widgets that you do not want to function as drag sources.

There are two different ways to disable the drop site functionality of a Text or TextField widget. If you want to turn off the drop site permanently, you can call XmDropSiteUnregister() for the widget. This routine removes the drop site associated with the widget, so you have to reregister it if you want to enable the drop site. To disable a drop site temporarily, it is easier to use the XmNdropSiteActivity resource defined by the DropSite object. This resource can be set to either XmDROP_SITE_ACTIVE or XmDROP_SITE_INACTIVE. When a drop site is inactive, it does not participate in drag and drop. You can set a drop site inactive using XmDropSiteUpdate(), as shown in the following code fragment:

Widget  text_w;
Arg     args[5];
int     n = 0;
...
XtSetArg (args[n], XmNdropSiteActivity, XmDROP_SITE_INACTIVE); n++;
XmDropSiteUpdate (text_w, args, n);
...
Even though drop sites are associated with widgets, you have to set DropSite resources using XmDropSiteUpdate(), not XtVaSetValues().

One situation in which you would probably want to disable a built-in drop site is when the widget is designed to be output-only. If you set the XmNeditable resource of a Text or TextField widget to False, the user cannot drop data in the widget because it is uneditable. However, the toolkit still displays the default drag-under visual effects in this case, so the widget appears as though it functions as a drop site. To make it clear that the widget is not a drop site, you can disable the drop site using one of the techniques we just described. If the widget is always uneditable, it is fine to use XmDropSiteUnregister(), but if the widget changes state, you are better off setting XmNdropSiteActivity. For certain other types of read-only widgets, an alternative exists in Motif 2.0 and later; set the XmDisplay resource XmNenableUnselectableDrag to True to turn off dragging in Label, LabelGadget, and Scale widgets.

When you set a Text or TextField widget insensitive, the user cannot interact with the widget, so it doesn't make sense for the widget to function as a drop site. However, there is currently a bug in the implementation of drag and drop such that the user can drop text in an insensitive widget. To prevent this problem, whenever you change the sensitivity of a Text widget, you should set the XmNdropSiteActivity resource to match the sensitivity.


Modifying the Visual Effects

Motif provides resources that both the user and the programmer can use to change the default drag-over visual effects that are used during a drag and drop transfer. The Screen object provides the following resources:

XmNdefaultSourceCursorIcon        XmNdefaultMoveCursorIcon
XmNdefaultCopyCursorIcon          XmNdefaultLinkCursorIcon
XmNdefaultValidCursorIcon         XmNdefaultInvalidCursorIcon
XmNdefaultNoneCursorIcon
These resources specify the default icons for all the components of a drag icon, including the different operations and states.

Motif creates a Screen object automatically for an application when it creates the first shell on a particular screen. If an application accesses multiple screens, it has a Screen object for each one. An application can retrieve the Screen object for a specified screen using XmGetXmScreen().

The drag icon resources defined by the Screen object only take effect when the XmNsourceCursorIcon, XmNoperationCursorIcon, and XmNstateCursorIcon resources have not been specified for a particular DragContext. All Motif widgets with built-in drag source functionality set the XmNsourceCursorIcon resource, so the Screen resource cannot be used to specify a different source icon for these components. The widgets do not set the XmNoperationCursorIcon and XmNstateCursorIcon resources, so you can set the various default icons for these components.

If neither the DragContext resources nor the Screen resources are specified, Motif uses hard-coded default icons. For example, the running figure shown in Figure 22-3 is used as the source icon whenever a source icon has not been specified. Since this icon is rather arbitrary, you might want to set the XmNdefaultSourceCursorIcon resource to something more appropriate for your application.

Before you can set the Screen resources in application code, you must create DragIcon objects for the different resources. In the Working With Drag Sources Section we describe how to create a drag icon using XmCreateDragIcon(). Once the drag icon exists, you can retrieve the Screen object using XmGetXmScreen() and set its resources, as shown in the following code fragment:

Widget drag_icon, screen, toplevel;
...
screen = XmGetXmScreen (XtScreen (toplevel));
XtVaSetValues (screen, XmNdefaultSourceCursorIcon, drag_icon, NULL);
...
The specified icon is used whenever the source icon has not been set for the DragContext for a drag and drop transfer.

The Screen resources can also be set in a resource file. In this case, the icons can be specified as bitmap files, so the application does not have to create DragIcon objects. Both the icon and an optional mask can be specified using resources as follows:

*defaultSourceCursorIcon.pixmap: icon.xbm
*defaultSourceCursorIcon.mask:   icon_mask.xbm
Although it is convenient to be able to set the Screen resources in a resource file, this feature really isn't that useful since the Motif widgets and most applications specify their drag icons using DragContext resources.

The resources XmNvalidCursorForeground, XmNinvalidCursorForeground, and XmNnoneCursorForeground of the DragContext can be used to further distinguish between the different states in a drag and drop transfer. These resources can be specified in a resource file as follows:

*validCursorForeground:   green
*invalidCursorForeground: red
*noneCursorForeground:    yellow
In this case, the drag icon changes color as the user moves it between components that are valid drop sites, components that are invalid drop sites, and components that are not drop sites. While it is possible to modify some aspects of the drag-over effects using Screen and DragContext resources, if you really want to provide customized visual effects, you need to understand more about the implementation of drag and drop. In the Working With Drop Sites Section we discuss how to provide custom drag-over effects.

Working With Drag Sources

Many applications work with data other than text. In order to provide drag and drop capabilities, these applications need to create drag sources for the data they manipulate. In this section, we describe the steps you need to follow to create a new drag source. We use an example program that displays all the files in a directory and allows the user to drag the files. However, in order for this drag to succeed, we need another application that understands files as objects and allows the user to drop files. In the Working With Drop Sites Section, we present a text editor that handles the dropping of file data, but for now we are just going to consider the ability to drag a file. Example 22-1 shows the file_manager.c application, which we are going to describe in detail in the following sections.3

Example  22-1 The file_manager.c program

/* file_manager.c -- displays all of the files in the current directory
** and creates a drag source for each file. The user can drag the
** contents of the file to another application that understands
** dropping file data. Demonstrates creating a drag source, creating
** drag icons, and handling data conversion.
*/

#include <Xm/Screen.h>
#include <Xm/ScrolledW.h>
#include <Xm/RowColumn.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/AtomMgr.h>
#include <Xm/DragDrop.h>
#include <X11/Xos.h>
#include <stdio.h>
#include <sys/stat.h>

typedef struct {
    char       *file_name;
    Boolean    is_directory;
} FileInfo;

/* global variable -- arbitrarily limit number of files to 256 */
FileInfo     files[256];
void         StartDrag(Widget, XEvent *, String *, Cardinal *);

/* translations and actions. Pressing mouse button 2 calls
** StartDrag to start a drag transaction
*/
static char dragTranslations[] = "#override <Btn2Down>: StartDrag()";
static XtActionsRec dragActions[] = {
    {"StartDrag", (XtActionProc) StartDrag}
    };

main (int argc, char *argv[])
{
    Arg                args[12];
    int                num_files, n, i = 0;
    Widget             toplevel, sw, panel, form, label;
    Display            *dpy;
    Atom               FILE_CONTENTS, FILE_NAME, DIRECTORY;
    XtAppContext       app;
    XtTranslations     parsed_trans;
    char               *p, buf[256];
    FILE               *pp, *popen();
    struct stat        s_buf;
    Pixmap             file, dir, pixmap;
    Pixel              fg, bg;

    XtSetLanguageProc (NULL, NULL, NULL);
    toplevel = XtOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, 
                                    sessionShellWidgetClass, NULL, 0);

    /* intern the Atoms for data targets */
    dpy = XtDisplay (toplevel);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    DIRECTORY = XInternAtom (dpy, "DIRECTORY", False);

    /* use popen to get the files in the directory */
    sprintf (buf, "/bin/ls .");
    if (!(pp = popen (buf, "r"))) {
        perror (buf);
        exit (1);
    }

    /* read output from popen -- store filename and type */
    while (fgets (buf, sizeof (buf), pp) && (i < 256)) {
        if (p = index (buf, '\n'))
            *p = 0;
        if (stat (buf, &s_buf) == -1)
            continue;
        else if ((s_buf.st_mode &S_IFMT) == S_IFDIR)
            files[i].is_directory = True;
        else if (!(s_buf.st_mode & S_IFREG))
            continue;
        else
            files[i].is_directory = False;
        files[i].file_name = XtNewString (buf);
        i++;
    }
    pclose (pp);
    num_files = i;

    /* create a scrolled window to contain the file labels */
    n = 0;
    XtSetArg (args[n], XmNwidth, 200);                   n++;
    XtSetArg (args[n], XmNheight, 300);                  n++;
    XtSetArg (args[n], XmNscrollingPolicy, XmAUTOMATIC); n++;
    sw = XmCreateScrolledWindow (toplevel, "sw", args, n);
    panel = XmCreateRowColumn (sw, "panel", NULL, 0);
    /* get foreground and background colors and create label pixmaps */
    XtVaGetValues (panel, XmNforeground, &fg, XmNbackground, &bg, NULL);
    file = XmGetPixmap (XtScreen (panel), "file.xbm", fg, bg);
    dir = XmGetPixmap (XtScreen (panel), "dir.xbm", fg, bg);
    if (file == XmUNSPECIFIED_PIXMAP || dir == XmUNSPECIFIED_PIXMAP) {
        puts ("Couldn't load pixmaps");
        exit (1);
    }

    parsed_trans = XtParseTranslationTable (dragTranslations);
    XtAppAddActions (app, dragActions, XtNumber (dragActions));

    /* create image and filename Labels for each file */
    for (i = 0; i < num_files; i++) {
        form = XmCreateForm (panel, "form", NULL, 0);

    if (files[i].is_directory)  pixmap = dir;
    else                        pixmap = file;

    n = 0;
    /* specify translation for drag and index into file array */
    XtSetArg (args[n], XmNtranslations, parsed_trans);         n++;
    XtSetArg (args[n], XmNuserData, i);                        n++;
    XtSetArg (args[n], XmNlabelType, XmPIXMAP);                n++;
    XtSetArg (args[n], XmNlabelPixmap, pixmap);                n++;
    XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM);       n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM);    n++;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);      n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION); n++;
    XtSetArg (args[n], XmNrightPosition, 25);                  n++;
    label = XmCreateLabel (form, "type", args, n);
    XtManageChild (label);

    n = 0;
    XtSetArg (args[n], XmNalignment, XmALIGNMENT_BEGINNING);   n++;
    XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM);       n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM);    n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM);     n++;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION);  n++;
    XtSetArg (args[n], XmNleftPosition, 25);                   n++;
        label = XmCreateLabel (form, files[i].file_name, args, n);
        XtManageChild (label);
        XtManageChild (form);
    }

    XtManageChild (panel);
    XtManageChild (sw);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app);
}

/* StartDrag() -- action routine called by initiator when a drag starts
** (in this case, when mouse button 2 is pressed). It starts
** the drag processing and establishes a drag context.
*/
void StartDrag (Widget widget, XEvent *event, String *params, 
                    Cardinal *num_params)
{
    Arg          args[10];
    int          n, i;
    Display      *dpy;
    Atom         FILE_CONTENTS, FILE_NAME, DIRECTORY;
    Atom         exportList[2];
    Widget       drag_icon, dc;
    Pixel        fg, bg;
    Pixmap       icon, iconmask;
    XtPointer    ptr;
    Boolean      ConvertProc(Widget, Atom *, Atom *, Atom *,
                            XtPointer *, unsigned long *, int *);
    void         DragDropFinish(Widget, XtPointer, XtPointer);

    Boolean      ConvertProc();
    void         DragDropFinish();

    /* intern the Atoms for data targets */
    dpy = XtDisplay (widget);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    DIRECTORY = XInternAtom (dpy, "DIRECTORY", False);

    /* get background and foreground colors and fetch index into file
    ** array from XmNuserData.
    */
    XtVaGetValues (widget, XmNbackground, &bg, XmNforeground, &fg, XmNuserData, 
                    &ptr, NULL);

    /* create pixmaps for drag icon -- either file or directory */
    i = (int) ptr;
    if (files[i].is_directory) {
        icon = XmGetPixmapByDepth (XtScreen (widget), "dir.xbm", 1, 0, 1);
        iconmask = XmGetPixmapByDepth (XtScreen (widget), "dirmask.xbm", 
                                                                1, 0, 1);
    }
    else {
        icon = XmGetPixmapByDepth (XtScreen (widget), "file.xbm", 1, 0, 1);
        iconmask = XmGetPixmapByDepth (XtScreen (widget), "filemask.xbm", 
                                                                1, 0, 1);
    }
    if (icon == XmUNSPECIFIED_PIXMAP || iconmask == XmUNSPECIFIED_PIXMAP) {
        puts ("Couldn't load pixmaps");
        exit (1);
    }
    n = 0;
    XtSetArg (args[n], XmNpixmap, icon);   n++;
    XtSetArg (args[n], XmNmask, iconmask); n++;
    drag_icon = XmCreateDragIcon (widget, "drag_icon", args, n);

    /* specify resources for DragContext for the transfer */
    n = 0;
    XtSetArg (args[n], XmNblendModel, XmBLEND_ALL);       n++;
    XtSetArg (args[n], XmNcursorBackground, bg);          n++;
    XtSetArg (args[n], XmNcursorForeground, fg);          n++;
    XtSetArg (args[n], XmNsourceCursorIcon, drag_icon);   n++;

    /* establish the list of valid target types */
    if (files[i].is_directory) {
        exportList[0] = DIRECTORY;
        XtSetArg (args[n], XmNexportTargets, exportList); n++;
        XtSetArg (args[n], XmNnumExportTargets, 1);       n++;
    }
    else {
        exportList[0] = FILE_CONTENTS;
        exportList[1] = FILE_NAME;
        XtSetArg (args[n], XmNexportTargets, exportList); n++;
        XtSetArg (args[n], XmNnumExportTargets, 2);       n++;
    }
    XtSetArg (args[n], XmNdragOperations, XmDROP_COPY);   n++;
    XtSetArg (args[n], XmNconvertProc, ConvertProc);      n++;
    XtSetArg (args[n], XmNclientData, widget);            n++;

    /* start the drag and register a callback to clean up when done */
    dc = XmDragStart (widget, event, args, n);
    XtAddCallback (dc, XmNdragDropFinishCallback, DragDropFinish, NULL);
}

/* ConvertProc() -- convert the file data to the format requested
** by the drop site.
*/
Boolean ConvertProc ( Widget           widget,
                     Atom             *selection,
                     Atom             *target,
                     Atom             *type_return,
                     XtPointer        *value_return,
                     unsigned long    *length_return,
                     int              *format_return)
{
    Display          *dpy;
    Atom             FILE_CONTENTS, FILE_NAME, MOTIF_DROP;
    XtPointer        ptr;
    Widget           label;
    int              i;
    char             *text;
    struct stat      s_buf;
    FILE             *fp;
    long             length;
    String           str;

    /* intern the Atoms for data targets */
    dpy = XtDisplay (widget);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    MOTIF_DROP = XInternAtom (dpy, "_MOTIF_DROP", False);

    /* check if we are dealing with a drop */
    if (*selection != MOTIF_DROP)
        return False;

    /* get the drag source widget */
    XtVaGetValues (widget, XmNclientData, &ptr, NULL);
    label = (Widget) ptr;
    if (label == NULL)
        return False;

    /* get the index into the file array from XmNuserData from the
    ** drag source widget.
    */
    XtVaGetValues (label, XmNuserData, &ptr, NULL);
    i = (int) ptr;
    /* this routine processes only file contents and file name */
    if (*target == FILE_CONTENTS) {
        /* get the contents of the file */
        if (stat (files[i].file_name, &s_buf) == -1 ||
                    (s_buf.st_mode & S_IFMT) != S_IFREG ||
                    !(fp = fopen (files[i].file_name, "r")))
            return False;
        length = s_buf.st_size;

        if (!(text = XtMalloc ((unsigned) (length + 1))))
            return False;
        else if (fread (text, sizeof (char), length, fp) != length)
            return False;
        else
            text[length] = 0;
        fclose (fp);

        /* format the value for transfer */
        *type_return = FILE_CONTENTS;
        *value_return = (XtPointer) text;
        *length_return = length;
        *format_return = 8;
        return True;
    }
    else if (*target == FILE_NAME) {
        str = XtNewString (files[i].file_name);
        /* format the value for transfer */
        *type_return = FILE_NAME;
        *value_return = (XtPointer) str;
        *length_return = strlen (str) + 1;
        *format_return = 8;
        return True;
    }
    else
        return False;
}

/* DragDropFinish() -- clean up after a drag and drop transfer. */
void DragDropFinish (Widget widget, XtPointer client_data, XtPointer call_data)
{
    Widget source_icon = NULL;
    XtVaGetValues (widget, XmNsourceCursorIcon, &source_icon, NULL);
    if (source_icon)
        XtDestroyWidget (source_icon);
}
The output of this application is shown in Figure 22-6.

Figure  22-6 The output of the file_manager program

The application gets the names of all of the files in the current directory4and displays the filenames using Label widgets. Each file has a file or folder image next to it, depending on whether it is a regular file or a directory. The images are the drag sources for manipulating the files. If the user presses the middle mouse button over one of the symbols, the pointer changes to a drag icon and he can drag the file to another application that has a drop site that understands files.


Creating a Drag Source

When the application reads the files in the directory, it creates a global array of structures that contain information about the files. This information is used to keep track of filenames and file types. For each file, the application creates two Label widgets: an image that represents the type of the file and a string that specifies the filename. To link the image Labels to the array, the application passes the index of each file in the array as the XmNuserData resource for the associated Label. This value can be retrieved and used to access the information in the array.

Depending on whether a file is a regular file or a directory, the application places an image of a file or a folder next to the filename Label. Each image is created using XmGetPixmap() and specified as the XmNlabelPixmap for the appropriate image Labels. The images are also used for drag icons during the drag operation, as we describe in the next section. For more information on XmGetPixmap(), see the Pixmaps Section in Chapter 3.

In order to specify that the file images are drag sources, we have to establish translations for the Label widgets that are used for the images. Label widgets already have drag source functionality, so we need to decide whether to override or augment this functionality. Since the existing translation merely allows the user to drag the pixmap image for the Label, we override the translation as shown in the following code fragment:

static char dragTranslations[] = "#override <Btn2Down>: StartDrag()";
static XtActionsRec dragActions[] = {
    {"StartDrag", (XtActionProc) StartDrag}
};
The application parses the translation table and adds the action using XtParseTranslationTable() and XtAppAddActions(), respectively. The new translation table is specified for the XmNtranslations resource for each of the image Labels.

The only other operation performed in main() that is relevant for the drag functionality is the interning of atoms for target types. We use the FILE_NAME target that is defined by the ICCCM, as well as two of our own targets, FILE_CONTENTS and DIRECTORY. We chose these target names ourselves because the ICCCM does not define any targets that are suitable for our purposes. We create atoms for these targets using XInternAtom(), as shown in the following code fragment5:

Widget     toplevel;
Display    *dpy;
Atom       FILE_CONTENTS, FILE_NAME, DIRECTORY;

dpy = XtDisplay (toplevel);

FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
DIRECTORY = XInternAtom (dpy, "DIRECTORY", False);
Although we don't actually use the atoms in main(), we intern them so that they are cached by the Motif toolkit. When we intern the atoms in other routines in the application, they are retrieved from the cache.


Starting the Drag

When the user starts a drag, the DragStart() action routine is called. This routine creates a custom dragicon and calls XmDragStart() to start the drag. To create the appropriate drag icon, we need to know whether the drag source represents a file or a directory, so we fetch the XmNuserData from the Label widget that is the drag source. We use this value to access the appropriate structure in the files array and determine the type of file the user is manipulating.

Once we know what type of file we are dealing with, we can create the source icon for the drag. We use the same pixmap as for the image Label, so the drag icon is either a file image or a folder image. We use XmGetPixmapByDepth() to create both the icon and an iconmask so that we can specify a depth of 1. We call XmCreateDragIcon() to create the drag icon, as shown in the following code fragment from Example 22-1:

n = 0;
XtSetArg (args[n], XmNpixmap, icon);   n++;
XtSetArg (args[n], XmNmask, iconmask); n++;
drag_icon = XmCreateDragIcon (widget, "drag_icon", args, n);
The DragIcon is created as a child of the drag source widget. We only need to specify the XmNpixmap and XmNmask resources because the DragIcon sets its other attributes, such as width and height, based on the pixmap. The DragIcon takes its foreground and background colors from its parent, so we don't need to specify these resources either. The XmNmask resource must be set to a pixmap of depth 1, while the XmNpixmap can be any depth.

Now that we have a DragIcon object for the source icon, we can call XmDragStart() to start the drag as shown below:

n = 0;
XtSetArg (args[n], XmNblendModel, XmBLEND_ALL);     n++;
XtSetArg (args[n], XmNcursorBackground, bg);        n++;
XtSetArg (args[n], XmNcursorForeground, fg);        n++;
XtSetArg (args[n], XmNsourceCursorIcon, drag_icon); n++;

if (files[i].directory) {
    exportList[0] = DIRECTORY;
    XtSetArg (args[n], XmNexportTargets, exportList); n++;
    XtSetArg (args[n], XmNnumExportTargets, 1);       n++;
} else {
    exportList[0] = FILE_CONTENTS;
    exportList[1] = FILE_NAME;
    XtSetArg (args[n], XmNexportTargets, exportList); n++;
    XtSetArg (args[n], XmNnumExportTargets, 2);       n++;
}

XtSetArg (args[n], XmNdragOperations, XmDROP_COPY); n++;
XtSetArg (args[n], XmNconvertProc, ConvertProc);    n++;
XtSetArg (args[n], XmNclientData, widget);          n++;
dc = XmDragStart (widget, event, args, n);
This routine creates a DragContext object for the drag and drop transfer and sets a number of resources for the DragContext. The XmNsourceCursorIcon specifies the source drag icon that we just created. We also specify the background and foreground colors for the icon. The DragContext also has the resources XmNoperationCursorIcon and XmNstateCursorIcon for specifying the operation and state icons, but our drag icon does not use these parts, so we don't set the resources.

The XmNblendModel resource controls the components of the drag icon that are used during the drag. This resource can take one of the following values:

XmBLEND_ALL                  XmBLEND_STATE_SOURCE
XmBLEND_JUST_SOURCE          XmBLEND_NONE
XmBLEND_ALL indicates that all three parts of the drag icon should be used, while XmBLEND_STATE_SOURCE causes only the state and source icons to be used. We specify the value XmBLEND_ALL since we want our source icon to be used for the drag icon, as well as the default system operation and status icons. XmBLEND_NONE means that the DragContext does not generate a drag icon.

Another important set of resources are the XmNexportTargets and XmNnumExportTargets resources. These resources specify the data targets to which the drag source can convert the actual data. The XmNexportTargets resource contains a list of target atoms.If the file is a directory, we specify the DIRECTORY target. Otherwise, we specify both the FILE_CONTENTS and FILE_NAME targets, which means that the drag source can provide both a filename and the actual contents of the file to a drop site. In order for the drag to succeed, another application must use at least one of these targets for a drop site so that the user has some place to drop the data.

The XmNdragOperations resource specifies all of the operations that are supported by the application. This value is specified as a bit mask formed by combining the following possible values:

XmDROP_COPY        XmDROP_MOVE         XmDROP_LINK        XmDROP_NOOP
For the limited purpose of this application, we specify XmDROP_COPY because we only allow the user to copy the contents of a file. A fully-functional file manager application would probably also support moving and copying files within the directory structure, but that functionality is beyond the scope of our discussion. During the drag, the operations supported by the current drop site are matched against those supported by the drag source to see if the transfer is possible.

The final DragContext resource that we specify is the XmNconvertProc. This resource indicates the procedure that is called to convert the actual data into the format requested by the drop site when the drop occurs. We specify the ConvertProc() routine for our application; this routine is described in the next section. We also set XmNclientData to the Label widget that started the drag, so that we have access to the filename and file type data stored about that Label, as this information is needed to process the drop.

After we create the DragContext and start the drag with XmDragStart(), we register a callback routine for the XmNdragDropFinishCallback so that we can destroy the DragIcon that we created. This routine is discussed further in the Cleaning Up Section later in this chapter.


Converting the Data

When a drop occurs, a procedure that has been registered by the drop site is called to verify that the drop can take place. This procedure checks the status of the operation and then starts the data transfer. The receiving application requests the format that it wants to receive the data in; the receiver can even request the data in multiple formats, if they are available. For each requested data target, the initiating application's XmNconvertProc is invoked. In our case, this is the ConvertProc() routine. Since we are not using incremental transfer, this routine is of type XtConvertSelectionProc, which takes the following form:

typedef Boolean (*XtConvertSelectionProc)( Widget         widget,
                                           Atom           *selection,
                                           Atom           *target,
                                           Atom           *type_return,
                                           XtPointer      *value_return,
                                           unsigned long  *length_return,
                                           int            *format_return)
The widget parameter is the DragContext for the drag operation, selection is the selection atom, which in this case is _MOTIF_DROP, and target is the type of information requested about the selection. The type_return, value_return, length_return, and format_return parameters return the type, value, length, and format of the converted data. The routine should return True if the conversion succeeds and False otherwise. For more information about this procedure type, see Volume 4, X Toolkit Intrinsics Programming Manual, and the appropriate reference page in Volume 5, X Toolkit Intrinsics Reference Manual.

The ConvertProc() routine in Example 22-1 starts by retrieving the Label widget from the XmNclientData resource of the DragContext. The goal is to get an index into the files array so that we can access information about the file. The index is stored in the XmNuserData resource of the Label widget. Once we have the index, we can use it to get the filename from the array.

Our conversion routine only handles requests for a filename or the contents of a file. If target is set to FILE_CONTENTS, ConvertProc() retrieves the contents of the file and formats the data for transfer back to the receiving client. The contents of the file are passed as a pointer to the text, using the value_return parameter. If the drop site has requested the FILE_NAME target, the routine returns the filename in value_return. In either case, the length_return argument is set to the length of the text, and format_return is set to 8 to specify the length of each of the elements in value_return. The return_type parameter is set to the appropriate target. If the drop site has requested any other target, the routine returns False to indicate that the transfer has failed.

The conversion routine does not handle the DIRECTORY target, partly because we have not implemented any drop sites that understand the target. A real file manager application would want to support the dragging of directories to allow the user to modify the file system using drag and drop. In this case, the conversion procedure would need to have another branch for handling the DIRECTORY target.

Since the drag source only supports the copy operation, the conversion routine does not have to worry about deleting the existing data. With a copy operation, the XmNconvertProc returns a pointer to the data so that when the operation is done, both the initiator and the receiver have a copy of the data. With a move operation, the initiating application returns a pointer to the data and then waits for the receiver to tell it to delete the data. The receiving application gets the data, stores it, and then specifies the DELETE target to handle this situation. When the initiating client gets this target, it can safely delete the data. With a link operation, the initiator again passes a pointer to the data, but in this case the receiver uses the pointer to establish a link to the data.


Modifying an Existing Drag Source

In file_manager.c, we decided to replace the existing drag capabilities of the image Label widgets and provide our own functionality instead. By default, the Labels would function as graphical drag sources, but since there are no drop sites that support graphical data, there is no reason to preserve this functionality.

However, if you want to provide the default functionality for a drag source as well as your own functionality, the set up of the drag source becomes more complicated. Each Motif widget that acts as a drag source has a translation and action that starts the drag. Since the existing action calls XmDragStart() for the transfer, another action routine cannot call XmDragStart() again. The solution to this problem is to write an action routine that retrieves the DragContext for the transfer and modifies its resources.

In our application, we want to augment the drag source functionality of the filename Labels. If the user drags the Label to a drop site that understands file objects, the actual file is transferred. Otherwise, the default drag functionality for the Label causes the text of the Label to be passed to the dropsite. The first thing that we need to do is modify the translations for the Label widgets. Since we want to provide the default functionality, the new translation calls the widget's existing drag action routine followed by our own action. The existing drag action routine for the Label widget is ProcessDrag(), so the translations and actions for the application can be defined as follows:

static char dragTranslations[] = "#override <Btn2Down>: StartDrag()";
static char newdragTranslations[] = 
                    "#override <Btn2Down>: ProcessDrag() UpdateDrag()";
static XtActionsRec dragActions[] = {
    {"StartDrag", (XtActionProc) StartDrag},
    {"UpdateDrag", (XtActionProc) UpdateDrag}
};
As always, the translations need to be parsed using XtParseTranslationTable(), and the actions need to be registered using XtAppAddActions(). Now, when we create each of the filename Labels, we can specify the new translation for the XmNtranslations resource, as shown in the following code fragment:

parsed_trans_text = XtParseTranslationTable (newdragTranslations);
...
n = 0;
XtSetArg (args[n], XmNtranslations, parsed_trans_text); n++;
XtSetArg (args[n], XmNuserData, i); n++;
...
label = XmCreateLabel (form, files[i].file_name, args, n);
Note that we also specify the index in the files array as the XmNuserData for these widgets, just as we did for the image Labels in Example 22-1.

The UpdateDrag() action routine is invoked after the Label's default drag action, which means that XmDragStart() has already been called for the operation. Our action routine retrieves the DragContext for the operation and modifies it, as shown in Example 22-26.

Example  22-2 The UpdateDrag() routine

void (*convert_proc) (Widget, Atom *, Atom *, Atom *,
                        XtPointer *, unsigned long *, int *);

void UpdateDrag (Widget widget, XEvent *event, String *params, 
                    Cardinal *num_params)
{
    Arg              args[10];
    int              n, m, i;
    Display          *dpy;
    Atom             FILE_CONTENTS, FILE_NAME, DIRECTORY;
    Widget           drag_icon, dc;
    Pixel            fg, bg;
    Pixmap           icon, iconmask;
    XtPointer        ptr;
    Boolean          NewConvertProc(Widget, Atom *, Atom *, Atom *, 
                                    XtPointer *, unsigned long *, int *);
    void             DragDropFinish(Widget, XtPointer, XtPointer);
    Cardinal         numExportTargets;
    Atom             *exportTargets, *newTargets;

    /* intern the Atoms for data targets */
    dpy = XtDisplay (widget);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    DIRECTORY = XInternAtom (dpy, "DIRECTORY", False);

    /* get background and foreground colors and fetch index into file
    ** array from XmNuserData.
    */
    XtVaGetValues (widget, XmNforeground, &fg, XmNbackground, &bg, XmNuserData, 
                        &ptr, NULL);

    /* create pixmaps for drag icon -- either file or directory */
    i = (int) ptr;
    if (files[i].is_directory) {
        icon = XmGetPixmapByDepth (XtScreen (widget), "dir.xbm", 1, 0, 1);
        iconmask = XmGetPixmapByDepth (XtScreen (widget), "dirmask.xbm", 
                                                                1, 0, 1);
    }
    else {
        icon = XmGetPixmapByDepth (XtScreen (widget), "file.xbm", 1, 0, 1);
        iconmask = XmGetPixmapByDepth (XtScreen (widget), "filemask.xbm", 
                                                                1, 0, 1);
    }
    if (icon == XmUNSPECIFIED_PIXMAP || iconmask == XmUNSPECIFIED_PIXMAP) {
        puts ("Couldn't load pixmaps");
        exit (1);
    }
    n = 0;
    XtSetArg (args[n], XmNpixmap, icon);   n++;
    XtSetArg (args[n], XmNmask, iconmask); n++;
    drag_icon = XmCreateDragIcon (widget, "drag_icon", args, n);

    /* get the DragContext and retrieve info about it */
    dc = XmGetDragContext (widget, event->xbutton.time);
    n = 0;
    XtSetArg (args[n], XmNexportTargets, &exportTargets);       n++;
    XtSetArg (args[n], XmNnumExportTargets, &numExportTargets); n++;
    XtSetArg (args[n], XmNconvertProc, &convert_proc);          n++;
    XtGetValues (dc, args, n);
    
    /* add new targets to the list of targets */
    n = 0;
    if (files[i].is_directory) {
        newTargets = (Atom *) XtMalloc (sizeof (Atom) * (numExportTargets + 1));
        for (m = 0; m < numExportTargets; m++)
            newTargets[m] = exportTargets[m];
        newTargets[m] = DIRECTORY;
        XtSetArg (args[n], XmNexportTargets, newTargets); n++;
        XtSetArg (args[n], XmNnumExportTargets, numExportTargets + 1); n++;
    } else {
        newTargets = (Atom *) XtMalloc (sizeof (Atom) * (numExportTargets + 2));
        for (m = 0; m < numExportTargets; m++)
            newTargets[m] = exportTargets[m];
        newTargets[m] = FILE_CONTENTS;
        newTargets[m+1] = FILE_NAME;
        XtSetArg (args[n], XmNexportTargets, newTargets);              n++;
        XtSetArg (args[n], XmNnumExportTargets, numExportTargets + 2); n++;
    }

    /* modify other DragContext resources */
    XtSetArg (args[n], XmNblendModel, XmBLEND_ALL);     n++;
    XtSetArg (args[n], XmNcursorBackground, bg);        n++;
    XtSetArg (args[n], XmNcursorForeground, fg);        n++;
    XtSetArg (args[n], XmNsourceCursorIcon, drag_icon); n++; 
    XtSetArg (args[n], XmNdragOperations, XmDROP_COPY); n++;
    XtSetArg (args[n], XmNconvertProc, NewConvertProc); n++;
    XtSetArg (args[n], XmNclientData, widget);          n++;
    XtSetValues (dc, args, n);
    XtAddCallback (dc, XmNdragDropFinishCallback, DragDropFinish, NULL);
}
This routine performs many of the same tasks as the StartDrag() action routine, such as accessing the appropriate structure in the files array and creating a DragIcon for the source icon. The main difference is that we use XmGetDragContext() to retrieve the current DragContext object, rather than creating one using XmDragStart().

The routine retrieves the values of the XmNexportTargets, XmNnumExportTargets, and XmNconvertProc resources using XtGetValues() so that it can preserve the existing functionality. The appropriate new targets are added to the list of targets based on the type of the file, and XmNexportTargets is set to the new list. The NewConvertProc() routine is used for the XmNconvertProc. The rest of the DragContext resources are specified as in StartDrag(), and the DragContext is modified using XtSetValues().

There is only one difference between the NewConvertProc() routine and ConvertProc() in file_manager.c. Instead of simply returning False if the requested target is not FILE_CONTENTS or FILE_NAME, NewConvertProc() calls the conversion procedure retrieved from the Label widget, as shown in the following fragment:

(*convert_proc) ( widget, selection, target, type_return, value_return, 
                  length_return, format_return);
Essentially, our conversion routine handles our data targets and passes other targets to the Label widget's default conversion procedure.


Providing Custom Drag-over Visuals

The DragContext has a number of callback routines that the initiating application can use to provide custom drag-over visuals. These callbacks are invoked when different events occur during the drag, like when the drag icon enters or leaves a drop site. The DragContext provides the following callback routines for monitoring the drag:

XmNdragMotionCallback             XmNdropSiteEnterCallback
XmNdropSiteLeaveCallback          XmNoperationChangedCallback
XmNtopLevelEnterCallback          XmNtopLevelLeaveCallback
The names of the routines are fairly self-explanatory. Each callback has its own special callback structure that contains the relevant information about the current state of the drag operation. For example, the XmNdropSiteEnterCallback uses a callback structure of type XmDropSiteEnterCallbackStruct, which is defined as follows:

typedef struct {
    int              reason;
    XEvent           *event;
    Time             timeStamp;
    unsigned char    operation;
    unsigned char    operations;
    unsigned char    dropSiteStatus;
    Position         x;
    Position         y;
} XmDropSiteEnterCallbackStruct, *XmDropSiteEnterCallback;
The reason field in this structure is always XmCR_DROP_SITE_ENTER. The operation and operations fields specify the current operation and the set of supported operations, respectively. The dropSiteStatus element indicates whether or not the current drop site is valid, based on the targets supported by the drag source and the drop site. This field can have one of the following values:

XmDROP_SITE_VALID      XmDROP_SITE_INVALID      XmNO_DROP_SITE
The operation, operations, and dropSiteStatus fields are initialized by the toolkit based on the values of different resources for both the drag source and the drop site. If the drop site has registered an XmNdragProc and the dynamic protocol is being used, this routine can update these fields as necessary before the data is passed to the callback routine. A drop site might want to update these fields if it is performing any special processing or simulating multiple drop sites.

All of the callback structures for the DragContext callback routines have a reason field that indicates why the callback was invoked. The callback structures also provide information that is relevant to the particular routine; they are all similar to the XmDropSiteEnterCallbackStruct. See the DragContext reference page in Volume 6 B, Motif Reference Manual, for complete information about the different callback structures.

When an application creates the DragContext for a drag, it can register routines for the different callback resources. These routines can perform any special processing that is necessary, as well as handle custom drag-over effects for the transfer. The typical way to handle drag-over effects is to modify the various drag icon resources of the DragContext during the drag. The XmNsourcePixmapIcon, XmNsourceCursorIcon, XmNoperationCursorIcon, and XmNstateCursorIcon resources specify the different components of the drag icon. The XmNvalidCursorForeground, XmNinvalidCursorForeground, and XmNnoneCursorForeground resources of the DragContext can be used to further distinguish between the different states during a drag.

The XmNsourcePixmapIcon is used under the preregister protocol and can be any size, while the XmNsourceCursorIcon is used for the dynamic protocol and is limited to the size of the largest cursor for a particular platform. If you want to specify a color icon, you must use the XmNsourcePixmapIcon resource. If XmNsourcePixmapIcon is not specified, the value of XmNsourceCursorIcon is used. If this resource has not been specified, the default source icon for the Screen object is used.

At any point during a drag, the initiating client can call XmDragCancel() to cancel the transfer. The user can also cancel the operation by pressing the ESCAPE key.The initiating client can retrieve additional information about the current drop site by calling XmDropSiteRetrieve() during the drag.

After the user drops the data in a drop site, the drag source has one last chance to check the status of the transfer and provide custom visual effects. After the receiving client's XmNdropProc completes, the DragContext's XmNdropStartCallback is invoked. This routine has a callback structure of type XmDropStartCallbackStruct, which is defined as follows:

typedef struct {
    int              reason;
    XEvent           *event;
    Time             timeStamp;
    unsigned char    operation;
    unsigned char    operations;
    unsigned char    dropSiteStatus;
    unsigned char    dropAction;
    Position         x;
    Position         y;
} XmDropStartCallbackStruct, *XmDropStartCallback;
The reason field is set to XmCR_DROP_START, while the operation, operations, and dropSiteStatus fields are set as described previously. The dropAction field is set to XmDROP if the user has simply dropped the data, XmDROP_HELP if the user has requested help on the drop site, or XmDROP_CANCEL if the user has cancelled the transfer.


Cleaning Up

The initiating client can also register callbacks that are invoked after a drag and drop transfer has completed. The XmNdropFinishCallback is called after the receiver's XmNtransferProc has finished processing all of the data targets requested by the receiver. This routine receives as call_data a callback structure of the type XmDropFinishCallbackStruct, where the reason field is XmCR_DROP_FINISH.

The XmNdragDropFinishCallback is invoked when the entire operation has completed, which is immediately after theXmNdropFinishCallback.In this case, the callback structure is an XmDragDropFinishCallbackStructure, and reason is XmCR_DRAG_DROP_FINISH. Our application uses this callback to destroy the drag icon that we created, as shown below:

void DragDropFinish (Widget widget, XtPointer client_data, XtPointer call_data)
{
    Widget source_icon = NULL;
    XtVaGetValues (widget, XmNsourceCursorIcon, &source_icon, NULL);
    if (source_icon)
        XtDestroyWidget (source_icon);
}
The widget passed to the callback routine is the DragContext object for the drag and drop transfer. The routine retrieves the source icon from the DragContext and destroys it using XtDestroyWidget().

Working With Drop Sites

In order to handle data from drag sources that provide something other than textual data, an application has to register drop sites that understand other types of data. To make the file_manager.c application useful, we need an application that has drop sites that can handle file objects. In this section, we are going to modify the text editor from Chapter 18, Text Widgets, so that it understands file data. The application contains two drop sites that handle files: the main text entry area and a filename status area. The Example 22-3 shows the main(), HandleDropLabel(), HandleDropText(), and TransferProc() routines for editor_dnd.c. The rest of the routines in the application are the same as in the A Text Editor Section in Chapter 18, so we have not shown them here.7

Example  22-3 The editor_dnd.c program

/* editor_dnd.c -- create an editor application that contains drop sites
** that understand file data. A file can be dragged from another
** application and dropped in the text entry area or the filename status
** area.
*/

#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xm/LabelG.h>
#include <Xm/PushBG.h>
#include <Xm/RowColumn.h>
#include <Xm/MainW.h>
#include <Xm/Form.h>
#include <Xm/FileSB.h>
#include <Xm/SeparatoG.h>
#include <Xm/DragDrop.h>
#include <X11/Xos.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FILE_OPEN 0
#define FILE_SAVE 1
#define FILE_EXIT 2
#define EDIT_CUT 0
#define EDIT_COPY 1
#define EDIT_PASTE 2
#define EDIT_CLEAR 3
#define SEARCH_FIND_NEXT 0
#define SEARCH_SHOW_ALL 1
#define SEARCH_REPLACE 2
#define SEARCH_CLEAR 3

/* global variables */
void (*drop_proc) (Widget, XtPointer, XtPointer);
Widget text_edit, search_text, replace_text, text_output;
Widget toplevel, file_label;

main (int argc, char *argv[])
{
    XtAppContext   app_context;
    Display        *dpy;
    Atom           FILE_CONTENTS, FILE_NAME;
    Widget         main_window, menubar, form, search_panel, label;
    Widget         sep1, sep2;
    void           file_cb(Widget, XtPointer, XtPointer);
    void           edit_cb(Widget, XtPointer, XtPointer);
    void           search_cb(Widget, XtPointer, XtPointer);
    Arg            args[10];
    int            n = 0;
    XmString       open, save, exit, exit_acc, file, edit, cut, clear, 
                   copy, paste, search, next, find, replace;
    Cardinal       numImportTargets;
    Atom           *importTargets, *newTargets;
    Atom           importList[2];
    void           HandleDropLabel(Widget, XtPointer, XtPointer);
    void           HandleDropText(Widget, XtPointer, XtPointer);

    XtSetLanguageProc (NULL, NULL, NULL);
    toplevel = XtVaOpenApplication (&app_context, "Demos", NULL, 0, &argc, 
                                argv, NULL, sessionShellWidgetClass, NULL);
    dpy = XtDisplay (toplevel);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    main_window = XmCreateMainWindow (toplevel, "main_window", NULL, 0);

    /* Create a simple MenuBar that contains three menus */
    file = XmStringCreateLocalized ("File");
    edit = XmStringCreateLocalized ("Edit");
    search = XmStringCreateLocalized ("Search");
    menubar = XmVaCreateSimpleMenuBar (main_window, "menubar",
                                        XmVaCASCADEBUTTON, file, 'F',
                                        XmVaCASCADEBUTTON, edit, 'E',
                                        XmVaCASCADEBUTTON, search, 'S',
                                        NULL);
    XmStringFree (file);
    XmStringFree (edit);
    XmStringFree (search);

    /* First menu is the File menu -- callback is file_cb() */
    open = XmStringCreateLocalized ("Open...");
    save = XmStringCreateLocalized ("Save...");
    exit = XmStringCreateLocalized ("Exit");
    exit_acc = XmStringCreateLocalized ("Ctrl+C");
    XmVaCreateSimplePulldownMenu (menubar, "file_menu", 0, file_cb,
                            XmVaPUSHBUTTON, open, 'O', NULL, NULL,
                            XmVaPUSHBUTTON, save, 'S', NULL, NULL,
                            XmVaSEPARATOR,
                            XmVaPUSHBUTTON, exit, 'x', "Ctrl<Key>c", exit_acc,
                            NULL);
    XmStringFree (open);
    XmStringFree (save);
    XmStringFree (exit);
    XmStringFree (exit_acc);

    /* ...create the "Edit" menu -- callback is edit_cb() */
    cut = XmStringCreateLocalized ("Cut");
    copy = XmStringCreateLocalized ("Copy");
    clear = XmStringCreateLocalized ("Clear");
    paste = XmStringCreateLocalized ("Paste");
    XmVaCreateSimplePulldownMenu (menubar, "edit_menu", 1, edit_cb,
                                XmVaPUSHBUTTON, cut, 't', NULL, NULL,
                                XmVaPUSHBUTTON, copy, 'C', NULL, NULL,
                                XmVaPUSHBUTTON, paste, 'P', NULL, NULL,
                                XmVaSEPARATOR,
                                XmVaPUSHBUTTON, clear, 'l', NULL, NULL,
                                NULL);
    XmStringFree (cut);
    XmStringFree (copy);
    XmStringFree (paste);

    /* create the "Search" menu -- callback is search_cb() */
    next = XmStringCreateLocalized ("Find Next");
    find = XmStringCreateLocalized ("Show All");
    replace = XmStringCreateLocalized ("Replace Text");
    XmVaCreateSimplePulldownMenu (menubar, "search_menu", 2, search_cb,
                                XmVaPUSHBUTTON, next, 'N', NULL, NULL,
                                XmVaPUSHBUTTON, find, 'A', NULL, NULL,
                                XmVaPUSHBUTTON, replace, 'R', NULL, NULL,
                                XmVaSEPARATOR,
                                XmVaPUSHBUTTON, clear, 'C', NULL, NULL,
                                NULL);
    XmStringFree (next);
    XmStringFree (find);
    XmStringFree (replace);
    XmStringFree (clear);
    XtManageChild (menubar);

    /* create a form work are */
    form = XmCreateForm (main_window, "form", NULL, 0);

    /* create horizontal RowColumn inside the form */
    n = 0;
    XtSetArg (args[n], XmNorientation, XmHORIZONTAL);      n++;
    XtSetArg (args[n], XmNpacking, XmPACK_TIGHT);          n++;
    XtSetArg (args[n], XmNtopAttachment, XmATTACH_FORM);   n++;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);  n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM); n++;
    search_panel = XmCreateRowColumn (form, "search_panel", args, n);

    /* Create two TextField widgets with Labels... */
    label = XmCreateLabelGadget (search_panel, "Search Pattern", NULL, 0);
    XtManageChild (label);

    search_text = XmCreateTextField (search_panel, "search_text", NULL, 0);
    XtManageChild (search_text);

    label = XmCreateLabelGadget (search_panel, "Replace Pattern", NULL, 0);
    XtManageChild (label);

    replace_text = XmCreateTextField (search_panel, "replace_text", NULL, 0);
    XtManageChild (replace_text);
    XtManageChild (search_panel);

    n = 0;
    XtSetArg (args[n], XmNeditable, False);                 n++;
    XtSetArg (args[n], XmNcursorPositionVisible, False);    n++;
    XtSetArg (args[n], XmNshadowThickness, 0);              n++;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);   n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM);  n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_FORM); n++;
    text_output = XmCreateTextField (form, "text_output", args, n);
    XtManageChild (text_output);

    n = 0;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);     n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM);    n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
    XtSetArg (args[n], XmNbottomWidget, text_output);         n++;
    sep2 = XmCreateSeparatorGadget (form, "sep2", args, n);
    XtManageChild (sep2);

    /* create file status area */
    n = 0;
    XtSetArg (args[n], XmNalignment, XmALIGNMENT_BEGINNING);  n++;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);     n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM);    n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
    XtSetArg (args[n], XmNbottomWidget, sep2);                n++;
    file_label = XmCreateLabelGadget (form, "Filename:", args, n);
    XtManageChild (file_label);

    /* register the file status label as a drop site */
    n = 0;
    importList[0] = FILE_CONTENTS;
    importList[1] = FILE_NAME;
    XtSetArg (args[n], XmNimportTargets, importList);               n++;
    XtSetArg (args[n], XmNnumImportTargets, XtNumber (importList)); n++;
    XtSetArg (args[n], XmNdropSiteOperations, XmDROP_COPY);         n++;
    XtSetArg (args[n], XmNdropProc, HandleDropLabel);               n++;
    XmDropSiteRegister (file_label, args, n);

    n = 0;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);     n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM);    n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
    XtSetArg (args[n], XmNbottomWidget, file_label);          n++;
    sep1 = XmCreateSeparatorGadget (form, "sep1", args, n);
    XtManageChild (sep1);

    /* create text entry area */
    n = 0;
    XtSetArg (args[n], XmNrows, 10);                          n++;
    XtSetArg (args[n], XmNcolumns, 80);                       n++;
    XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT);       n++;
    XtSetArg (args[n], XmNtopAttachment, XmATTACH_WIDGET);    n++;
    XtSetArg (args[n], XmNtopWidget, search_panel);           n++;
    XtSetArg (args[n], XmNleftAttachment, XmATTACH_FORM);     n++;
    XtSetArg (args[n], XmNrightAttachment, XmATTACH_FORM);    n++;
    XtSetArg (args[n], XmNbottomAttachment, XmATTACH_WIDGET); n++;
    XtSetArg (args[n], XmNbottomWidget,     sep1);            n++;
    text_edit = XmCreateScrolledText (form, "text_edit", args, n);
    XtManageChild (text_edit);

    /* retrieve drop site info so that we can modify it */
    n = 0;
    XtSetArg (args[n], XmNimportTargets, &importTargets);       n++;
    XtSetArg (args[n], XmNnumImportTargets, &numImportTargets); n++;
    XtSetArg (args[n], XmNdropProc, &drop_proc);                n++;
    XmDropSiteRetrieve (text_edit, args, n);

    /* add FILE_CONTENTS and FILE_NAME to the list of targets */
    newTargets = (Atom *) XtMalloc (sizeof (Atom) * (numImportTargets + 2));

    for (n = 0; n < numImportTargets; n++)
        newTargets[n] = importTargets[n];
    newTargets[n] = FILE_CONTENTS;
    newTargets[n+1] = FILE_NAME;

    /* update the drop site */
    n = 0;
    XtSetArg (args[n], XmNimportTargets, newTargets); n++;
    XtSetArg (args[n], XmNnumImportTargets, numImportTargets+2); n++;
    XtSetArg (args[n], XmNdropProc, HandleDropText); n++;
    XmDropSiteUpdate (text_edit, args, n);

    XtManageChild (form);
    XtManageChild (main_window);
    XtRealizeWidget (toplevel);
    XtAppMainLoop (app_context);
}

/* HandleDropLabel() -- start the data transfer when data is dropped in
** the filename status area.
*/
void HandleDropLabel (Widget widget, XtPointer client_data, 
                        XtPointer call_data)
{
    Display                  *dpy;
    Atom                     FILE_CONTENTS, FILE_NAME;
    XmDropProcCallback       DropData;
    XmDropTransferEntryRec   transferEntries[2];
    XmDropTransferEntry      transferList;
    Arg                      args[10];
    int                      n, i;
    Widget                   dc;
    Cardinal                 numExportTargets;
    Atom                     *exportTargets;
    Boolean                  file_name = False;
    void                     TransferProc(Widget, XtPointer, Atom *, Atom *, 
                                        XtPointer, unsigned long *, int);


    /* intern the Atoms for data targets */
    dpy = XtDisplay (toplevel);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    DropData = (XmDropProcCallback) call_data;
    dc = DropData->dragContext;

    /* retrieve the data targets and search for FILE_NAME */
    n = 0;
    XtSetArg (args[n], XmNexportTargets, &exportTargets);       n++;
    XtSetArg (args[n], XmNnumExportTargets, &numExportTargets); n++;
    XtGetValues (dc, args, n);
    for (i = 0; i < numExportTargets; i++) {
        if (exportTargets[i] == FILE_NAME) {
            file_name = True;
            break;
        }
    }

    /* make sure we have a drop that is a copy operation and one of
    ** the targets is FILE_NAME. if not, set the status to failure.
    */
    n = 0;
    if ((!file_name) || (DropData->dropAction != XmDROP) ||
                        (DropData->operation != XmDROP_COPY)) {
        XtSetArg (args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
        XtSetArg (args[n], XmNnumDropTransfers, 0);                n++;
    }
    else {
        /* set up transfer requests for drop site */
        transferEntries[0].target = FILE_CONTENTS;
        transferEntries[0].client_data = (XtPointer) text_edit;
        transferEntries[1].target = FILE_NAME;
        transferEntries[1].client_data = (XtPointer) file_label;
        transferList = transferEntries;
        XtSetArg (args[n], XmNdropTransfers, transferEntries); n++;
        XtSetArg (args[n], XmNnumDropTransfers,
                                XtNumber (transferEntries));  n++;
        XtSetArg (args[n], XmNtransferProc, TransferProc);     n++;
    }
    XmDropTransferStart (dc, args, n);
}

/* HandleDropText() -- start the data transfer when data is dropped in
** the text entry area.
*/
void HandleDropText (Widget widget, XtPointer client_data, XtPointer call_data)
{
    Display                *dpy;
    Atom                   FILE_CONTENTS, FILE_NAME;
    XmDropProcCallback     DropData;
    XmDropTransferEntryRec transferEntries[2];
    XmDropTransferEntry    transferList;
    Arg                    args[10];
    int                    n, i;
    Widget                 dc;
    Cardinal               numExportTargets;
    Atom                   *exportTargets;
    Boolean                file_contents = False;
    void                   TransferProc(Widget, XtPointer, Atom *, Atom *, 
                                        XtPointer, unsigned long *, int);

    /* intern the Atoms for data targets */
    dpy = XtDisplay (toplevel);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    DropData = (XmDropProcCallback) call_data;
    dc = DropData->dragContext;

    /* retrieve the data targets and search for FILE_CONTENTS */
    n = 0;
    XtSetArg (args[n], XmNexportTargets, &exportTargets); n++;
    XtSetArg (args[n], XmNnumExportTargets, &numExportTargets); n++;
    XtGetValues (dc, args, n);
    for (i = 0; i < numExportTargets; i++) {
        if (exportTargets[i] == FILE_CONTENTS) {
            file_contents = True;
            break;
        }
    }
    if (file_contents) {
        /* make sure we have a drop that is a copy operation.
        ** if not, set the status to failure.
        */
        n = 0;
        if ((DropData->dropAction != XmDROP) ||
                                (DropData->operation != XmDROP_COPY)) {
            XtSetArg (args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
            XtSetArg (args[n], XmNnumDropTransfers, 0); n++;
        }
        else {
            /* set up transfer requests for drop site */
            transferEntries[0].target = FILE_CONTENTS;
            transferEntries[0].client_data = (XtPointer) text_edit;
            transferEntries[1].target = FILE_NAME;
            transferEntries[1].client_data = (XtPointer) file_label;
            transferList = transferEntries;
            XtSetArg (args[n], XmNdropTransfers, transferEntries); n++;
            XtSetArg (args[n], XmNnumDropTransfers,
                                    XtNumber (transferEntries)); n++;
            XtSetArg (args[n], XmNtransferProc, TransferProc); n++;
        }
        XmDropTransferStart (dc, args, n);
    }
    else 
        (*drop_proc) (widget, client_data, call_data);
    }

/* TransferProc() -- handle data transfer of converted data from drag
** source to drop site.
*/
void TransferProc (Widget widget, XtPointer client_data, Atom *seltype, 
            Atom *type, XtPointer value, unsigned long *length, int format)
{
    Display     *dpy;
    Atom        FILE_CONTENTS, FILE_NAME;
    Widget      w;
    XmString    string;
    char        label[256];

    /* intern the Atoms for data targets */
    dpy = XtDisplay (toplevel);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    w = (Widget) client_data;

    if (*type == FILE_CONTENTS)
        XmTextSetString (w, value);
    else if (*type == FILE_NAME) {
        sprintf (label, "Filename: %s", value);
        string = XmStringCreateLocalized (label);
        XtVaSetValues (w, XmNlabelString, string, NULL);
        XmStringFree (string);
    }
}
The application basically has the same functionality as editor.c in Chapter 18, Text Widgets. The only difference in the interface is the Filename: status area that displays the name of the current file. This status area is also a drop site for file objects, so the user can drag a file from the file_manager.c application and drop it in this area. When a file is dropped here, the filename is displayed in the status area, and the contents of the file are copied into the ScrolledText object. The ScrolledText object has also been modified to function as a drop site for file data, so the user can drop a file in the text entry area. Figure 22-7 shows the output of the application before and after a file has been dropped in the file status area.

Figure  22-7 The output of the editor_dnd program


Creating a Drop Site

The file status area is a Label widget, so it does not have any drop site capabilities by default. In order for the widget to function as a drop site, we have to register it using XmDropSiteRegister(), as shown below:

n = 0;
importList[0] = FILE_CONTENTS;
importList[1] = FILE_NAME;
XtSetArg (args[n], XmNimportTargets, importList); n++;
XtSetArg (args[n], XmNnumImportTargets, XtNumber (importList)); n++;
XtSetArg (args[n], XmNdropSiteOperations, XmDROP_COPY); n++;
XtSetArg (args[n], XmNdropProc, HandleDropLabel); n++;
XmDropSiteRegister (file_label, args, n);
This routine registers information about the drop site in a DropSite object using resources that are specified as for a normal widget. Since drop sites are referenced by their associated widget, however, the resources cannot be set using XtVaSetValues().

The XmNimportTargets resource specifies the data targets that the drop site can handle. We use the FILE_CONTENTS and FILE_NAME targets that we have interned using XInternAtom()8. The drop site only supports copy operations, so XmNdropSiteOperations is set to XmDROP_COPY. The final resource that we specify is the XmNdropProc. This callback is invoked when a drop occurs in the drop site; it is responsible for starting the transfer of data from the drag source to the drop site. The HandleDropLabel() routine handles the drop for the file status area, as we describe in Handling the Drop Section later in this chapter.


Modifying an Existing Drop Site

The editor_dnd.c application also allows the user to drag a file from file_manager.c to the main text entry area and drop it. This action causes the contents of the file to be copied to the Text widget. By default, the Text widget also has its own drop site functionality that allows the user to drop textual data. We want to modify the drop site to incorporate our own functionality but still allow the user to drag and drop textual data in the widget. The Text widget has already been registered as a drop site by the Motif toolkit, so we do not need to call XmDropSiteRegister(). In fact, if we did call that routine, we would override the default functionality.

Instead, we call XmDropSiteRetrieve() to get the values of the XmNimportTargets, XmNnumImportTargets, and XmNdropProc resources for the Text widget drop site, as shown in the following fragment:

n = 0;
XtSetArg (args[n], XmNimportTargets, &importTargets);       n++;
XtSetArg (args[n], XmNnumImportTargets, &numImportTargets); n++;
XtSetArg (args[n], XmNdropProc, &drop_proc);                n++;
XmDropSiteRetrieve (text_edit, args, n);
Although a drop site is always associated with a widget, the XtVaGetValues() routine cannot be used to retrieve drop site resources, as the resources are stored separately from the widget in a DropSite object. We retrieve the XmNimportTargets resource so that we can add our own targets to the list of data targets for the drop site. A drop site can only have one XmNdropProc associated with it, so we need to get the existing routine and store it before we specify our own routine.

Once we have the data targets for the drop site, we create a new list that contains the existing targets, as well as the FILE_CONTENTS and FILE_NAME targets. We use XmDropSiteUpdate() to modify the drop site:

n = 0;
XtSetArg (args[n], XmNimportTargets, newTargets);              n++;
XtSetArg (args[n], XmNnumImportTargets, numImportTargets + 2); n++;
XtSetArg (args[n], XmNdropProc, HandleDropText);               n++;
XmDropSiteUpdate (text_edit, args, n);
The HandleDropText() routine processes the drops that occur in the Text widget. We explain this routine in detail in the following section.

If you need to update information for a number of drop sites, you should use the XmDropSiteStartUpdate() and XmDropSiteEndUpdate() routines, as they optimize the process. After a call to XmDropSiteStartUpdate(), XmDropSiteUpdate() can be called repeatedly for different drop sites. When you are finished updating all of the drop sites, call XmDropSiteEndUpdate().


Handling the Drop

When a drop occurs, the receiving application takes over and the XmNdropProc for the drop site is called. This callback provides a callback structure of type XmDropProcCallbackStruct, which is defined as follows:

typedef struct {
    int              reason;
    XEvent           *event;
    Time             timeStamp;
    Widget           dragContext;
    Position         x;
    Position         y;
    unsigned char    dropSiteStatus;
    unsigned char    operation;
    unsigned char    operations;
    unsigned char    dropAction;
} XmDropProcCallbackStruct, *XmDropProcCallback;
The reason field is always XmCR_DROP_MESSAGE, and dragContext specifies the DragContext object for the drag operation that caused the drop. The dropSiteStatus element is set to either XmDROP_SITE_VALID or XmDROP_SITE_INVALID, depending on the targets that are supported by the drop site and the drag source. The callback routine can change this value if necessary.

The operations and operation fields are set to the possible operations for the drag source data and the current operation, respectively. The dropAction field specifies the action requested by the user. If this field is set to XmDROP, the user has requested a normal drop; if it is set to XmDROP_HELP, the user has requested help for the drop site. We discuss providing help for a drop site in the next section.

The main task of the XmNdropProc is to determine whether or not the operation is possible and to start the data transfer by calling XmDropTransferStart(). This routine creates a DropTransfer object that keeps track of information about the data transfer. The HandleDropLabel() routine initiates the data transfer for the file status drop site, as shown in the following code fragment from Example 22-3:

n = 0;
if ((!file_name) || (DropData->dropAction != XmDROP) ||
                    (DropData->operation != XmDROP_COPY)) {
    XtSetArg (args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
    XtSetArg (args[n], XmNnumDropTransfers, 0);                n++;
}
else {
    transferEntries[0].target = FILE_CONTENTS;
    transferEntries[0].client_data = (XtPointer) text_edit;
    transferEntries[1].target = FILE_NAME;
    transferEntries[1].client_data = (XtPointer) file_label;
    transferList = transferEntries;
    XtSetArg (args[n], XmNdropTransfers, transferEntries);               n++;
    XtSetArg (args[n], XmNnumDropTransfers, XtNumber (transferEntries)); n++;
    XtSetArg (args[n], XmNtransferProc, TransferProc);                   n++;
}
XmDropTransferStart (dc, args, n);
If the action requested by the user is not a normal drop or if the operation is not a copy operation, we do not process the data transfer. However, we still have to call XmDropTransferStart() to clean up after the whole drag and drop operation. In this case, we set the XmNtransferStatus resource to XmTRANSFER_FAILURE to indicate that the transfer should not proceed. We also set XmNnumDropTransfers to 0.

Otherwise, the drop can proceed, so we establish a list of target data types that we want to receive using the XmNdropTransfers and XmNnumDropTransfer resources. Each entry in XmNdropTransfers is an XmDropTransferEntryRec, which is defined as follows:

typedef struct {
    XtPointer    client_data;
    Atom         target;
} XmDropTransferEntryRec, *XmDropTransferEntry;
The target field specifies the requested data target, and client_data passes any additional data that is necessary to the routine that processes the data transfer. We specify the FILE_CONTENTS and FILE_NAME targets. For each target, we pass the widget that is modified by the data from the drag source as client_data. For the FILE_CONTENTS format, the widget is the text entry area text_edit, while for FILE_NAME, the widget is the file status area file_label.

The final resource that we specify for the DropTransfer is the XmNtransferProc routine. This routine is of type XtSelectionCallbackProc; it is responsible for actually processing the formatted data that is received from the drag source. The routine is called for each target data type requested by the drop site. This routine takes the following form:

typedef void (*XtSelectionCallbackProc)( Widget           widget,
                                         XtPointer        client_data,
                                         Atom             *selection,
                                         Atom             *type,
                                         XtPointer        value,
                                         unsigned long    *length,
                                         int              *format);
The widget parameter is the widget that requested the data, and client_data is the data specified in the client_data field of the XmDropTransferEntryRec that is being processed. The type, value, length, and format arguments contain the data that was converted by the drag source in its XmNconvertProc.

The TransferProc() routine in Example 22-3 checks the type to determine what needs to be done with the data. If the data is FILE_CONTENTS data, the text in value is placed in the Text widget with XmTextSetString(). Otherwise, the text is used to create a new value for XmNlabelString for the file status area. Since the file status area requests both target data types, both formats are processed by TransferProc().

The HandleDropText() routine for the ScrolledText object is very similar to HandleDropLabel(). The main difference is that the routine for the text area checks the XmNexportTargets resource of the DragContext object to determine whether or not the drag source provides file data. If it does, HandleDropText() initiates the data transfer just as in HandleDropLabel(). Otherwise, the text routine calls the XmNdropProc that we retrieved from the Text widget when we modified the drop site. By calling the original drop routine, we allow the Text widget to process textual data as it would by default. As a result, the user can drop a file object in the text entry area, as well as manipulate textual data in the widget using drag and drop.

Once a data transfer is in progress, additional targets for the DropTransfer object can be specified using XmDropTransferAdd(). The primary use of this routine is for move operations. In this case, the drop site receives a copy of the data from the drag source and then requests that the source delete the data. Once the drop site has stored the data, it can call XmDropTransferAdd() to specify the DELETE target, which indicates to the initiating application that it should delete the data.


Providing Help

Since it is not always obvious what will happen when data is dropped on a particular drop site, the user can request help on a drop site by pressing the HELP or F1 key when the drag icon is over the drop site. An application should provide help information for its drop sites to assist users in understanding the drag and drop capabilities of the application. When the user requests help, the drop site should respond by posting an InformationDialog that explains what would happen and allows the user to proceed with the drop or cancel it.

When the user presses HELP while the drag icon is over a drop site, the XmNdropProc for the drop site is called with the dropAction field in the callback structure set to to XmDROP_HELP. Example 22-4 shows a new HandleDropLabel() routine for the editor_dnd.c application that provides help for the file status drop site. The example also shows the HandleDropOK() and HandleDropCancel() callback routines for the help dialog.9

Example  22-4 The HandleDropLabel(), HandleDropOK(), and HandleDropCancel() routines

/* HandleDropLabel() -- start the data transfer when data is dropped in
** the filename status area.
*/
void HandleDropLabel (Widget widget, XtPointer client_data, XtPointer call_
data)
{
    Display                             *dpy;
    Atom                                FILE_CONTENTS, FILE_NAME;
    XmDropProcCallback                  DropData;
    XmDropTransferEntryRec              transferEntries[2];
    XmDropTransferEntry                 transferList;
    Arg                                 args[10];
    int                                 n, i;
    Widget                              dc;
    Cardinal                            numExportTargets;
    Atom                                *exportTargets;
    Boolean                             file_name = False;
    static XmDropProcCallbackStruct     client;
    static Widget                       dialog = NULL;
    XmString                            message;

    void    HandleDropOK(Widget, XtPointer, XtPointer);
    void    HandleDropCancel(Widget, XtPointer, XtPointer);
    void    TransferProc (Widget, XtPointer, Atom *, Atom *, XtPointer,
                            unsigned long *, int);

    /* intern the Atoms for data targets */
    dpy = XtDisplay (toplevel);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);
    DropData = (XmDropProcCallback) call_data;
    dc = DropData->dragContext;
    /* retrieve the data targets and search for FILE_NAME */
    n = 0;
    XtSetArg (args[n], XmNexportTargets, &exportTargets);       n++;
    XtSetArg (args[n], XmNnumExportTargets, &numExportTargets); n++;
    XtGetValues (dc, args, n);
    for (i = 0; i < numExportTargets; i++) {
        if (exportTargets[i] == FILE_NAME) {
            file_name = True;
            break;
        }
    }
    /* if one of the targets is not FILE_NAME, transfer fails */
    if (!file_name) {
        n = 0;
        XtSetArg (args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
        XtSetArg (args[n], XmNnumDropTransfers, 0);                n++;
    }
    /* check if the user has requested help */
    else if (DropData->dropAction == XmDROP_HELP) {
        /* create a dialog if it doesn't already exist */
        if (!dialog) {
            n = 0;
            message = XmStringGenerate ((XtPointer) help_str, 
                            XmFONTLIST_DEFAULT_TAG, XmCHARSET_TEXT, NULL);
            XtSetArg (args[n], XmNdialogStyle, 
                            XmDIALOG_FULL_APPLICATION_MODAL); n++;
            XtSetArg (args[n], XmNtitle, "Drop Help");         n++;
            XtSetArg (args[n], XmNmessageString, message);     n++;
            dialog = XmCreateInformationDialog (toplevel, "help", args, n);
            XmStringFree (message);
            XtUnmanageChild (XtNameToWidget (dialog, "Help"));
            XtAddCallback (dialog, XmNokCallback, HandleDropOK, 
                            (XtPointer) &client);
            XtAddCallback (dialog, XmNcancelCallback, HandleDropCancel, 
                            (XtPointer) &client);
        }
        /* set up the callback structure for when the user proceeds
        ** with the drop and pass it as client data to the callbacks
        ** for the buttons.
        */
        client.dragContext = dc;
        client.x = DropData->x;
        client.y = DropData->y;
        client.dropSiteStatus = DropData->dropSiteStatus;
        client.operation = DropData->operation;
        client.operations = DropData->operations;
        XtManageChild (dialog);
        return;
    }
    else if (DropData->operation != XmDROP_COPY) {
        /* if the operation is not a copy, the transfer fails */
        n = 0;
        XtSetArg (args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
        XtSetArg (args[n], XmNnumDropTransfers, 0);                n++;
    }
    else {
        /* set up transfer requests since this is a normal drop */
        n = 0;
        transferEntries[0].target = FILE_CONTENTS;
        transferEntries[0].client_data = (XtPointer) text_edit;
        transferEntries[1].target = FILE_NAME;
        transferEntries[1].client_data = (XtPointer) file_label;
        transferList = transferEntries;
        XtSetArg (args[n], XmNdropTransfers, transferEntries); n++;
        XtSetArg (args[n], XmNnumDropTransfers,
                            XtNumber (transferEntries));      n++;
        XtSetArg (args[n], XmNtransferProc, TransferProc);     n++;
    }
    XmDropTransferStart (dc, args, n);
}

/* HandleDropOK() -- callback routine for OK button in drop site help
** dialog that processes the drop as normal.
*/
void HandleDropOK (Widget widget, XtPointer client_data, XtPointer call_data)
{
    Display                      *dpy;
    Atom                         FILE_CONTENTS, FILE_NAME;
    XmDropProcCallbackStruct     *DropData;
    XmDropTransferEntryRec       transferEntries[2];
    XmDropTransferEntry          transferList;
    Arg                          args[10];
    int                          n;
    Widget                       dc;
    void                         TransferProc (Widget, XtPointer, Atom *, 
                                                Atom *, XtPointer, 
                                                unsigned long *, int);

    /* intern the Atoms for data targets */
    dpy = XtDisplay (toplevel);
    FILE_CONTENTS = XInternAtom (dpy, "FILE_CONTENTS", False);
    FILE_NAME = XInternAtom (dpy, "FILE_NAME", False);

    /* get the callback structure passed via client data */
    DropData = (XmDropProcCallbackStruct *) client_data;
    dc = DropData->dragContext;

    n = 0;
    /* if operation is not a copy, the transfer fails */
    if (DropData->operation != XmDROP_COPY) {
        XtSetArg (args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
        XtSetArg (args[n], XmNnumDropTransfers, 0);                n++;
    }
    else {
        /* set up transfer requests to process data transfer */
        transferEntries[0].target = FILE_CONTENTS;
        transferEntries[0].client_data = (XtPointer) text_edit;
        transferEntries[1].target = FILE_NAME;
        transferEntries[1].client_data = (XtPointer) file_label;
        transferList = transferEntries;
        XtSetArg (args[n], XmNdropTransfers, transferEntries); n++;
        XtSetArg (args[n], XmNnumDropTransfers,
                            XtNumber (transferEntries));      n++;
        XtSetArg (args[n], XmNtransferProc, TransferProc);     n++;
    }
    XmDropTransferStart (dc, args, n);
}
/* HandleDropCancel() -- callback routine for Cancel button in drop site
** help dialog that cancels the transfer.
*/
void HandleDropCancel (Widget widget, XtPointer client_data, 
                        XtPointer call_data)
{
    XmDropProcCallbackStruct     *DropData;
    Arg                          args[10];
    int                          n;
    Widget                       dc;

    /* get the callback structures passed via client data */
    DropData = (XmDropProcCallbackStruct *) client_data;
    dc = DropData->dragContext;
    /* user has cancelled the transfer, so it fails */
    n = 0;
    XtSetArg (args[n], XmNtransferStatus, XmTRANSFER_FAILURE); n++;
    XtSetArg (args[n], XmNnumDropTransfers, 0);                n++;
    XmDropTransferStart (dc, args, n);
}
When the user requests help on the file status drop site, the application displays a help dialog, as shown in Figure 22-8.

Figure  22-8 The drag and drop help dialog

The new HandleDropLabel() routine handles the case when the dropAction field is set to XmDROP_HELP. In this case, the routine creates an InformationDialog if it has not already been created. The HandleDropOK() and HandleDropCancel() routines are registered for the OK and Cancel buttons in the dialog. If the dialog already exists, the necessary fields in the client structure are specified so that the callback structure information is passed to the callback routines as client data. If the user has performed a normal drop operation, the drop proceeds just as it did in editor_dnd.c.

The HandleDropOK() routine is invoked when the user presses the OK button in the help dialog. This routine proceeds with the drop by calling XmDropTransferStart(). The status of the transfer is based on whether the drop performs a copy operation or not. HandleDropCancel() cancels the drop when the user presses the Cancel button by calling XmDropTransferStart() with XmNtransferStatus set to XmTRANSFER_FAILURE. One thing to note about both of these procedures is that they get the XmDropProcCallbackStruct from the client_data parameter, since the call_data parameter is the callback structure for the dialog.


Providing Custom Drag-under Visuals

Under the preregister protocol, the drop site does not participate during the drag. The initiating application handles the drag-under visual effects based on the value of the XmNanimationStyle resource for the drop site. This resource can have one of the following values:

XmDRAG_UNDER_HIGHLIGHT       XmDRAG_UNDER_SHADOW_OUT
XmDRAG_UNDER_SHADOW_IN       XmDRAG_UNDER_PIXMAP
XmDRAG_UNDER_NONE
The default value is XmDRAG_UNDER_HIGHLIGHT, which means that a highlighting rectangle is displayed around the drop site when the drag icon enters it. The drop site can also be displayed with an inset or outset shadow using XmDRAG_UNDER_SHADOW_OUT and XmDRAG_UNDER_SHADOW_IN, respectively. The XmDRAG_UNDER_PIXMAP value specifies that a special pixmap is displayed in the drop site when the drag icon is in it; the XmNanimationPixmap and XmNanimationMask resources indicate the pixmap that is used. If XmNanimationStyle is set to XmDRAG_UNDER_NONE, there are no animation effects unless they are provided by the XmNdragProc.

Under the dynamic protocol, the drop site can participate in the drag by specifying an XmNdragProc. This callback routine is invoked when the drag icon enters or leaves the drop site, when the drag icon moves within the drop site, and when the operation changes while the icon is in the drop site. The callback receives a callback structure of the type XmDragProcCallbackStruct, which is defined as follows:

typedef struct {
    int                reason;
    XEvent             *event;
    Time               timeStamp;
    Widget             dragContext;
    Position           x;
    Position           y;
    unsigned char      dropSiteStatus;
    unsigned char      operation;
    unsigned char      operations;
    Boolean            animate;
} XmDragProcCallbackStruct, *XmDragProcCallback;
The reason field is set to one of the following, depending upon the event that triggered the callback:

XmCR_DROP_SITE_ENTER_MESSAGE      XmCR_DROP_SITE_LEAVE_MESSAGE
XmCR_DRAG_MOTION_MESSAGE          XmCR_OPERATION_CHANGED_MESSAGE
The dragContext field specifies the current DragContext object, while dropSiteStatus is set to either XmDROP_SITE_VALID or XmDROP_SITE_INVALID, based on the values of XmNimportTargets and XmNexportTargets for the drop site and the drag source, respectively. The operations and operation fields are set to the possible operations for the drag source data and the current operation, respectively. The value of operations is based on the value of the XmNdragOperations resource for the DragContext, while the value of operation is based on operations and the value of XmNdropSiteOperations.

The XmNdragProc can change the values of these three fields based on any special processing it performs, such as handling simulated drop sites. When the routine is done, the toolkit uses these values of the fields to initialize the fields in the callback structure that is passed to the corresponding DragContext callback routine in the initiating application.

The animate field specifies whether the toolkit or the receiving client is handling drag-under effects for the drop site. If the value is True, as it is by default, the toolkit handles the effects based on the XmNanimationStyle resource. The receiving client can set the field to False so that it is responsible for providing drag-under effects. The main use of the XmNdragProc is for providing specialized drag-under effects, such as actual animation, that the toolkit itself does not support.

Summary

The drag and drop capabilities provided by Motif are highly customizable, so an application can use the toolkit to implement whatever functionality is necessary. The examples in this chapter have demonstrated many of the techniques that an application needs to use to provide drag and drop functionality, but they really just scratch the surface of what is possible.

Our examples implement the drag and drop features directly in application code because that is sufficient for our purposes. However, if you are developing real applications, you should think seriously about encapsulating drag and drop functionality in widgets, so that you can reuse the components in all of the applications.

In Chapter 23, The Uniform Transfer Model, we introduce the schemes introduced in Motif 2.0 which build on top of the mechanisms described in this chapter and Chapter 21, The Clipboard. These schemes allow the programmer to transfer data between widgets or the clipboard, either through the primary selection mechanisms or drag and drop, using a single programming interface.



1 Under CDE Motif 2.0 or later, dragging from a Scale, Label, or LabelGadget may be turned off if the XmDisplay resource XmNenableUnselectableDrag is False.

2 In Motif 2.0 and later, drag and drop for the Scale, Label and LabelGadget are by default turned off if the XmDisplay resource XmNenableUnselectableDrag is True.

3 XtAppInitialize() is considered deprecated in X11R6. XmInternAtom() is marked for deprecation in Motif 2.0 and later.

4 We use popen() here, but you should use opendir() and readdir().

5 As of Motif 2.0, XmInternAtom() is marked for deprecation.

6 XmInternAtom() is marked for deprecation from Motif 2.0.

7 XtVaAppInitialize() is considered deprecated in X11R6. XmInternAtom() is marked for deprecation from Motif 2.0.

8 XmInternAtom() is marked for deprecation as of Motif 2.0.

9 XmStringCreateLtoR(), XmMessageBoxGetChild() are deprecated from Motif 2.0 onwards. XmStringGenerate () is only available from Motif 2.0 onwards. XmInternAtom() is marked for deprecation from Motif 2.0 onwards.






X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation

Previous Next Contents Document Index Motif Docs motifdeveloper.com Imperial Software Technology X-Designer




Copyright © 1991, 1994, 2000, 2001, 2002 O'Reilly & Associates, Inc., Antony Fountain and Jeremy Huxtable. All Rights Reserved.