Contents Previous Next

19 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.

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. The ability to transfer data using drag and drop is new in Motif 1.2.

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.

19.1 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 drop site. The drop occurs when the user releases the mouse button. the figure shows the conceptual model of drag and drop.

figs.eps/V6a.18.01.eps.png
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 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.

19.2 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 1.2, 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 data. 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 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.

linesize(2), tab(@); l | l | l lp9 | lp9 | l. 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 COLORMAP@COLORMAP@Colormap ID ODIF@TEXT@ISO Office Document Interchange Format OWNER_OS@TEXT@Operating system of owner FILE_NAME@TEXT@Full path name of a file HOST_NAME@TEXT@Hostname of machine of owner CHARACTER_POSITION@SPAN@Start and end of selection in bytes LINE_NUMBER@SPAN@Start and end line numbers COLUMN_NUMBER@SPAN@Start and end column numbers LENGTH@INTEGER@Number of bytes in selection USER@TEXT@Name of user running owner PROCEDURE@TEXT@Name of selected procedure MODULE@TEXT@Name of selected module PROCESS@INTEGER, TEXT@Process ID of owner TASK@INTEGER, TEXT@Task ID of owner CLASS@TEXT@Class of owner (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 new 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 SixB, Motif Reference Manual.

19.2.1 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 needs to call XtParseTranslationTable() and XtAppAddActions(). The parsed translation table can be used to set the XmN­translations 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 XmN­dragOperations resource specifies the operations supported by the drag source, while XmN­exportTargets 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 XmN­sourceCursorIcon, XmN­operationCursorIcon, and XmN­stateCursorIcon 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 in Section #sdragicon.

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. Section #sdragcallbk provides more information about these callbacks.

The XmN­convertProc 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 procedure is either an XtConvertSelectionProc or an XtConvertSelectionIncrProc, depending on whether or not the drag source is using incremental transfer. If the XmN­incremental 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 Four, 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();
   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 Section #sdragsource, 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 XmN­convertProc 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.

19.2.2 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 XmN­importTargets and XmN­numImportTargets re­sources specify this information, while the XmN­dropSiteOperations 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 XmN­animationStyle 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 XmN­dragProc. 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 XmN­dragProc is optional, every drop site must have a XmN­dropProc 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();

   ...
   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 XmN­dropProc 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 XmN­transferStatus 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 XmN­transferStatus 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 XmN­dropTransfers and XmN­numDropTransfers resources specify the data targets to be processed, while XmN­transferProc indicates the procedure that receives the converted data from the drag source. This procedure is of type XtSelectionCallbackProc. Once the data transfer has started, XmDropTransferAdd() can be used to request the processing of additional data targets. In Section #sdropsite, 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 XmN­dropRectangles and XmN­numDropRectangles 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 XmN­dropSiteType 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 XmDropSiteQueryStackingOrder() and XmDropSiteConfigureStackingOrder().

19.2.3 The Drag Icon

During a drag, the pointer shape is changed into a drag icon 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. the figure shows the components of a drag icon.

figs.eps/V6a.18.02.eps.png
A drag icon


The Motif toolkit provides default icons for all of the different drag icon components. The default source icons for textual data and for generic data are shown in the figure.

figs.eps/V6a.18.03.eps.png
Default source icons

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

figs.eps/V6a.18.04.eps.png
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 XmN­blendModel 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 XmN­pixmap and XmN­mask resources represent the actual pixmap and its mask if you use one. Other resources include XmN­height, XmN­width, and XmN­depth for specifying those attributes of the image, as well as XmN­hotX and XmN­hotY for indicating the x,y coordinate of the hotspot for the icon. The XmN­attachment, XmN­offsetX , and XmN­offsetY 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 Section #smodicon. 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.

19.2.4 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. 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 XmN­dragProc. 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 XmN­dragProc. 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 XmN­dragInitiatorProtocolStyle and XmN­dragReceiverProtocolStyle. 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 Section #sdragprot, when we discuss how to customize drag and drop. 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 XmN­transferProc.

The DragContext and DropTransfer objects both have XmN­incremental 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.

19.2.5 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. the figure 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. 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.

figs.eps/V6a.18.05.eps.png
Drag and drop programming model

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 XmN­dropProc that takes over when a drop occurs in the drop site. The receiver can also specify an XmN­dragProc 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 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 XmN­convertProc 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. 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 for a 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 XmN­dragProc, 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 XmN­dragProc 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-over visuals. When the user drops the data, the toolkit on the receiving side takes over from the toolkit on the initiating side. The XmN­dropProc 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 XmN­dropProc 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 XmN­dropStartCallback on its DragContext, it is invoked now. Other than this callback, the initiating client plays no role when the drop occurs. 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 XmN­transferProc 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 XmN­convertProc of the initiator is called to convert the data to the specified format. The formatted data is passed back to the receiver's XmN­transferProc. Once the entire transfer is complete, the XmN­dropFinishCallback and XmN­dragDropFinishCallback callbacks of the initiating client's DragContext are invoked, if they have been specified.

19.3 Customizing Built-in Drag and Drop

In Motif 1.2, the Text and TextField widgets, the List widget, and the Label widget and its subclasses all support drag and drop functionality by default. 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 Section #sdropsite.

The user can drag the text of either a single item or the current selection in a List widget. 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.

The Text and TextField widgets are the only Motif widgets that 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.

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.

19.3.1 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 XmN­dragInitiatorProtocolStyle and XmN­dragReceiverProtocolStyle 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 XmN­dragInitiatorProtocolStyle and XmN­dragReceiverProtocolStyle 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 XmN­dragReceiverProtocolStyle.
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 XmN­dragInitiatorProtocolStyle 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. shows how the protocol is resolved based on the preferred protocols for the initiator and the receiver. tab(@); l | c s s s ^ | l | l | l |l l | lp9 | lp9 | lp9 | lp9. T{
Initiator
Protocol Style
T}@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 XmN­dragInitiatorProtocolStyle or XmN­dragReceiverProtocolStyle 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 XmN­dragProc 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 XmN­dragInitiatorProtocolStyle and XmN­dragReceiverProtocolStyle 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 XmN­dragInitiatorProtocolStyle is set to XmDRAG_PREFER_RECEIVER and XmN­dragReceiverProtocolStyle is set to XmDRAG_PREFER_PREREGISTER. If you have implemented custom drag-under visuals with an XmN­dragProc, 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.

19.3.2 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 XmN­dragInitiatorProtocolStyle and XmN­dragReceiverProtocolStyle 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 XmN­translations 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 XmN­dropSiteActivity 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 XmN­editable 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 XmN­dropSiteActivity.

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 XmN­dropSiteActivity resource to match the sensitivity.

19.3.3 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 XmN­sourceCursorIcon, XmN­operationCursorIcon, and XmN­stateCursorIcon resources have not been specified for a particular DragContext. All Motif widgets with built-in drag source functionality set the XmN­sourceCursorIcon resource, so the Screen resource cannot be used to specify a different source icon for these components. The widgets do not set the XmN­operationCursorIcon and XmN­stateCursorIcon 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 the figure 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 XmN­defaultSourceCursorIcon 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 Section #screateicon 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 XmN­validCursorForeground, XmN­invalidCursorForeground, and XmN­noneCursorForeground resources 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 Section #sdragcallbk we discuss how to provide custom drag-over effects.

19.4 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 Section #sdropsite, 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. the source code shows the file_manager.c application, which we are going to describe in detail in the following sections. This chapter describes functionality that is new in Motif 1.2, so this example only works with the 1.2 version of the Motif toolkit.

   /* 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();

   /* 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 (argc, argv)
   int argc;
   char *argv[];
   {
       Arg                     args[10];
       int                     num_files, n, i = 0;
       Widget                  toplevel, sw, panel, form;
       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;
       Pixel                   fg, bg;

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtAppInitialize (&app, "Demos", NULL, 0, &argc, argv,
           NULL, NULL, 0);

       /* intern the Atoms for data targets */
       dpy = XtDisplay (toplevel);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (dpy, "FILE_NAME", False);
       DIRECTORY = XmInternAtom (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, '0))
               *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 */
       sw = XtVaCreateManagedWidget ("sw",
           xmScrolledWindowWidgetClass, toplevel,
           XmNwidth, 200,
           XmNheight, 300,
           XmNscrollingPolicy, XmAUTOMATIC,
           NULL);

       panel = XtVaCreateWidget ("panel", xmRowColumnWidgetClass, sw, NULL);

       /* 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 = XtVaCreateWidget ("form", xmFormWidgetClass, panel, NULL);
           XtVaCreateManagedWidget ("type", xmLabelWidgetClass, form,
               /* specify translation for drag and index into file array */
               XmNtranslations, parsed_trans,
               XmNuserData, i,
               XmNlabelType, XmPIXMAP,
               XmNlabelPixmap, files[i].is_directory ? dir : file,
               XmNtopAttachment, XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               XmNleftAttachment, XmATTACH_FORM,
               XmNrightAttachment, XmATTACH_POSITION,
               XmNrightPosition, 25,
               NULL);
           XtVaCreateManagedWidget (files[i].file_name,
               xmLabelWidgetClass, form,
               XmNalignment, XmALIGNMENT_BEGINNING,
               XmNtopAttachment, XmATTACH_FORM,
               XmNbottomAttachment, XmATTACH_FORM,
               XmNrightAttachment, XmATTACH_FORM,
               XmNleftAttachment, XmATTACH_POSITION,
               XmNleftPosition, 25,
               NULL);
           XtManageChild (form);
       }

       XtManageChild (panel);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* StartDrag() -- action routine called by the 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, event, params, num_params)
   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();
       void            DragDropFinish();

       /* intern the Atoms for data targets */
       dpy = XtDisplay (widget);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (dpy, "FILE_NAME", False);
       DIRECTORY = XmInternAtom (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_JUST_SOURCE); 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, selection, target, type_return, value_return,
           length_return, format_return)
   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 = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (dpy, "FILE_NAME", False);
       MOTIF_DROP = XmInternAtom (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, client_data, call_data)
   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 the figure.

figs.eps/V6a.18.06.eps.png
Output of file_manager.c


The application gets the names of all of the files in the current directory We use popen() here, but you should use opendir() and readdir(). and 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.

19.4.1 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 XmN­userData 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 XmN­labelPixmap 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 Section #spixmaps.

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 XmN­translations 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 XmInternAtom(), as shown in the following code fragment:

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

   dpy = XtDisplay (toplevel);
   FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
   FILE_NAME = XmInternAtom (dpy, "FILE_NAME", False);
   DIRECTORY = XmInternAtom (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.

19.4.2 Starting the Drag

When the user starts a drag, the DragStart() action routine is called. This routine creates a custom drag icon 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 XmN­userData 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 the source code

   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 XmN­pixmap and XmN­mask 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 XmN­mask resource must be set to a pixmap of depth 1, while the XmN­pixmap 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_JUST_SOURCE); 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 XmN­sourceCursorIcon specifies the source drag icon that we just created. We also specify the background and foreground colors for the icon. The DragContext also has XmN­operationCursorIcon and XmN­stateCursorIcon resources for specifying the operation and state icons, but our drag icon does not use these parts, so we don't set the resources.

The XmN­blendModel 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_JUST_SOURCE since we only want the source icon to be used for the drag icon. XmBLEND_NONE means that the DragContext does not generate a drag icon.

Another important set of resources are the XmN­exportTargets and XmN­numExportTargets resources. These resources specify the data targets to which the drag source can convert the actual data. The XmN­exportTargets 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 XmN­dragOperations resource specifies all of the operations that are supported by the application. This value is specified as a bitmask 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 XmN­convertProc. 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 XmN­clientData 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 XmN­dragDropFinishCallback so that we can destroy the DragIcon that we created. This routine is discussed further in Section #sdragclean.

19.4.3 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 XmN­convertProc 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, Atom *, Atom *,
            Atom *, XtPointer *, unsigned long *, int *);
       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 Four, X Toolkit Intrinsics Programming Manual, and the appropriate reference page in Volume Five, X Toolkit Intrinsics Reference Manual.

The ConvertProc() routine in the source code starts by retrieving the Label widget from the XmN­clientData 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 XmN­convertProc 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.

19.4.4 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 drop site. 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 XmN­translations resource, as shown in the following code fragment:
   parsed_trans_text = XtParseTranslationTable (newdragTranslations);
   ...
   XtVaCreateManagedWidget (files[i].file_name,
       xmLabelWidgetClass, form,
       XmNtranslations, parsed_trans_text,
       XmNuserData, i,
       ...
       NULL);
Note that we also specify the index in the files array as the XmN­userData for these widgets, just as we did for the image Labels in the source code

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 the source code

   void      (*convert_proc) ();

   void
   UpdateDrag(widget, event, params, num_params)
   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();
       void            DragDropFinish();
       Cardinal        numExportTargets;
       Atom           *exportTargets, *newTargets;

       /* intern the Atoms for data targets */
       dpy = XtDisplay (widget);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (dpy, "FILE_NAME", False);
       DIRECTORY = XmInternAtom (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 retrive 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_JUST_SOURCE); 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 XmStartDrag().

The routine retrieves the values of the XmN­exportTargets, XmN­numExportTargets, and XmN­convertProc 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 XmN­exportTargets is set to the new list. The NewConvertProc() routine is used for the XmN­convertProc. 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.

19.4.5 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 XmN­dropSiteEnterCallback 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 XmN­dragProc 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 Six 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 XmN­sourcePixmapIcon, XmN­sourceCursorIcon, XmN­operationCursorIcon, and XmN­stateCursorIcon resources specify the different components of the drag icon. The XmN­validCursorForeground, XmN­invalidCursorForeground, and XmN­noneCursorForeground resources of the DragContext can be used to further distinguish between the different states during a drag.

The XmN­sourcePixmapIcon is used under the preregister protocol and can be any size, while the XmN­sourceCursorIcon 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 XmN­sourcePixmapIcon resource. If XmN­sourcePixmapIcon is not specified, the value of XmN­sourceCursorIcon 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 XmN­dropProc completes, the DragContext's XmN­dropStartCallback 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.

19.4.6 Cleaning Up

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

The XmN­dragDropFinishCallback is invoked when the entire operation has completed, which is immediately after the XmN­dropFinishCallback. 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, client_data, call_data)
   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().

19.5 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 14, 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 source code 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 Section #stexteditor, so we have not shown them here.

   /* 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          text_edit, search_text, replace_text, text_output;
   Widget          toplevel, file_label;

   main(argc, argv)
   int argc;
   char *argv[];
   {
       XtAppContext  app_context;
       Display      *dpy;
       Atom          FILE_CONTENTS, FILE_NAME;
       Widget        main_window, menubar, form, search_panel;
       Widget        sep1, sep2;
       void          file_cb(), edit_cb(), search_cb();
       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(), HandleDropText();

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app_context, "Demos",
           NULL, 0, &argc, argv, NULL, NULL);

       dpy = XtDisplay (toplevel);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (dpy, "FILE_NAME", False);

       main_window = XtVaCreateWidget ("main_window",
           xmMainWindowWidgetClass, toplevel, NULL);

       /* 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 = XtVaCreateWidget ("form",
           xmFormWidgetClass, main_window, NULL);

       /* create horizontal RowColumn inside the form */
       search_panel = XtVaCreateWidget ("search_panel",
           xmRowColumnWidgetClass, form,
           XmNorientation,     XmHORIZONTAL,
           XmNpacking,         XmPACK_TIGHT,
           XmNtopAttachment,   XmATTACH_FORM,
           XmNleftAttachment,  XmATTACH_FORM,
           XmNrightAttachment, XmATTACH_FORM,
           NULL);

       /* Create two TextField widgets with Labels... */
       XtVaCreateManagedWidget ("Search Pattern:",
           xmLabelGadgetClass, search_panel, NULL);
       search_text = XtVaCreateManagedWidget ("search_text",
           xmTextFieldWidgetClass, search_panel, NULL);
       XtVaCreateManagedWidget ("     Replace Pattern:",
           xmLabelGadgetClass, search_panel, NULL);
       replace_text = XtVaCreateManagedWidget ("replace_text",
           xmTextFieldWidgetClass, search_panel, NULL);
       XtManageChild (search_panel);

       text_output = XtVaCreateManagedWidget ("text_output",
           xmTextFieldWidgetClass, form,
           XmNeditable,              False,
           XmNcursorPositionVisible, False,
           XmNshadowThickness,       0,
           XmNleftAttachment,        XmATTACH_FORM,
           XmNrightAttachment,       XmATTACH_FORM,
           XmNbottomAttachment,      XmATTACH_FORM,
           NULL);

       sep2 = XtVaCreateManagedWidget ("sep2",
           xmSeparatorGadgetClass, form,
           XmNleftAttachment,        XmATTACH_FORM,
           XmNrightAttachment,       XmATTACH_FORM,
           XmNbottomAttachment,      XmATTACH_WIDGET,
           XmNbottomWidget,          text_output,
           NULL);

       /* create file status area */
       file_label = XtVaCreateManagedWidget ("Filename:",
           xmLabelGadgetClass, form,
           XmNalignment,             XmALIGNMENT_BEGINNING,
           XmNleftAttachment,        XmATTACH_FORM,
           XmNrightAttachment,       XmATTACH_FORM,
           XmNbottomAttachment,      XmATTACH_WIDGET,
           XmNbottomWidget,          sep2,
           NULL);

       /* 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);

       sep1 = XtVaCreateManagedWidget ("sep1",
           xmSeparatorGadgetClass, form,
           XmNleftAttachment,        XmATTACH_FORM,
           XmNrightAttachment,       XmATTACH_FORM,
           XmNbottomAttachment,      XmATTACH_WIDGET,
           XmNbottomWidget,          file_label,
           NULL);

       /* 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, client_data, call_data)
   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();

       /* intern the Atoms for data targets */
       dpy = XtDisplay (toplevel);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (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, client_data, call_data)
   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();

       /* intern the Atoms for data targets */
       dpy = XtDisplay (toplevel);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (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, client_data, seltype, type, value, length, format)
   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 = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (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 14. 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. the figure shows the output of the application before and after a file has been dropped in the file status area.

19.5.1 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 XmN­importTargets 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 XmInternAtom(). The drop site only supports copy operations, so XmN­dropSiteOperations is set to XmDROP_COPY. The final resource that we specify is the XmN­dropProc. 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 Section #shandledrop.

figs.eps/V6a.18.07.eps.png
Output of editor_dnd.c


19.5.2 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 XmN­importTargets, XmN­numImportTargets, and XmN­dropProc 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 XmN­importTargets 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 XmN­dropProc 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(), you can call XmDropSiteUpdate() repeatedly for different drop sites. When you are finished updating all of the drop sites, call XmDropSiteEndUpdate().

19.5.3 Handling the Drop

When a drop occurs, the receiving application takes over and the XmN­dropProc 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 XmN­dropProc 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 the source code

   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 XmN­transferStatus resource to XmTRANSFER_FAILURE to indicate that the transfer should not proceed. We also set XmN­numDropTransfers to 0.

Otherwise, the drop can proceed, so we establish a list of target data types that we want to receive using the XmN­dropTransfers and XmN­numDropTransfer resources. Each entry in XmN­dropTransfers 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 XmN­transferProc 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, XtPointer, Atom *,
           Atom *, XtPointer, unsigned long*, int *);
       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 XmN­convertProc.

The TransferProc() routine in the source code 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 XmN­labelString 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 XmN­exportTargets 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 XmN­dropProc 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.

19.5.4 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 XmN­dropProc for the drop site is called with the dropAction field in the callback structure set to to XmDROP_HELP. the source code 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.

   /* HandleDropLabel() -- start the data transfer when data is dropped in
    * the filename status area.
    */
   void
   HandleDropLabel(widget, client_data, call_data)
   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(), HandleDropCancel();
       void                    TransferProc();

       /* intern the Atoms for data targets */
       dpy = XtDisplay (toplevel);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (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 = XmStringCreateLtoR (help_str, XmFONTLIST_DEFAULT_TAG);
               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 (XmMessageBoxGetChild
                   (dialog, XmDIALOG_HELP_BUTTON));

               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, client_data, call_data)
   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();

       /* intern the Atoms for data targets */
       dpy = XtDisplay (toplevel);
       FILE_CONTENTS = XmInternAtom (dpy, "FILE_CONTENTS", False);
       FILE_NAME = XmInternAtom (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, client_data, call_data)
   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 canceled 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 the figure.

figs.eps/V6a.18.08.eps.png
A 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 XmN­transferStatus set to XmTRANS­FER_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.

19.5.5 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 XmN­animationStyle 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 XmN­animationPixmap and XmN­animationMask resources indicate the pixmap that is used. If XmN­animationStyle is set to XmDRAG_UNDER_NONE, there are no animation effects unless they are provided by the XmN­dragProc .

Under the dynamic protocol, the drop site can participate in the drag by specifying an XmN­dragProc. 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 XmCR_DROP_SITE_ENTER_MESSAGE , XmCR_DROP_ SITE_LEAVE_MESSAGE, XmCR_DRAG_MOTION_MESSAGE , or XmCR_OPERATION_ CHANGED_MESSAGE, depending on the event that triggered the callback.

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 XmN­importTargets and XmN­exportTargets 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, repectively. The value of operations is based on the value of the XmN­dragOperations resource for the DragContext, while the value of operation is based on operations and the value of XmN­dropSiteOperations.

The XmN­dragProc 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 XmN­animationStyle 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.

19.6 Summary

The drag and drop capabilities provided by Motif 1.2 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.


Contents Previous Next