The X Advisor: July 1995 - Vol 1 No 2. http://landru.unx.com/DD/advisor/
DISCOVERY PUBLISHING GROUP
This article will present several ways to add drag and drop functionality. First it will present built-in features, add drag and/or drop functionality to widgets using the default visual effects and finally show how to add custom visual effects.
The protocols can be specified in a resource file via XmNdragInitiatorProtocolStyle and XmNdragReceiverProtocolStyle. The default protocol is pre-register. The first part of this article will use the pre-register protocol and defer coverage of the dynamic protocol until the end.
This is an encoding of text that supports internationalization that was introduced in X11R4. Interclient communication in the X Window system is through collections of named data called properties. For network efficiency the names of properties are "aliased" with fixed length identifiers called atoms. The atom representing the COMPOUND_TEXT property is interned (the atom is created by the X window server) and put into the importTargets array. Any other type that will be imported will be added to this array. The toolkit will create an XmDropSite widget to store information about the site. This widget is not created by the application. The resources given here can be found on the man page for the XmDropSite widget.
#include < Xm/DragDrop.h> void newDropSite(site) Widget site; { Atom importList[1]; Arg args[4]; int nargs; Atom COMPOUND_TEXT; COMPOUND_TEXT = XmInternAtom(XtDisplay(site), "COMPOUND_TEXT", False); importList[0] = COMPOUND_TEXT; nargs = 0; XtSetArg(args[nargs], XmNimportTargets, importList); nargs++; XtSetArg(args[nargs], XmNnumImportTargets, 1); nargs++; XtSetArg(args[nargs], XmNdropSiteOperations, XmDROP_COPY); nargs++; XtSetArg(args[nargs], XmNdropProc, HandleDrop); nargs++; XmDropSiteRegister(site, args, nargs); }
The XmNtransferProc is registered here. This is the function that will receive the converted data from the initiator. The widget that was registered as a drop site is passed in as the first formal parameter. It will be used in the transferEntries struct to pass this widget to the XmNtransferProc. The desired data type is also specified as COMPOUND_TEXT.
What actually happens is Motif will create a XmDropTransfer widget here. Applications never create this widget directly. It is used internally by the toolkit to store information about the transfer. The resources that are set here can be found on the man page for this widget. This will be passed as the first parameter to the XmNtransferProc. The dragContext member of the callback structure will be discussed in the examples on creating an initiator (a drag source).
void HandleDrop(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { XmDropProcCallbackStruct *DropInfo = (XmDropProcCallbackStruct *) call_data; XmDropTransferEntryRec transferEntries[2]; Arg args[10]; int nargs; nargs = 0; if ((DropInfo->dropAction == XmDROP) && (DropInfo->operation == XmDROP_COPY)) { transferEntries[0].target = COMPOUND_TEXT; transferEntries[0].client_data = (XtPointer) w; XtSetArg(args[nargs], XmNdropTransfers, transferEntries); nargs++; XtSetArg(args[nargs], XmNnumDropTransfers, 1); nargs++; XtSetArg(args[nargs], XmNtransferProc, TransferProc); nargs++; } else { /* veto */ XtSetArg(args[nargs], XmNtransferStatus, XmTRANSFER_FAILURE); nargs++; XtSetArg(args[nargs], XmNnumDropTransfers, 0); nargs++; DropInfo->operation = XmDROP_NOOP; DropInfo->dropSiteStatus = XmINVALID_DROP_SITE; } XmDropTransferStart(DropInfo->dragContext, args, nargs); }
void TransferProc(w, client_data, selection, type, value, length, format) Widget w; /* the XmDropTransfer widget */ XtPointer client_data; Atom *selection; Atom *type; XtPointer value; unsigned long *length; int *format; { Widget label = (Widget)client_data; Atom COMPOUND_TEXT; COMPOUND_TEXT = XmInternAtom(XtDisplay(label), "COMPOUND_TEXT", False); if (*type == COMPOUND_TEXT) { XtVaSetValues(label, XmNlabelString, XmCvtCTToXmString((char *) value), NULL); } }
if (*type == COMPOUND_TEXT) {
Window root, child;
int rx, ry, wx, wy, pos;
unsigned int keys;
XQueryPointer(XtDisplay(list), XtWindow(list), &root, &child, &rx,&ry,
&wx, &wy, &keys);
pos = XmListYToPos(list, wy);
xms = XmCvtCTToXmString((char *) value);
XmListAddItem(list, xms, pos);
}
The widget that is passed in is the XmDropTransfer widget that was created by XmDropTransferStart(). If something goes wrong with the conversion, the XmNtransferStatus resource should be set to XmTRANSFER_FAILURE, and the XmNnumDropTransfers set to 0. This will make the icon snap back to the initiator.
/* e.g. for numerical data */
if((val > max) || (val < min)) {
XtVaSetValues(w, /* the XmDropTransfer widget */
XmNtransferStatus, XmTRANSFER_FAILURE,
XmNnumDropTransfers, 0,
NULL);
return;
}
XmDRAG_UNDER_HIGHLIGHT
XmDRAG_UNDER_SHADOW_OUT
XmDRAG_UNDER_SHADOW_IN
XmDRAG_UNDER_PIXMAP
XmDRAG_UNDER_NONE
The default value is XmDRAG_UNDER_HIGHLIGHT. The drop site widget should have its XmNhighlightThickness greater than zero for this to be visible. (XmLabel has it set to zero by default). For the *SHADOW* values the XmNshadowThickness also must be non zero. If XmDRAG_UNDER_PIXMAP is used, the XmNanimationPixmap resource must be set a pixmap that has been created, and the XmNanimationPixmapDepth must be set to its depth.
Optionally, the XmNanimationMask can be set to another pixmap. This pixmap must have a depth of 1. The pixmap is not centered in the widget like the XmNlabelPixmap resource but placed in the upper left hand corner. The XmNlabelType resource does not have to be XmPIXMAP. Actually if it were, the dropped text would disappear into the pixmap! On the other hand if a pixmap were to be transferred instead of compound text then that setting would be appropriate.
Pixmap pix, mask;
Pixel fore, back;
Screen *screen = XtScreen(site);
fore = BlackPixelOfScreen ( screen );
back = WhitePixelOfScreen ( screen );
pix = XmGetPixmap(screen, "left_ptr", fore, back);
mask = XmGetPixmapByDepth(screen, "left_ptrmsk ", 1,0,1);
...
XtSetArg(args[nargs], XmNanimationStyle, XmDRAG_UNDER_PIXMAP); nargs++;
XtSetArg(args[nargs], XmNanimationPixmap, pix); nargs++;
XtSetArg(args[nargs], XmNanimationMask, mask); nargs++;
XtSetArg(args[nargs], XmNanimationPixmapDepth,
DefaultDepthOfScreen( XtScreen(site)) ); nargs++;
...
XmDropSiteRegister(site, args, nargs);
More control over the drag under effects will require using the dynamic protocol. We will cover that later.
Widget
getScrollBar(scale)
{
Cardinal n;
WidgetList kids;
int j;
XtVaGetValues(scale,
XmNchildren, &kids,
XmNnumChildren, &n,
NULL);
for(j=0; j < n; j++) {
if( XtIsSubclass(kids[j],xmScrollBarWidgetClass) )
return(kids[j]);
}
return(NULL);
}
static char dragTranslations[]=": startDrag()"; static XtActionsRec dragActions[]={"startDrag",(XtActionProc)startDrag}; ... /* for each widget */ XtOverrideTranslations(sb, XtParseTranslationTable(dragTranslations)); /* just once in the program */ XtAppAddActions(ac, dragActions, XtNumber(dragActions) );
In the next step, the action procedure that is called registers the XmNconvertProc and specifies the types that will be converted and the operations that the initiator will perform. In the case of the scrollbar, the XmDROP_COPY makes more sense than XmDROP_MOVE. Once again the types that will be converted are specified as atoms. In this example, the only type that is exported is COMPOUND_TEXT.
XmDragStart() initiates the drag. In order to keep track of the various configuration parameters, it creates a special widget called a XmDragContext. The resources that are set in this action can be found on the man page for this widget. This XmDragContext widget will arrive in the receiver's XmNdropProc as a field within the XmDropProcCallbackStruct structure which will be used as a parameter to XmDropTransferStart().
/* function of type XtActionProc. OReilly volume 5, p415*/
static void
startDrag(w, e, p, np)
Widget w;
XEvent *e;
String *p;
Cardinal *np;
{
Atom exportList[1];
Arg args[4];
int n;
exportList[0] = XmInternAtom(XtDisplay(w), "COMPOUND_TEXT", False);
n=0;
XtSetArg(args[n], XmNexportTargets, exportList); n++;
XtSetArg(args[n], XmNnumExportTargets, 1); n++;
XtSetArg(args[n], XmNdragOperations, XmDROP_COPY); n++;
XtSetArg(args[n], XmNconvertProc, dragConvertProc); n++;
XmDragStart(w, e, args, n);
}
In step 3, you write the XmNconvertProc function that was registered in the previous action procedure. This is a function of type XtConvertSelectionIncrProc (OReilly vol V, p429). Although this conversion is the most painful part of the process, it is just a standard Xt selection procedure. In this example we are simply transferring a few bytes. If the data to be transferred were larger then an incremental transfer would have been set up. In either case, the type of the procedure is XtConvertSelectionIncrProc. This means that in non-incremental transfers (i.e. atomic), several of the formal parameters are undefined. That is the reason the scrollbar is not passed in as client data.
The target is the data type that was requested by the receiver. If this is not a type that can be handled, the function must return False to indicate that the conversion could not be accomplished. Otherwise it retrieves the value from the scrollbar and converts it into compound text. Memory is allocated here for this compound text variable which will be deleted by the toolkit. The results of the conversion are stored in several of the formal parameters and the function then returns True indicating that the conversion was successful.
static Boolean dragConvertProc(w, selection, target, type, value, length, format, maxLen, client_data, id) Widget w; Atom *selection, *target, *type; XtPointer *value; unsigned long *length; int *format; unsigned long *maxLen; XtPointer client_data; XtRequestId *id; { Widget context; XmString xms; static Atom COMPOUND_TEXT = 0; int svalue; char *ct, *text, temp[8]; if(COMPOUND_TEXT == 0) COMPOUND_TEXT = XmInternAtom(XtDisplay(w), "COMPOUND_TEXT", False); /* if we cannot handle the conversion, return */ if (*target != COMPOUND_TEXT) return(False); /* get value */ XtVaGetValues(sb, XmNvalue, &svalue, NULL); /* convert it to compound text */ sprintf(temp, "%d", svalue); xms = XmStringCreateLocalized(temp); ct = XmCvtXmStringToCT(xms); text = XtMalloc( strlen(ct)+1); memcpy(text, ct, strlen(ct) +1); /* stuff it into the appropriate parameters */ *type = COMPOUND_TEXT; *value = (XtPointer) text; *length = strlen(text); *format = 8; /* 8 bits in a char */ /* indicate that the conversion was ok */ return(True); }
#include < X11/bitmaps/left_ptr> #include < X11/bitmaps/left_ptrmsk> #include < Xm/Screen.h> /* must be called after the source has been realized */ makeIcons(source) Widget source; { Widget xmscreen, icon; Arg args[7]; int n; Pixmap pix, mask; Pixel fore, back; Screen *screen = XtScreen(source); /* first get the XmScreen object */ xmscreen = XmGetXmScreen(screen); /* make the pixmap and mask */ fore = BlackPixelOfScreen ( screen ); back = WhitePixelOfScreen ( screen ); pix = XCreatePixmapFromBitmapData(XtDisplay(source), XtWindow(source), left_ptr_bits, left_ptr_width, left_ptr_height, fore, back,1); mask = XCreatePixmapFromBitmapData(XtDisplay(source), XtWindow(source), left_ptrmsk_bits, left_ptrmsk_width, left_ptrmsk_height, 1,0,1); /* create the drag icon */ n=0; XtSetArg(args[n], XmNwidth,left_ptr_width ); n++; XtSetArg(args[n], XmNheight,left_ptr_height ); n++; XtSetArg(args[n], XmNhotX,left_ptr_x_hot ); n++; XtSetArg(args[n], XmNhotY,left_ptr_y_hot ); n++; XtSetArg(args[n], XmNpixmap, pix); n++; XtSetArg(args[n], XmNdepth, 1); n++; XtSetArg(args[n], XmNmask, mask); n++; icon = XmCreateDragIcon(source, "sourceicon", args, n); /* use it as the default source icon replacing the running figure */ XtVaSetValues(xmscreen, XmNdefaultSourceCursorIcon, icon, NULL); }
In this example we created an XmDragIcon widget to represent the source part of the cursor and installed it on the XmScreen object. The pixmaps for the cursor and the cursor mask are created first. The depth of the mask must be one.
The possible dragIcons that can be set on the XmScreen are:
XmNdefaultSourceCursorIcon XmNdefaultInvalidCursorIcon XmNdefaultValidCursorIcon XmNdefaultLinkCursorIcon XmNdefaultMoveCursorIcon XmNdefaultCopyCursorIcon XmNdefaultNoneCursorIcon
Figure 12. Communication during a drag using the dynamic protocol
XtVaSetValues(XmGetXmDisplay(XtDisplay(shell)),
XmNdragReceiverProtocolStyle, XmDRAG_DYNAMIC,
XmNdragInitiatorProtocolStyle, XmDRAG_PREFER_RECEIVER,
NULL);
The possible values forXmNdragInitiatorProtocolStyle are:
XmDRAG_DYNAMIC can only use dynamic XmDRAG_PREREGISTER can only use preregister XmDRAG_PREFER_RECEIVER does both but uses receiver's XmDRAG_PREFER_DYNAMIC both but prefers dynamic XmDRAG_PREFER_PREREGISTER both but prefers preregister XmDRAG_DROP_ONLY data transfer but no effects XmDRAG_NONE turns off drag and drop
The possible values for XmNdragReceiverProtocolStyle are the same except for XmDRAG_PREFER_RECEIVER. The following table shows which protocol will actually be used given the settings of these two resources. For example, if the initiator specifies PREFER_DYNAMIC and the receiver specifies PREFER_PREREGISTER, then the resulting protocol will be DYNAMIC.
...
XtSetArg(args[nargs], XmNdragProc, DragProc); nargs++;
XmDropSiteRegister(site, args, nargs);
The drag procedure is a regular callback. The call data that is passed is an XmDragProcCallbackStruct. There are several fields that the programmer can check.
The reason field will be one of the following:
XmCR_DROP_SITE_ENTER_MESSAGE XmCR_DROP_SITE_LEAVE_MESSAGE XmCR_DROP_SITE_MOTION_MESSAGE XmCR_OPERATION_CHANGED
That enables the programmer to change the drop sites when any of these events occur.
There are two other fields with confusingly similar names; operation and operations. The operations field is a list of operations that are supported by the drop site. Since it is a single char, the appropriate operation can be checked by a bitwise and.
Thus, if (cbs->operations & XmDROP_MOVE) will test
to see if the move operation is supported by this site. The other field, the
operation field, is the operation that would take place if the drop actually
occurred. Actually both operation and operations can be modified by the
XmNdragProc to enable the drop site to message the initiator. The dropSiteStatus
field can also be modified by the XmNdragProc.
if (cbs->dropSiteStatus == XmDROP_SITE_VALID) {
if(somethingIsNotOk)
cbs->dropSiteStatus = XmDROP_SITE_INVALID;
}
Finally the animate field is a Boolean that can be set to show which side will be responsible for the drag-under effects. If it is set to False, then the receiver will provide them. If set to true, the behavior is the same as if the protocol were set to preregister (the toolkit will provide them).
The following example is simply boilerplate code to illustrate these concepts.
static void DragProc(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { XmDragProcCallbackStruct *cbs = (XmDragProcCallbackStruct *) call_data; /* operation, operations, and dropSiteStatus can be modified */ switch(cbs->reason) { case XmCR_DROP_SITE_ENTER_MESSAGE: printf("enter\n"); break; case XmCR_DROP_SITE_LEAVE_MESSAGE: printf("leave\n"); break; case XmCR_DROP_SITE_MOTION_MESSAGE: printf("motion\n"); break; case XmCR_OPERATION_CHANGED: printf("operation changed\n"); break; } if (cbs->operations & XmDROP_MOVE) { printf("supports move operation\n"); } else if (cbs->operations & XmDROP_COPY) { printf("supports copy operation\n"); } else if (cbs->operations & XmDROP_LINK) { printf("supports link operation\n"); } if (cbs->operation == XmDROP_MOVE) { printf("move operation\n"); } else if (cbs->operation == XmDROP_COPY) { printf("copy operation\n"); } else if (cbs->operation == XmDROP_LINK) { printf("link operation\n"); } else if (cbs->operation == XmDROP_NOOP) { printf("no operation\n"); } /* checks targets and operation */ if (cbs->dropSiteStatus == XmDROP_SITE_VALID) { printf("valid drop site\n"); } else if (cbs->dropSiteStatus == XmDROP_SITE_INVALID) { printf("invalid drop site\n"); } /* if false the receiver will provide drag-under effects */ /* if true the toolkit will provide drag-under effects as with preregister */ cbs->animate = False; }
/* get the index into the colormap for these colors */
XAllocNamedColor(display, cmap, "red", &red, &unused);
XAllocNamedColor(display, cmap, "green", &green, &unused);
XAllocNamedColor(display, cmap, "dodgerblue", &blue, &unused);
/* the usual resources for transfer here */
...
XtSetArg(args[n], XmNnoneCursorForeground, red.pixel); n++;
XtSetArg(args[n], XmNvalidCursorForeground, green.pixel);n++;
XtSetArg(args[n], XmNinvalidCursorForeground, red.pixel);n++;
XtSetArg(args[n], XmNcursorBackground, blue.pixel); n++;
dragContext = XmDragStart(w, e, args, n);
To use color in X, the programmer must first find the index into the colormap where the desired RGB values reside or allocate a new colorcell in the colormap. XAllocNamedColor() does this and stores the colormap index in an XColor structure that is passed in as a parameter. The structure member "pixel" contains the index. Once the colors are found we simply change the colors of the default drag icons. The actual drag icons can be set here also.
The XmDragContext has several callbacks.
dragContext = XmDragStart(w, e, args, n);
XtAddCallback(dragContext, XmNdropSiteEnterCallback, Enter, NULL);
XtAddCallback(dragContext, XmNdropSiteLeaveCallback, Leave, NULL);
XtAddCallback(dragContext, XmNdragMotionCallback, Motion, NULL);
XtAddCallback(dragContext, XmNoperationChangedCallback, Changed, NULL);
XtAddCallback(dragContext, XmNtopLevelEnterCallback, TopEnter, NULL);
XtAddCallback(dragContext, XmNtopLevelLeaveCallback, TopLeave, NULL);
Each of these are regular callbacks with their own calldata structures. Many of them have a dropSiteStatus field that reflects the value set in the receiver's XmNdragProc and the targets. The parts of the XmDragIcon can be changed dynamically in the callbacks.
static void Enter(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; { XmDropSiteEnterCallbackStruct *cbs = (XmDropSiteEnterCallbackStruct *) call_data; XmDragContext dc = (XmDragContext) w; if (cbs->dropSiteStatus == XmVALID_DROP_SITE) { printf("entercb valid drop site\n"); XtVaSetValues(w, XmNsourceCursorIcon, validIcon, XmNblendModel, XmBLEND_ALL, NULL); } if (cbs->dropSiteStatus == XmINVALID_DROP_SITE) { printf("entercb invalid drop site\n"); XtVaSetValues(w, XmNsourceCursorIcon, invalidIcon, XmNblendModel, XmBLEND_JUST_SOURCE, NULL); } if (cbs->dropSiteStatus == XmNO_DROP_SITE) { printf("entercb no drop site\n"); XtVaSetValues(w, XmNsourceCursorIcon, nodropIcon, XmNblendModel, XmBLEND_ALL, NULL); } }
This example shows how to change the cursor when entering a drop site. If the drop site is invalid, a special dragIcon is set for the source icon. The other parts of the dragIcon are turned off (no operation or state icons). When entering a valid drop site, the dragIcon for a valid operation is installed and all the other parts are made visible again.
Since Motif drag and drop is based on XtSelections, the details of the actual data transfer will be discussed in a future article on the selection mechanisms which will include transfer of different data types and incremental transfers for large amounts of data. The Uniform Transfer Model that is a feature of Motif 2.0 is designed to ease the pain of the actual data transfer, but does not change the features addressed here.