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.
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.
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.
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.
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 XmNtranslations resource for the drag source widget.
The Motif toolkit uses the DragContext object to
store information about a drag source once a drag has started. This
object also keeps track of state information about the transfer as it
is happening. The routine that starts a drag calls XmDragStart()
to create the DragContext and get things rolling. The DragContext
object has resources that need to be set at creation time to provide
information about the drag source. The XmNdragOperations
resource specifies the operations supported by the drag source, while
XmNexportTargets and XmNnumExportTargets indicate the
data targets that are supported.
The DragContext also has a number of resources that
control the visual effects used during the drag. Many of these
resources specify various attributes of the drag icon for the transfer.
For example, the XmNsourceCursorIcon,
XmNoperationCursorIcon, and XmNstateCursorIcon resources
indicate the images that are used for different parts of the drag icon.
If these resources are not specified, the DragContext uses default
icons. There are also resources that allow you to specify different
foreground and background colors for the drag icon. We describe the
drag icon in more detail 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 XmNconvertProc is a procedure that
must be specified when a DragContext is created. This procedure is used
to convert the drag source data to the format requested by the drop
site when the drop occurs. The procedure is either an
XtConvertSelectionProc or an XtConvertSelectionIncrProc,
depending on whether or not the drag source is using incremental
transfer. If the XmNincremental resource is set to True
, the data is transferred incrementally. Both of these procedures are
part of the underlying Xt selection mechanism that is not completely
hidden by the Motif drag and drop abstraction. See Volume 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 XmNconvertProc to be called for each data target
that the drop site has requested. This routine converts the data into
the requested format and passes it back to the drop site, so the drop
site can do whatever it needs with the data.
Once the user starts a drag and drop transfer, the
data can be dropped in any location that has been registered as a drop
site, and if the drop site understands the data, the transfer will
succeed. The application that contains the drop site where data is
dropped is the receiving client in a drag and drop transfer. A drop
site is always associated with a widget. Like a drag source, a drop
site supports particular types of data, depending on the type of object
it is.
A drop site can be designed to handle any type of
data, or even multiple types if appropriate. A drop site also specifies
the operations that it supports. The standard operations are to move,
copy, and link data. However, a drop site can instead invoke an action
as the result of a drop. For example, a "send message" drop site could
send an electronic mail message when text is dropped in it. The type of
object that functions as the drop site also has an effect on the
supported operations. In most cases, it only makes sense for writable
components to act as drop sites, since read-only components like Lists
and Labels cannot be modified by the user.
Motif stores information about all of the drop sites
in an application using DropSite objects. An application registers a
widget as a drop site by calling XmDropSiteRegister() for the
widget. The DropSite object uses resources to keep track of information
about the drop site. This information can be set when the drop site is
registered, or it can be specified later using XmDropSiteUpdate()
; the values of the resources can be retrieved using
XmDropSiteRetrieve(). Since a widget is being used as the handle to
the drop site, you cannot use XtVaSetValues() and
XtVaGetValues() to set and retrieve drop site information, as these
routines manipulate the widget's resources.
Just as a drag source specifies the data types that
it can process, a drop site also needs to provide this information.
The XmNimportTargets and XmNnumImportTargets
resources specify this information, while the
XmNdropSiteOperations resource specifies the operations supported
by the drop site.
A drop site provides visual effects when the drag
icon passes through it; these effects are called drag-under effects. By
default, the widget is highlighted. Other simple effects, such as a
shadow border or a special pixmap, can be specified using the
XmNanimationStyle resource. All of these effects are handled
automatically by the toolkit on the initiating side once the resource
is set. For more sophisticated effects, such as animation, a drop site
must register an XmNdragProc. This callback is invoked
whenever there is any drag activity in the drop site, so the
application can do whatever it likes in terms of drag-under effects.
While the XmNdragProc is optional, every
drop site must have a XmNdropProc registered. This routine is
called when a drop occurs in the drop site. The procedure is
responsible for determining whether the drop is successful or not,
based on the targets and operations supported by the drag source and
the drop site. The following code fragment shows how a widget that can
handle compound text is registered as a drop site:
Arg args[10]; int n; Widget label; Atom importList[1]; void HandleDrop(); ... n = 0; importList[0] = COMPOUND_TEXT; XtSetArg (args[n], XmNimportTargets, importList); n++; XtSetArg (args[n], XmNnumImportTargets, XtNumber (importList)); n++; XtSetArg (args[n], XmNdropSiteOperations, XmDROP_COPY); n++; XtSetArg (args[n], XmNdropProc, HandleDrop); n++; XmDropSiteRegister (label, args, n);When a drop occurs in the drop site, the XmNdropProc is called automatically by the Motif toolkit. This routine must call XmDropTransferStart() whether or not the drop is successful. XmDropTransferStart() creates a DropTransfer object that maintains information about the data transfer. When The DropTransfer object is created, the XmNtransferStatus resource must be set to indicate the success or failure of the drop. If the resource is set to XmTRANSFER_FAILURE, XmDropTransferStart() does not transfer any data and merely cleans up after the drag and drop transfer.
If XmNtransferStatus is set to
XmTRANSFER_SUCCESS when the DropTransfer object is created, some
other resources must also be specified to cause the data to be
transferred. The XmNdropTransfers and
XmNnumDropTransfers resources specify the data targets to be
processed, while XmNtransferProc indicates the procedure that
receives the converted data from the drag source. This 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 XmNdropRectangles and XmNnumDropRectangles
resources control this feature. Drop sites can also be nested, so that
a manager widget can be a drop site and can also contain children that
are drop sites. The XmNdropSiteType resource controls whether
the drop site is a simple drop site or a compound drop site. Drop sites
have a stacking order, which means that they can overlap. When drop
sites overlap, the drop site on the top of the stack obscures the drop
sites beneath it, as you would expect. An application can control the
stacking order of drop sites using XmDropSiteQueryStackingOrder()
and XmDropSiteConfigureStackingOrder().
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.
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.
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.
Motif uses DragIcon objects to represent the parts
of a drag icon. In order to use a custom image, you need to create each
part of the icon using XmCreateDragIcon(). The
XmNblendModel resource of the DragContext for a drag and drop
transfer specifies the different pieces that are blended together to
create the actual drag icon.
A drag icon is essentially a pixmap, and the
DragIcon object encapsulates all of the information about the image.
When you create a DragIcon, you specify resources that describe the
image. The XmNpixmap and XmNmask resources
represent the actual pixmap and its mask if you use one. Other
resources include XmNheight, XmNwidth, and
XmNdepth for specifying those attributes of the image, as well as
XmNhotX and XmNhotY for indicating the x,y coordinate of
the hotspot for the icon. The XmNattachment, XmNoffsetX
, and XmNoffsetY resources specify how the icon is attached to
the other parts of a drag icon.
There are a number of ways in which you can
customize the drag icons that are used for drag and drop. You can
specify default icons for all drag and drop transfers that start from
your application by setting various resources of the Screen object.
When you change the default drag icons for the Screen, the toolkit
handles the drag-over effects using the icons, as we discuss in 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.
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 XmNdragProc. Based on the movement of
the drag icon, the initiator receives the updated message back in one
of its drag-related callbacks.
The benefit of the dynamic protocol is that the
receiving application can provide sophisticated drag-under effects and
drag processing using its XmNdragProc. However, the
application does not have to provide these effects, as the toolkit
provides some basic effects by default. The dynamic protocol also has
some drawbacks. One drawback is that the messaging is expensive in
terms of network traffic and may lead to unacceptable performance if
the network is heavily loaded. Another limitation is that the image
used for the drag icon can only be as large as the largest cursor
supported by the system running the application.
The Display object provides two resources that can
be set to indicate which protocol the toolkit should use when an
application is the initiating or the receiving application in a drag
and drop transfer. These resources are
XmNdragInitiatorProtocolStyle and
XmNdragReceiverProtocolStyle. An application can set the resources
if it needs to specify a particular protocol, or they can be set by the
user in a resource file. We describe the different values for the
resources and how the actual protocols are determined in 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 XmNtransferProc.
The DragContext and DropTransfer objects both have
XmNincremental resources that specify whether or not the data
transfer is incremental. Incremental transfers are used when the data
is too large for a single X protocol request. No matter how the two
resources are set, the toolkit handles the transfer of data using the
underlying Xt selection mechanisms. Both the initiator and the receiver
are informed about the completion of the entire transfer once all of
the subtransfers are done, if there are any.
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.
During the initialization and setup of the user
interface, the initiating application needs to create any custom drag
icons that it wants to use for drag-over visual effects. The initiator
also needs to set up translations or event handlers to deal with
ButtonPress events for the second mouse button. The initiator (or
the user) can specify the drag protocol if necessary.
The receiving application needs to register widgets
as drop sites. For each drop site, the receiver must specify the valid
data targets and the XmNdropProc that takes over when a drop
occurs in the drop site. The receiver can also specify an
XmNdragProc to handle special processing during the drag and
custom drag-under visuals for the drop site. The receiver can query and
modify the stacking order of drop sites, as well as update information
about drop sites while the application is running. When the user starts
a drag operation, the toolkit on the initiating side takes control. The
application needs to create a DragContext by calling XmDragStart()
. It must specify the valid targets for the operation and the
XmNconvertProc that processes data transfer requests from the
receiving client. The application can also specify callbacks that are
invoked at various points during the drag, custom drag-over visual
effects, and a drop callback that is called when the drop occurs.
Receiving clients are not involved in this step. 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 XmNdragProc, it is invoked
each time a message is sent to the receiver. This routine can provide
custom drag-under visuals and other special processing. After the
XmNdragProc is finished, information about the drop site is passed
back to the initiator, and the DragContext callbacks are invoked, so
the initiator can still perform any special processing and provide
custom drag-over visuals. When the user drops the data, the toolkit on
the receiving side takes over from the toolkit on the initiating side.
The XmNdropProc for the drop site determines what action the
user has requested. If the user has requested help, the receiving
application should display a help dialog and see if the user wants to
proceed. If the user cancels the transfer, the drop does not proceed.
Otherwise, the XmNdropProc determines if the transfer is
possible by checking the targets supported by drag source.
If the drop is valid, the receiving client starts
the transfer of data by calling XmDropTransferStart(). If the
transfer is not valid, the routine still needs to be called to clean up
the operation. If the initiator has registered an
XmNdropStartCallback on its DragContext, it is invoked now. Other
than this callback, the initiating client plays no role when the drop
occurs. When the receiver calls XmDropTransferStart(), it must
specify a list of data and target formats that it wants from the
initiating application. The routine creates a DropTransfer object that
can be updated during the transfer. The receiver must also specify an
XmNtransferProc to handle the data once it has been converted by
the initiator. The receiver can cancel the transfer at any point.
For each target requested by the receiver, the
XmNconvertProc of the initiator is called to convert the data to
the specified format. The formatted data is passed back to the
receiver's XmNtransferProc. Once the entire transfer is
complete, the XmNdropFinishCallback and
XmNdragDropFinishCallback callbacks of the initiating client's
DragContext are invoked, if they have been specified.
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.
Motif supports two different protocols for
communication between applications during a drag. The dynamic protocol
passes messages between the two applications about the location of drop
sites, while the preregister protocol keeps track of drop site
information in a database. Since the preregister protocol does not
require communication between applications, it can provide better
performance on a heavily-loaded network. However, the dynamic protocol
offers the advantage of sophisticated drag-under visual effects.
The programmer or the user can specify the drag
protocol for an application by setting the
XmNdragInitiatorProtocolStyle and
XmNdragReceiverProtocolStyle resources defined by the Display
object. Motif creates a Display object automatically for an application
when it creates the first shell on a particular display. If an
application uses multiple displays, it has a Display object for each
one. An application can retrieve the Display object for a specified
display using XmGetXmDisplay().
The XmNdragInitiatorProtocolStyle and XmNdragReceiverProtocolStyle resources indicate the preferred drag protocol for an application when it is acting as an initiator and as a receiver, respectively, in a drag and drop transfer. Each resource can be set to one of the following values:
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
XmNdragInitiatorProtocolStyle or XmNdragReceiverProtocolStyle
to XmDRAG_NONE, the application does not participate in drag
and drop as an initiator or a receiver, respectively. This value is
useful for disabling drag and drop functionality, as we discuss in the
next section.
The actual protocol used for a drag and drop
transfer controls the visual effects that the user sees during the
drag. Under the preregister protocol, the server is grabbed so the drag
icon can be a pixmap of arbitrary size. The drag icon uses the depth
and colormap of the drag source widget, so it can be a color image.
When the dynamic protocol is used, the drag icon is implemented using
the X cursor, so it must be a bitmap and is limited in size (use
XQueryBestCursor() to determine the largest size for a particular
hardware configuration).
An application should support both the dynamic and
preregister protocols so that the user can select the protocol based on
his needs. Since the toolkit supports both protocols by default, an
application can easily support both as well. The code for handling drag
sources is the same under both protocols. Drop sites can specify an
optional XmNdragProc routine that is invoked under the
dynamic protocol and can be used to provide sophisticated drag-under
effects.
The only reason that you should specify the
XmNdragInitiatorProtocolStyle and
XmNdragReceiverProtocolStyle resources in application code is if
your application is going to support only one of the drag protocols. In
this case, you should set the resources to force the application to use
the supported protocol. You can retrieve the Display object for the
application using XmGetXmDisplay() and then use
XtVaSetValues() to specify the resources. You can also use
XtVaGetValues() to check the values of the protocol resources.
If your application supports both drag protocols,
you can specify the protocol resources in an app-defaults file to
indicate the application's preferred protocol. By default, an
application uses the preregister protocol because
XmNdragInitiatorProtocolStyle is set to XmDRAG_PREFER_RECEIVER
and XmNdragReceiverProtocolStyle is set to
XmDRAG_PREFER_PREREGISTER. If you have implemented custom
drag-under visuals with an XmNdragProc, you should set the
protocol resources to XmDRAG_PREFER_DYNAMIC so that the
dynamic protocol is used whenever possible. You can set these resources
in an app-defaults file as follows:
*DragInitiatorProtocolStyle: DRAG_PREFER_DYNAMIC *DragReceiverProtocolStyle: DRAG_PREFER_DYNAMICIf 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.
If you do not want to provide drag and drop in an
application, you can turn off the functionality in a number of ways.
The most effective way to turn off the functionality is to set both
XmNdragInitiatorProtocolStyle and
XmNdragReceiverProtocolStyle to XmDRAG_NONE. These
settings completely disable drag and drop for the application. You can
also set just one of the resources to this value to prevent an
application from participating in drag and drop as either an initiator
or a receiver.
You can also selectively turn off individual drag
sources in an application. To prevent a widget from providing its
default drag source functionality, you need to override the translation
for the second mouse button for the widget, as shown in the following
code fragment:
static char dragTranslations[] = "#override <Btn2Down>: DoNothing()"; static XtActionsRec dragActions[] = { {"DoNothing", (XtActionProc) DoNothing} };True to its name, the DoNothing() action routine does nothing. Once you parse the translation table and add the actions to the application, you can use the translation to set the XmNtranslations resources of all of the widgets that you do not want to function as drag sources.
There are two different ways to disable the drop
site functionality of a Text or TextField widget. If you want to turn
off the drop site permanently, you can call XmDropSiteUnregister()
for the widget. This routine removes the drop site associated with the
widget, so you have to reregister it if you want to enable the drop
site. To disable a drop site temporarily, it is easier to use the
XmNdropSiteActivity resource defined by the DropSite object. This
resource can be set to either XmDROP_SITE_ACTIVE or
XmDROP_SITE_INACTIVE. When a drop site is inactive, it does not
participate in drag and drop. You can set a drop site inactive using
XmDropSiteUpdate(), as shown in the following code fragment:
Widget text_w; Arg args[5]; int n = 0; ... XtSetArg (args[n], XmNdropSiteActivity, XmDROP_SITE_INACTIVE); n++; XmDropSiteUpdate (text_w, args, n); ...Even though drop sites are associated with widgets, you have to set DropSite resources using XmDropSiteUpdate(), not XtVaSetValues().
One situation in which you would probably want to
disable a built-in drop site is when the widget is designed to be
output-only. If you set the XmNeditable resource of a Text or
TextField widget to False, the user cannot drop data in the
widget because it is uneditable. However, the toolkit still displays
the default drag-under visual effects in this case, so the widget
appears as though it functions as a drop site. To make it clear that
the widget is not a drop site, you can disable the drop site using one
of the techniques we just described. If the widget is always
uneditable, it is fine to use XmDropSiteUnregister(), but if
the widget changes state, you are better off setting
XmNdropSiteActivity.
When you set a Text or TextField widget insensitive,
the user cannot interact with the widget, so it doesn't make sense for
the widget to function as a drop site. However, there is currently a
bug in the implementation of drag and drop such that the user can drop
text in an insensitive widget. To prevent this problem, whenever you
change the sensitivity of a Text widget, you should set the
XmNdropSiteActivity resource to match the sensitivity.
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 XmNdefaultNoneCursorIconThese resources specify the default icons for all the components of a drag icon, including the different operations and states.
Motif creates a Screen object automatically for an
application when it creates the first shell on a particular screen. If
an application accesses multiple screens, it has a Screen object for
each one. An application can retrieve the Screen object for a specified
screen using XmGetXmScreen().
The drag icon resources defined by the Screen object
only take effect when the XmNsourceCursorIcon,
XmNoperationCursorIcon, and XmNstateCursorIcon resources
have not been specified for a particular DragContext. All Motif widgets
with built-in drag source functionality set the
XmNsourceCursorIcon resource, so the Screen resource cannot be
used to specify a different source icon for these components. The
widgets do not set the XmNoperationCursorIcon and
XmNstateCursorIcon resources, so you can set the various default
icons for these components.
If neither the DragContext resources nor the Screen
resources are specified, Motif uses hard-coded default icons. For
example, the running figure shown in 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
XmNdefaultSourceCursorIcon resource to something more appropriate
for your application.
Before you can set the Screen resources in
application code, you must create DragIcon objects for the different
resources. In 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.xbmAlthough 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 XmNvalidCursorForeground,
XmNinvalidCursorForeground, and XmNnoneCursorForeground
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: yellowIn 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.
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.
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.
When the application reads the files in the
directory, it creates a global array of structures that contain
information about the files. This information is used to keep track of
filenames and file types. For each file, the application creates two
Label widgets: an image that represents the type of the file and a
string that specifies the filename. To link the image Labels to the
array, the application passes the index of each file in the array as
the XmNuserData resource for the associated Label. This value
can be retrieved and used to access the information in the array.
Depending on whether a file is a regular file or a
directory, the application places an image of a file or a folder next
to the filename Label. Each image is created using XmGetPixmap()
and specified as the XmNlabelPixmap for the appropriate image
Labels. The images are also used for drag icons during the drag
operation, as we describe in the next section. For more information on
XmGetPixmap(), see 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 XmNtranslations resource for each of the image Labels.
The only other operation performed in main()
that is relevant for the drag functionality is the interning of atoms
for target types. We use the FILE_NAME target that is defined by the
ICCCM, as well as two of our own targets, FILE_CONTENTS and DIRECTORY.
We chose these target names ourselves because the ICCCM does not define
any targets that are suitable for our purposes. We create atoms for
these targets using 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.
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 XmNuserData
from the Label widget that is the drag source. We use this value to
access the appropriate structure in the files array and
determine the type of file the user is manipulating.
Once we know what type of file we are dealing with,
we can create the source icon for the drag. We use the same pixmap as
for the image Label, so the drag icon is either a file image or a
folder image. We use XmGetPixmapByDepth() to create both the
icon and an iconmask so that we can specify a depth of 1. We
call XmCreateDragIcon() to create the drag icon, as shown in
the following code fragment from 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 XmNpixmap and XmNmask resources because the DragIcon sets its other attributes, such as width and height, based on the pixmap. The DragIcon takes its foreground and background colors from its parent, so we don't need to specify these resources either. The XmNmask resource must be set to a pixmap of depth 1, while the XmNpixmap can be any depth.
Now that we have a DragIcon object for the source
icon, we can call XmDragStart() to start the drag as shown
below:
n = 0; XtSetArg (args[n], XmNblendModel, XmBLEND_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 XmNsourceCursorIcon specifies the source drag icon that we just created. We also specify the background and foreground colors for the icon. The DragContext also has XmNoperationCursorIcon and XmNstateCursorIcon 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 XmNblendModel resource controls the
components of the drag icon that are used during the drag. This
resource can take one of the following values:
XmBLEND_ALL XmBLEND_STATE_SOURCE XmBLEND_JUST_SOURCE XmBLEND_NONEXmBLEND_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
XmNexportTargets and XmNnumExportTargets resources.
These resources specify the data targets to which the drag source can
convert the actual data. The XmNexportTargets resource
contains a list of target atoms. If the file is a directory, we specify
the DIRECTORY target. Otherwise, we specify both the FILE_CONTENTS and
FILE_NAME targets, which means that the drag source can provide both a
filename and the actual contents of the file to a drop site. In order
for the drag to succeed, another application must use at least one of
these targets for a drop site so that the user has some place to drop
the data.
The XmNdragOperations resource specifies
all of the operations that are supported by the application. This value
is specified as a 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 XmNconvertProc. This resource indicates the procedure
that is called to convert the actual data into the format requested by
the drop site when the drop occurs. We specify the ConvertProc()
routine for our application; this routine is described in the next
section. We also set XmNclientData to the Label widget that
started the drag, so that we have access to the filename and file type
data stored about that Label, as this information is needed to process
the drop.
After we create the DragContext and start the drag
with XmDragStart(), we register a callback routine for the
XmNdragDropFinishCallback so that we can destroy the DragIcon that
we created. This routine is discussed further in Section #sdragclean.
When a drop occurs, a procedure that has been
registered by the drop site is called to verify that the drop can take
place. This procedure checks the status of the operation and then
starts the data transfer. The receiving application requests the format
that it wants to receive the data in; the receiver can even request the
data in multiple formats, if they are available. For each requested
data target, the initiating application's XmNconvertProc is
invoked. In our case, this is the ConvertProc() routine. Since
we are not using incremental transfer, this routine is of type
XtConvertSelectionProc, which takes the following form:
typedef Boolean (*XtConvertSelectionProc)(Widget, 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 XmNclientData
resource of the DragContext. The goal is to get an index into the
files array so that we can access information about the file. The
index is stored in the XmNuserData resource of the Label
widget. Once we have the index, we can use it to get the filename from
the array.
Our conversion routine only handles requests for a
filename or the contents of a file. If target is set to
FILE_CONTENTS, ConvertProc() retrieves the contents of the
file and formats the data for transfer back to the receiving client.
The contents of the file are passed as a pointer to the text, using the
value_return parameter. If the drop site has requested the
FILE_NAME target, the routine returns the filename in value_return
. In either case, the length_return argument is set to the
length of the text, and format_return is set to 8 to
specify the length of each of the elements in value_return.
The return_type parameter is set to the appropriate target. If
the drop site has requested any other target, the routine returns
False to indicate that the transfer has failed.
The conversion routine does not handle the DIRECTORY
target, partly because we have not implemented any drop sites that
understand the target. A real file manager application would want to
support the dragging of directories to allow the user to modify the
file system using drag and drop. In this case, the conversion procedure
would need to have another branch for handling the DIRECTORY target.
Since the drag source only supports the copy
operation, the conversion routine does not have to worry about deleting
the existing data. With a copy operation, the XmNconvertProc
returns a pointer to the data so that when the operation is done, both
the initiator and the receiver have a copy of the data. With a move
operation, the initiating application returns a pointer to the data and
then waits for the receiver to tell it to delete the data. The
receiving application gets the data, stores it, and then specifies the
DELETE target to handle this situation. When the initiating client gets
this target, it can safely delete the data. With a link operation, the
initiator again passes a pointer to the data, but in this case the
receiver uses the pointer to establish a link to the data.
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 XmNtranslations 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 XmNuserData 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
XmNexportTargets, XmNnumExportTargets, and
XmNconvertProc resources using XtGetValues() so that it
can preserve the existing functionality. The appropriate new targets
are added to the list of targets based on the type of the file, and
XmNexportTargets is set to the new list. The NewConvertProc()
routine is used for the XmNconvertProc. The rest of the
DragContext resources are specified as in StartDrag(), and the
DragContext is modified using XtSetValues().
There is only one difference between the
NewConvertProc() routine and ConvertProc() in
file_manager.c. Instead of simply returning False if the
requested target is not FILE_CONTENTS or FILE_NAME,
NewConvertProc() calls the conversion procedure retrieved from the
Label widget, as shown in the following fragment:
(*convert_proc) (widget, selection, target, type_return, value_return, length_return, format_return);Essentially, our conversion routine handles our data targets and passes other targets to the Label widget's default conversion procedure.
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 XmNtopLevelLeaveCallbackThe names of the routines are fairly self-explanatory. Each callback has its own special callback structure that contains the relevant information about the current state of the drag operation. For example, the XmNdropSiteEnterCallback uses a callback structure of type XmDropSiteEnterCallbackStruct, which is defined as follows:
typedef struct { int reason; XEvent *event; Time timeStamp; unsigned char operation; unsigned char operations; unsigned char dropSiteStatus; Position x; Position y; } XmDropSiteEnterCallbackStruct, *XmDropSiteEnterCallback;The reason field in this structure is always XmCR_DROP_SITE_ENTER. The operation and operations fields specify the current operation and the set of supported operations, respectively. The dropSiteStatus element indicates whether or not the current drop site is valid, based on the targets supported by the drag source and the drop site. This field can have one of the following values:
XmDROP_SITE_VALID XmDROP_SITE_INVALID XmNO_DROP_SITEThe operation, operations, and dropSiteStatus fields are initialized by the toolkit based on the values of different resources for both the drag source and the drop site. If the drop site has registered an XmNdragProc and the dynamic protocol is being used, this routine can update these fields as necessary before the data is passed to the callback routine. A drop site might want to update these fields if it is performing any special processing or simulating multiple drop sites.
All of the callback structures for the DragContext
callback routines have a reason field that indicates why the
callback was invoked. The callback structures also provide information
that is relevant to the particular routine; they are all similar to the
XmDropSiteEnterCallbackStruct. See the DragContext reference
page in Volume 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
XmNsourcePixmapIcon, XmNsourceCursorIcon,
XmNoperationCursorIcon, and XmNstateCursorIcon resources
specify the different components of the drag icon. The
XmNvalidCursorForeground, XmNinvalidCursorForeground,
and XmNnoneCursorForeground resources of the DragContext can
be used to further distinguish between the different states during a
drag.
The XmNsourcePixmapIcon is used under the
preregister protocol and can be any size, while the
XmNsourceCursorIcon is used for the dynamic protocol and is
limited to the size of the largest cursor for a particular platform. If
you want to specify a color icon, you must use the
XmNsourcePixmapIcon resource. If XmNsourcePixmapIcon is
not specified, the value of XmNsourceCursorIcon is used. If
this resource has not been specified, the default source icon for the
Screen object is used.
At any point during a drag, the initiating client
can call XmDragCancel() to cancel the transfer. The user can
also cancel the operation by pressing the ESCAPE key. The initiating
client can retrieve additional information about the current drop site
by calling XmDropSiteRetrieve() during the drag.
After the user drops the data in a drop site, the
drag source has one last chance to check the status of the transfer and
provide custom visual effects. After the receiving client's
XmNdropProc completes, the DragContext's XmNdropStartCallback
is invoked. This routine has a callback structure of type
XmDropStartCallbackStruct, which is defined as follows:
typedef struct { int reason; XEvent *event; Time timeStamp; unsigned char operation; unsigned char operations; unsigned char dropSiteStatus; unsigned char dropAction; Position x; Position y; } XmDropStartCallbackStruct, *XmDropStartCallback;The reason field is set to XmCR_DROP_START, while the operation, operations, and dropSiteStatus fields are set as described previously. The dropAction field is set to XmDROP if the user has simply dropped the data, XmDROP_HELP if the user has requested help on the drop site, or XmDROP_CANCEL if the user has cancelled the transfer.
The initiating client can also register callbacks
that are invoked after a drag and drop transfer has completed. The
XmNdropFinishCallback is called after the receiver's
XmNtransferProc has finished processing all of the data targets
requested by the receiver. This routine receives a callback structure
of type XmDropFinishCallbackStruct, where the reason
field is XmCR_DROP_FINISH.
The XmNdragDropFinishCallback is invoked
when the entire operation has completed, which is immediately after the
XmNdropFinishCallback. 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().
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.
The file status area is a Label widget, so it does
not have any drop site capabilities by default. In order for the widget
to function as a drop site, we have to register it using
XmDropSiteRegister(), as shown below:
n = 0; importList[0] = FILE_CONTENTS; importList[1] = FILE_NAME; XtSetArg (args[n], XmNimportTargets, importList); n++; XtSetArg (args[n], XmNnumImportTargets, XtNumber (importList)); n++; XtSetArg (args[n], XmNdropSiteOperations, XmDROP_COPY); n++; XtSetArg (args[n], XmNdropProc, HandleDropLabel); n++; XmDropSiteRegister (file_label, args, n);This routine registers information about the drop site in a DropSite object using resources that are specified as for a normal widget. Since drop sites are referenced by their associated widget, however, the resources cannot be set using XtVaSetValues().
The XmNimportTargets resource specifies the data targets that the drop site can handle. We use the FILE_CONTENTS and FILE_NAME targets that we have interned using XmInternAtom(). The drop site only supports copy operations, so XmNdropSiteOperations is set to XmDROP_COPY. The final resource that we specify is the XmNdropProc. This callback is invoked when a drop occurs in the drop site; it is responsible for starting the transfer of data from the drag source to the drop site. The HandleDropLabel() routine handles the drop for the file status area, as we describe in Section #shandledrop.
The editor_dnd.c application also allows the
user to drag a file from file_manager.c to the main text entry
area and drop it. This action causes the contents of the file to be
copied to the Text widget. By default, the Text widget also has its own
drop site functionality that allows the user to drop textual data. We
want to modify the drop site to incorporate our own functionality but
still allow the user to drag and drop textual data in the widget. The
Text widget has already been registered as a drop site by the Motif
toolkit, so we do not need to call XmDropSiteRegister(). In
fact, if we did call that routine, we would override the default
functionality.
Instead, we call XmDropSiteRetrieve() to
get the values of the XmNimportTargets,
XmNnumImportTargets, and XmNdropProc resources for the
Text widget drop site, as shown in the following fragment:
n = 0; XtSetArg (args[n], XmNimportTargets, &importTargets); n++; XtSetArg (args[n], XmNnumImportTargets, &numImportTargets); n++; XtSetArg (args[n], XmNdropProc, &drop_proc); n++; XmDropSiteRetrieve (text_edit, args, n);Although a drop site is always associated with a widget, the XtVaGetValues() routine cannot be used to retrieve drop site resources, as the resources are stored separately from the widget in a DropSite object. We retrieve the XmNimportTargets resource so that we can add our own targets to the list of data targets for the drop site. A drop site can only have one XmNdropProc associated with it, so we need to get the existing routine and store it before we specify our own routine.
Once we have the data targets for the drop site, we
create a new list that contains the existing targets, as well as the
FILE_CONTENTS and FILE_NAME targets. We use XmDropSiteUpdate()
to modify the drop site:
n = 0; XtSetArg (args[n], XmNimportTargets, newTargets); n++; XtSetArg (args[n], XmNnumImportTargets, numImportTargets + 2); n++; XtSetArg (args[n], XmNdropProc, HandleDropText); n++; XmDropSiteUpdate (text_edit, args, n);The HandleDropText() routine processes the drops that occur in the Text widget. We explain this routine in detail in the following section.
If you need to update information for a number of
drop sites, you should use the XmDropSiteStartUpdate() and
XmDropSiteEndUpdate() routines, as they optimize the process. After
a call to XmDropSiteStartUpdate(), you can call
XmDropSiteUpdate() repeatedly for different drop sites. When you
are finished updating all of the drop sites, call
XmDropSiteEndUpdate().
When a drop occurs, the receiving application takes
over and the XmNdropProc for the drop site is called. This
callback provides a callback structure of type
XmDropProcCallbackStruct, which is defined as follows:
typedef struct { int reason; XEvent *event; Time timeStamp; Widget dragContext; Position x; Position y; unsigned char dropSiteStatus; unsigned char operation; unsigned char operations; unsigned char dropAction; } XmDropProcCallbackStruct, *XmDropProcCallback;The reason field is always XmCR_DROP_MESSAGE, and dragContext specifies the DragContext object for the drag operation that caused the drop. The dropSiteStatus element is set to either XmDROP_SITE_VALID or XmDROP_SITE_INVALID, depending on the targets that are supported by the drop site and the drag source. The callback routine can change this value if necessary.
The operations and operation
fields are set to the possible operations for the drag source data and
the current operation, respectively. The dropAction field
specifies the action requested by the user. If this field is set to
XmDROP, the user has requested a normal drop; if it is set to
XmDROP_HELP, the user has requested help for the drop site. We
discuss providing help for a drop site in the next section.
The main task of the XmNdropProc is to
determine whether or not the operation is possible and to start the
data transfer by calling XmDropTransferStart(). This routine
creates a DropTransfer object that keeps track of information about the
data transfer. The HandleDropLabel() routine initiates the
data transfer for the file status drop site, as shown in the following
code fragment from 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 XmNtransferStatus resource to XmTRANSFER_FAILURE to indicate that the transfer should not proceed. We also set XmNnumDropTransfers to 0.
Otherwise, the drop can proceed, so we establish a
list of target data types that we want to receive using the
XmNdropTransfers and XmNnumDropTransfer resources. Each
entry in XmNdropTransfers is an XmDropTransferEntryRec
, which is defined as follows:
typedef struct { XtPointer client_data; Atom target; } XmDropTransferEntryRec, *XmDropTransferEntry;The target field specifies the requested data target, and client_data passes any additional data that is necessary to the routine that processes the data transfer. We specify the FILE_CONTENTS and FILE_NAME targets. For each target, we pass the widget that is modified by the data from the drag source as client_data. For the FILE_CONTENTS format, the widget is the text entry area text_edit, while for FILE_NAME, the widget is the file status area file_label.
The final resource that we specify for the
DropTransfer is the XmNtransferProc routine. This routine is
of type XtSelectionCallbackProc; it is responsible for
actually processing the formatted data that is received from the drag
source. The routine is called for each target data type requested by
the drop site. This routine takes the following form:
typedef void (*XtSelectionCallbackProc)(Widget, 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 XmNconvertProc.
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
XmNlabelString for the file status area. Since the file status
area requests both target data types, both formats are processed by
TransferProc().
The HandleDropText() routine for the
ScrolledText object is very similar to HandleDropLabel(). The
main difference is that the routine for the text area checks the
XmNexportTargets resource of the DragContext object to determine
whether or not the drag source provides file data. If it does,
HandleDropText() initiates the data transfer just as in
HandleDropLabel(). Otherwise, the text routine calls the
XmNdropProc that we retrieved from the Text widget when we
modified the drop site. By calling the original drop routine, we allow
the Text widget to process textual data as it would by default. As a
result, the user can drop a file object in the text entry area, as well
as manipulate textual data in the widget using drag and drop.
Once a data transfer is in progress, additional
targets for the DropTransfer object can be specified using
XmDropTransferAdd(). The primary use of this routine is for move
operations. In this case, the drop site receives a copy of the data
from the drag source and then requests that the source delete the data.
Once the drop site has stored the data, it can call
XmDropTransferAdd() to specify the DELETE target, which indicates
to the initiating application that it should delete the data.
Since it is not always obvious what will happen when
data is dropped on a particular drop site, the user can request help on
a drop site by pressing the HELP or F1 key when the drag icon is over
the drop site. An application should provide help information for its
drop sites to assist users in understanding the drag and drop
capabilities of the application. When the user requests help, the drop
site should respond by posting an InformationDialog that explains what
would happen and allows the user to proceed with the drop or cancel it.
When the user presses HELP while the drag icon is
over a drop site, the XmNdropProc for the drop site is called
with the dropAction field in the callback structure set to to
XmDROP_HELP. 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.
The new HandleDropLabel() routine handles
the case when the dropAction field is set to XmDROP_HELP
. In this case, the routine creates an InformationDialog if it has not
already been created. The HandleDropOK() and
HandleDropCancel() routines are registered for the OK and
Cancel buttons in the dialog. If the dialog already exists, the
necessary fields in the client structure are specified so that
the callback structure information is passed to the callback routines
as client data. If the user has performed a normal drop operation, the
drop proceeds just as it did in editor_dnd.c.
The HandleDropOK() routine is invoked when
the user presses the OK button in the help dialog. This routine
proceeds with the drop by calling XmDropTransferStart(). The
status of the transfer is based on whether the drop performs a copy
operation or not. HandleDropCancel() cancels the drop when the
user presses the Cancel button by calling
XmDropTransferStart() with XmNtransferStatus set to
XmTRANSFER_FAILURE. One thing to note about both of these
procedures is that they get the XmDropProcCallbackStruct from
the client_data parameter, since the call_data
parameter is the callback structure for the dialog.
Under the preregister protocol, the drop site does
not participate during the drag. The initiating application handles the
drag-under visual effects based on the value of the
XmNanimationStyle resource for the drop site. This resource can
have one of the following values:
XmDRAG_UNDER_HIGHLIGHT XmDRAG_UNDER_SHADOW_OUT XmDRAG_UNDER_SHADOW_IN XmDRAG_UNDER_PIXMAP XmDRAG_UNDER_NONEThe default value is XmDRAG_UNDER_HIGHLIGHT, which means that a highlighting rectangle is displayed around the drop site when the drag icon enters it. The drop site can also be displayed with an inset or outset shadow using XmDRAG_UNDER_SHADOW_OUT and XmDRAG_UNDER_SHADOW_IN, respectively. The XmDRAG_UNDER_PIXMAP value specifies that a special pixmap is displayed in the drop site when the drag icon is in it; the XmNanimationPixmap and XmNanimationMask resources indicate the pixmap that is used. If XmNanimationStyle is set to XmDRAG_UNDER_NONE, there are no animation effects unless they are provided by the XmNdragProc .
Under the dynamic protocol, the drop site can
participate in the drag by specifying an XmNdragProc. This
callback routine is invoked when the drag icon enters or leaves the
drop site, when the drag icon moves within the drop site, and when the
operation changes while the icon is in the drop site. The callback
receives a callback structure of the type XmDragProcCallbackStruct
, which is defined as follows:
typedef struct { int reason; XEvent *event; Time timeStamp; Widget dragContext; Position x; Position y; unsigned char dropSiteStatus; unsigned char operation; unsigned char operations; Boolean animate; } XmDragProcCallbackStruct, *XmDragProcCallback;The reason field is set to 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 XmNimportTargets and XmNexportTargets for
the drop site and the drag source, respectively. The operations
and operation fields are set to the possible operations for
the drag source data and the current operation, repectively. The value
of operations is based on the value of the
XmNdragOperations resource for the DragContext, while the value of
operation is based on operations and the value of
XmNdropSiteOperations.
The XmNdragProc can change the values of
these three fields based on any special processing it performs, such as
handling simulated drop sites. When the routine is done, the toolkit
uses these values of the fields to initialize the fields in the
callback structure that is passed to the corresponding DragContext
callback routine in the initiating application.
The animate field specifies whether the
toolkit or the receiving client is handling drag-under effects for the
drop site. If the value is True, as it is by default, the
toolkit handles the effects based on the XmNanimationStyle
resource. The receiving client can set the field to False so
that it is responsible for providing drag-under effects. The main use
of the XmNdragProc is for providing specialized drag-under
effects, such as actual animation, that the toolkit itself does not
support.
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.