![]() |
Documentation: FOX Drag and Drop Facilities
![]() |
Drag and Drop
![]() |
For better understanding of how this works, it is important to define
some terminology first:
As mentioned before, the Drag Source and the Drop Target may or may not be in the same application. In fact, their corresponding applications could even be running on different machines. We assume, of course, that both drag source and drop target are shown on the same display.
Registering Drag Types
![]() |
FXDragType FXApp::registerDragType(const FXString& name) const;The registerDragType() function registers a new Drag Type "name" with the application's display, and returns an abstract handle to the Drag Type. The returned handle is used in all subsequent Drag and Drop operations. The Drag Type handle is unique for the display, that is, each application subsequently registering the same drag type name will receive the same handle. Obviously, the display must have been already opened before calling this function.
It is strongly suggested that if your application intends to communicate
with others, the Drag Type Names you use should be those of the corresponding
MIME
types.
This guarantees everybody else's applications can make sense of drag
data originating in your application [and vice versa]. Otherwise,
Drag Type Names can be any ASCII string sequence.
A corresponding function:
FXString FXApp::getDragTypeName(FXDragType type) const;Will return the Drag Type Name, given the Drag Type. You may need to use this in case your application receives a drop of an unknown type, and you need to decide what to do with it.
Becoming A Drop Target
![]() |
virtual void FXWindow::dropEnable();
To cancel drop-ability, use a function:
virtual void FXWindow::dropDisable();A Widget will not receive drag and drop messages unless it has been enabled as a drop target with dropEnable(). Note that the Widget may receive drag and rop messages with drop-data it does not understand, and thus it should only accept drops of the proper type.
Messages to the Drop Target
![]() |
Drop Target Widgets may receive the following messages:
void FXWindow::acceptDrop(FXDragAction action=DRAG_ACCEPT);
To accept or reject a drop, the Widget calls acceptDrop()
with an argument specifying the Drag Action suggested by the Drop
Target. The Widget can call this any number of times; however, the
last value will be the one reported to the Drag Source Widget. For
acceptDrop(),
the following values are valid:
FXDragAction FXWindow::inquireDNDAction() const;
The Drag Source should change the cursor to reflect the Drag Action
in effect; if necessary, the cursor should change to reflect the Drag Action
suggested by the Drop Target.
Normally, a Widget may get many, many SEL_DND_MOTION messages.
In order to cut down on the traffic, a Drop Target Widget may indicate
a rectangle and whether or not it wants further updates while the cursor
is inside this rectangle by calling:
void FXWindow::setDragRectangle(FXint x,FXint y,FXint w,FXint h,FXbool wantupdates=TRUE);
Widgets which do not care where the drop takes place may call setDragRectangle(0,0,width,height,FALSE),
which will cause the Drag Source to send no further updates while the cursor
is inside the Widget.
void FXWindow::clearDragRectangle();
Clearly, this is the opposite of setDragRectangle(). It is
equivalent to setDragRectangle(0,0,0,0,1);
FXbool FXWindow::inquireDNDTypes(const FXDragType*& types,FXuint& numtypes);
FXbool FXWindow::offeredDNDType(FXDragType type);
The first call yields an array of Drag Types currently available
from the Drag Source. The list is read-only, and should
NOT be freed. The Widget should NOT keep pointers to this
list, as the list ceases to exist after SEL_DND_LEAVE. The second
call tests to see if a certain Drag Type is being offered by the Drag Source.
If the Drag Type information is not enough,
the Drop Target may have to inquire the actual data from the Drag Source
and inspect it. It does this by calling:
FXbool FXWindow::getDNDData(FXDNDOrigin origin,FXDragType type,FXuchar*& data,FXuint& size);This call acquires the Drag Type type from the Drag Source. Upon return, data points to an array of bytes containing the Drop Data, and size is set to the number of bytes in the array. The array is now owned by the Drop Target Widget, and should be freed with the FXFREE() macro. The corresponding function in the Drag Source is describes elsewhere. The parameter origin should be set to FROM_DRAGNDROP.
Becoming a Drag Source
![]() |
FXbool FXWindow::beginDrag(const FXDragType *types,FXuint numtypes);
to start a drag-operation. The arguments to beginDrag()
describe the list of types [which must have been registered previously]
which are being offered. The Drag Source must be willing to furnish
each of these types when requested by the Drop Target. The beginDrag()
function returns FALSE if it failed to initiate a drag operation; the application
should not proceed with dragging in this case.
Upon each mouse movement, the Drag Source needs to indicate the new
mouse position to the system; it also notifies the Drop Target of the new
Drag Action. It does this by calling the function:
FXbool FXWindow::handleDrag(FXint x,FXint y,FXDragAction action=DRAG_COPY);
The handleDrag() function determines the Widget under the
cursor, and issues a SEL_DND_ENTER when it first enters a Widget, a SEL_DND_LEAVE
when it leaves the Widget, and a SEL_DND_MOTION when the cursor simply
has moved over the Widget [subject to the drag rectangle set by the Drop
Target]. It will not send any messages if the widget under the cursor
has not called dropEnable() first to enable drops on it.
The handleDrag() function may return FALSE if it fails.
To find out if a Drag Source is in the middle of a drag operation, applications
may call the following member function:
FXbool FXWindow::isDragging() const;While the Drag Source is dragging, it may want to inquire whether the Drop Target's accepted or rejected a drop. It does this by calling:
FXDragAction FXWindow::didAccept() const;The function didAccept() simply returns DRAG_REJECT when the Drop Target would NOT accept the drop, and returns DRAG_COPY, DRAG_MOVE, DRAG_LINK if it did; the Drag Source should reflect the Drag Action returned by changing its cursor.
Applications may choose to change the cursor shape based on what didAccept()
returned, as illustrated by the following code fragment:
handleDrag(event->root_x,event->root_y);
if(didAccept()!=DRAG_REJECT){
setDragCursor(drop_ok_cursor);
}
else{
setDragCursor(drop_not_ok_cursor);
}The rationale is that even though Drop Targets may give a visual cue when a drop is OK, not all applications running on your system may be drag-and-drop aware; changing the cursor also will give an additional clue.
When the user releases the mouse button, the Widget needs to call ungrab()
to release the mouse capture, and then calls:
FXbool FXWindow::endDrag(FXbool drop=TRUE);This will cause a SEL_DND_DROP message to be sent to the Drop Target, if and only if:
Messages to the Drag Source
![]() |
In FOX, there are three possible types of SEL_SELECTION_REQUEST messages.
The type of request is given in the FXEvent member variable origin.
For drag & drop, the origin will be set to FROM_DRAGNDROP; Drag
Sources which will perform only drag & drop data transfer should have
the message handler return 0 if the origin is not FROM_DRAGNDROP.
If the origin is FROM_DRAGNDROP, the owner of the dragged
data should inspect the requested type, which is found in the FXEvent's
target member variable, and if it can supply this type it
should gather up the requested data.
The Drag Source then calls
FXbool FXWindow::setDNDData(FXDNDOrigin origin,FXDragType type,FXuchar* data,FXuint size);
To hand the array over to the system. The ownership of the
array passes to the system, and the Drag Source should not attempt
to make any further references to this array. The parameter origin
should be set to FROM_DRAGNDROP.
The Drop target may make a request for the Drag Data from the drag
source by calling getDNDData(), described above.
Drag and Drop of FOX Objects
![]() |
FOX takes care of some of the latter troubles by furnishing special
FOX primitive types, such as FXchar, FXshort, FXint and so on. A
FOX implementation will ALWAYS make sure these types have the same size,
although byte order may still be reversed on some machines.
More sophisticated data transfers can be accomplished using the FOX
FXMemoryStream
class. The FXMemoryStream is a subclass of FXStream that serializes/deserializes
data to/from a memory-buffer. The FXStream classes also support
byte
swapping on the reader side, making it very convenient to exchange
data between hererogeneous machines; moreover, the FOX Stream classes support
serialization of FOX Objects.
Thus, entire networks of objects may be serialized, transmitted to
the drop site, and then deserialized.
Example:
Serialize into a buffer, then give the buffer to the DND system:
FXMemoryStream str;
FXuchar *buffer;
FXuint size;
FXObject *myobjectptr; // Pointer to the FXObject-derived object we wish to transfer
FXuchar endianness;endianness=FXStream::isLittleEndian();
str.open(NULL,FXStreamSave); // The FXMemoryStream will create its own buffer
str << endianness;
str << myobjectptr;
str.takeBuffer(buffer,size); // Take ownership of the buffer away from the FXMemoryStream
str.close();
setDNDData(dndtype,buffer,size); // Give the buffer to the DND system
Take data from the DND system, then give the buffer to the Stream
and deserialize from it:
FXMemoryStream str;As you see, this is a mighty fine way to transfer arbitrary objects between applications. All you have to do is derive certain objects from the FXObject base class, then properly inplement the load() and save() member functions for that class, so that all object member data may be properly serialized or deserialized. For more info, see the chapter on Serialization.
FXuchar *buffer;
FXuint size;
FXObject *myobjectptr; // When done, this points to an FXObject-derived object
FXuchar endianness;getDNDData(dndtype,buffer,size); // Take possesion of the buffer from the DND system
str.open(buffer,size,FXStreamLoad);
str >> endianness;
str.swapBytes(endianness!=FXStream::isLittleEndian()); // Swap bytes in the receiver if necessary!!
str >> myobjectptr;
str.close();
FXFREE(&buffer);
Tips and Hints: Moving Data Between Applications
![]() |
When data is being moved between applications, the Drop Target should perform the following sequence of operations:
Acquire the dropped data, using getDNDData(), exactly the same as what it would do for a Copy Drag Action;
Then do a getDNDData() with the Drag Type DELETE, which must have been previously registered with registerDragType("DELETE").
The Drag Source will not supply any data when a request for the DELETE
drag type is received; instead, knowing the data has been properly received
by the
Drop Target, it will delete the data instead.
Thus, the getDNDData() call with Drag Type DELETE will yield a NULL
data array pointer.
Tips and Hints: When to Copy and When to Move
![]() |
Tips and Hints: When to Auto-Scroll
![]() |
Tips and Hints: Let Cursor Reflect the Action
![]() |
This reflects my view that in the software world, we can make our
own rules; we can diverge from the physical model of ``manipulating
rigid objects'' if this is appropriate or gives the user a better handle
on things.