![]() ![]() |
![]() |
||||
![]() |
![]() |
||||
Home News Download License Goals & Approach Documentation FAQ FXRex Screenshots Adie PathFinder FOX Calculator Projects FXPy FXRuby EiffelFox Japanese Docs ![]() |
![]() Documentation: FOX Drag and Drop Facilities
Drag and Drop Drag and Drop refers to the facility in FOX that allows entities to be dragged not just from within one part of an application to another, but also between applications. The FOX Drag And Drop implementation is based on the XDND Protocol developed by John Lindal. As FOX provides fairly high-level API's to access these features, it is actually fairly easy to instrument your programs with a Drag & Drop facility. For better understanding of how this works, it is important to define
some terminology first:
Registering Drag Types In order to communicate a particular data structure across applications, both partners need to first register a Drag Type. The Drag Type is created by calling the function:
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.
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 In order to be able to receive drops, a FOX Widget first needs to make itself a Drop Target. It does this by calling:
virtual void FXWindow::dropEnable();
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 FOX Widgets which have have been enabled for Drop Targets may receive a number of messages during a drag-and-drop operation. To give a user feedback about what is going on, I suggest that the Widget somehow changes its visual appearance based on receiving these messages. For example, a Folder Icon, normally shown in the closed state, may be changed to the opened state to indicate that a drop is pending and will be accepted if performed. Another method [which is usually performed by the Drag Source Widget, see later], is to change the shape of the cursor to a STOP sign when the drop will NOT be accepted; one could also use a combination of the two methods. Drop Target Widgets may receive the following messages:
void FXWindow::acceptDrop(FXDragAction action=DRAG_ACCEPT);
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);
void FXWindow::clearDragRectangle();
FXbool FXWindow::inquireDNDTypes(const FXDragType*& types,FXuint& numtypes); FXbool FXWindow::offeredDNDType(FXDragType type);
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 Making a Widget a Drag Source is comparatively easy. The transaction begins when the mouse button goes down. The Widget will need to call grab() to capture the mouse to the Widget, so that all future mouse events will be reported to the Widget, even if they occur outside of the Widget. Next, the Widget will call: FXbool FXWindow::beginDrag(const FXDragType *types,FXuint numtypes);
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);
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. For safety's sake, didAccept() will also returns DRAG_REJECT if the Drop Target has not called dropEnable(), or if the Drop Target fails to respond to any drag-and-drop messages. 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 During a drag operation, a Drag Source may receive one or more requests for the Drag Data. These requests take the form of a SEL_SELECTION_REQUEST message sent to the owner of the Drag Data. When a Drag Source receives a request for the selection, it should allocate an array (using the FXMALLOC macro) and stuff the data into it. 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.
FXbool FXWindow::setDNDData(FXDNDOrigin origin,FXDragType type,FXuchar* data,FXuint size);
Drag and Drop of FOX Objects The data exchange described above takes place using raw bytes. In more realistic cases, complicated data structures may have to be exchanges. It is important to realize that:
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.
Example: Serialize into a buffer, then give the buffer to the DND system:
FXMemoryStream str;
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.
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
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 This is no hard and fast rule, but generally speaking when data are being dragged within the same window, the default Drag Action should default to DRAG_MOVE, whereas when dragging between windows, the Drag Action should default to DRAG_COPY. These defaults can be overridden by holding down the Control-Key, which should force a DRAG_COPY, or the Shift-Key, which should force a DRAG_MOVE. Holding down the Alt-Key should probably force a DRAG_LINK.
Tips and Hints: When to Auto-Scroll When dragging from within scrollable windows, no scrolling should take place while outside the window; instead, scrolling should happen only when the cursor is being moved very close to the window border.
Tips and Hints: Let Cursor Reflect the Action There are two major schools of thought; some people prefer to let animate or highlight the drop-site to indicate an impending accept or reject of a drop, whereas others change the cursor instead. Apart from psychology, my take on this is do both:
|
||||
![]() |
![]() |
||||
![]() |
![]() |