A Step by Step Introduction to Motif Drag & Drop

by Gene De Lisa

Copyright © 1995 Gene De Lisa, All Rights Reserved. Used With Permission.


The X Advisor: July 1995 - Vol 1 No 2. http://landru.unx.com/DD/advisor/

DISCOVERY PUBLISHING GROUP

In Motif release 1.2, the OSF introduced several new widgets and functions to support an interactive style of data transfer called drag and drop. Motif drag and drop is built on top of Xt selection mechanisms which in turn is based on X selections. This article will demonstrate transfer of a special format of text called "compound text". (In depth coverage of selection facilities such as incremental transfer and conversion of different data types will be covered in a future article). Drag and drop adds facilities to provide several different visual effects during the operation making it appropriate in cases where there is direct user manipulation of on screen objects.

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.

Introduction

Motif drag and drop is an intuitive way for the user to transfer data from one place to another. The data transfer could be between parts of the same application or to other applications. The user selects data typically using the middle mouse button (or whatever the Motif virtual binding BTransfer is set to). This widget or area is called the initiator of the drag. While the mouse button is pressed and moved, the user sees special icons that could indicate the specific operation taking place and/or the data type being transferred.


Figure 1. The different icons used on the cursor during a drag.

If the user releases the mouse button when the cursor is over a place that is willing to receive the data (a drop site), the icon will appear to "melt" into it. If released over an invalid drop site the icon will snap back to the drag initiator.


Figure 2. User's view of drag and drop. This is transferring the selected name from the list to perhaps one of the text fields.

The operations that are available are move (deleting the data from the source after the transfer), copy (leaving the data intact at the source), or link (which will enable changes in data to be reflected in either place). The initiation of a move operation is specified by the Motif style guide as BTransfer modified by the shift key, the copy operation with the control key and link with both. The default operation (just BTransfer) for each widget can be different and may depend on the drop site.


Figure 3. The special cursor icons representing the operation.

The data type that is currently being transferred is represented by one of the two default pixmaps. For other data types, the programmer can define their own special icons.


Figure 4. The cursor icons representing the data type being transferred.

Protocols

There are 2 protocols that specify how the visual effects are managed. If the protocol is specified as "pre-register" then the toolkit will manage the effects. This is easiest for the programmer. A server grab will take place during the drag which will reduce traffic but make anything else (such as screen dump) temporarily impossible. The other protocol is "dynamic" which allows the programmer to specify all of the 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.

How to Incorporate Drag and Drop Into Your Program

  • The Easiest Method

    There are several widget classes that have drag and drop functionality already built-in. The XmText and XmTextField widgets are registered as both drag sources and drop sites. The XmLabel and all of its descendants (e.g. XmPushButton), and the XmList are registered as drop sites. The user can drag from any registered drag source and drop in any registered drop site, provided that the drag source and drop site agree on the data type to be transferred. It is not necessary for the drag source and drop site to be on the same machine.

    Adding a New Drop Site With Default Visual Effects

    In this example we will use the pre-register protocol which will leave the responsibility for visual effects with the toolkit. There are 3 major steps to creating a new drop site.

    1. XmDropSiteRegister() registers the drop site. This registers the XmNdropProc which will be called when the drop occurs.

    2. XmDropTransferStart() should be invoked when the drop occurs (in the XmNdropProc). This registers the XmNtransferProc that will receive the data.

    3. Write the XmNtransferProc that will process the data.


    Figure 5. Adding a Drop Site

    1. In the following code example, we register the drop site. When the drop site is registered, the operations (XmDROP_COPY, XmDROP_MOVE, XmDROP_LINK) to be performed and data types that the site will accept are specified. The XmNdropProc, which is a function that will be called by the toolkit when the initiator releases BTransfer, is registered here. In the following code example, the data type that this site will accept is COMPOUND_TEXT.

      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); }

    2. Write the XmNdropProc. First the dropAction member of the XmDropProcCallbackStruct is checked. (This is an XmDropSite callback structure). In this example we will only support a drop. Another possible value for the dropAction would be XmDROP_HELP which will enable the programmer to display a message dialog with help on a dropsite (see code example help.c). If a copy operation has occurred then we will set up the arguments to XmDropTransferStart(), otherwise the drop is vetoed.

      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); }

    3. The XmNtransferProc is a function of type XtSelectionCallbackProc (see O'Reilly Volume V, page 466). This function is called for each of the targets (with appropriate client data) in the XmNdropTransfers in step 2. In this case the client data was the original drop site. If the type that arrives is COMPOUND_TEXT then a Motif resource conversion function that will convert compound text to an XmString is invoked.


      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); } }

    In the preceding example, the drop site was a label or one of the buttons that inherits from it. It would be just as easy if the drop site were a list widget. The actual position in the list where the item will be inserted can be calculated with XmListYToPos() which will return the position under the pointer. In many of the callback structures the location of the pointer is stored. The XmNtransferProc does not get such a callback structure so we need to resort to Xlib to locate the pointer.

    
    
        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;
    	}
    


    Figures 6 & 7.

  • Adding Simple Drag Under Effects

    The XmDropSite widget that is created with XmDropSiteRegister() has several resources that will change the appearance of the drop site when the pointer enters. The XmNanimationStyle resource can be set to one of
    
           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.

  • Adding a New Drag Source With Default Visual Effects

    In this example we will still use the pre-register protocol.
    1. Add an action and translation for BTransfer press.

    2. In the action function set up the arguments and call XmDragStart(). This will also register the XmNconvertProc.

    3. Write the XmNconvertProc that will convert the data into the requested type.


    Figure 8. Example program showing the default source icon with the copy operation during a drag.

    Our choices for a new drag initiator are XmScrollBar, XmDrawnButton, XmScale, XmDrawingArea and the rest of the composite widgets. The scrollbar is the standard example due to the ease of adding the translations. If a scale is desired as an initiator, then the scrollbar child of the scale can be obtained with the following code:

    
    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);
    }
    
    
    


    Figures 9 & 10.

    The first step is to make the initiator respond to BTransfer press events. This is accomplished by making translation and action tables then adding the new translations to each initiator and the action table (just once) to the application. The translation table is simply one big string that associates an event with an action procedure. In this case we are interested in only one event. The action table maps the string used in the translation table with the address of the actual action procedure. This needs to be done only once in the program.

    
    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); }


    Figure 11. Overview of interactions between drag initiator and drop receiver.

    Changing the Default Drag Over Effects

    When the first shell widget is created, Motif will automatically create an XmScreen object. There are several icon resources that can be set on this object that will be visible if the drag context has not created its own dragIcons. The following code example shows how to set the source icon to a left pointer instead of the default running figure icon. The operation cursors, and cursors for valid and invalid drop sites are also available.


    #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 	
    
    

    The Dynamic Protocol

    We have been using the preregister protocol. The drag-under and drag-over were managed by the toolkit on the initiator side. The receiver was not involved until the drop occurred. With the dynamic protocol, there is communication between the initiator and the receiver during the drag. In this way the receiver will control the effects by replying to messages from the initiator. The drop site will register a XmNdragProc that will be called whenever the initiator sends a message.


    Figure 12. Communication during a drag using the dynamic protocol


    Specifying the Dynamic Protocol

    The toolkit will automatically create an XmDisplay object along with an XmScreen object when the first shell is created in the application. This object can be retrieved with XmGetXmDisplay().

    
        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.



    Adding Drag Under Effects With the Dynamic Protocol

    The drop site registers a drag procedure that will be messaged by the initiator.

    
           ...
           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; }

    Adding Drag-over Effects With the Dynamic Protocol

    In the action we installed that is invoked when BTransfer is pressed, we can install callbacks on the XmDragContext widget that is created and returned by XmDragStart(). There are several resources pertaining to drag-over effects for the XmDragContext widget that can be set at creation or in the callbacks.

    
        /* 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.

    Conclusion

    We have concentrated on the functionality of the Motif drag and drop toolkit that provides visual feedback to the user during data transfer. The programmer is provided with a variety of ways to change the appearance of the drag cursor and the drop site. There are a few more features that have not been discussed such as simulating and nesting drop sites. However these are demonstrated in the OSF example DNDDemo.c.

    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.


    Source Code

    You can download the source code examples to this article.

    Appendix - Drag & Drop Widget Classes and Functions

    Glossary


    References
    1. O'Reilly and Associates Inc. Volume 5. "The X Toolkit Intrinsics Reference Manual". Third edition, April, 1992

    2. Prentice Hall. "OSF/Motif Programmer's Guide". Revision 1.2. 1993

    3. Douglas A. Young "The X Window system. Programming and applications with Xt". 2nd edition. Prentice Hall.

    Original blurb: Gene De Lisa is an instructor/consultant for Bluestone. He has been developing UNIX based software for over 10 years and teaches Xlib, Xt and Motif, C++, and Object Oriented Analysis and Design. He holds a doctorate from the University of North Texas. Gene can be reached via email at mailto:gene@bluestone.com.
    Current blurb: I don't work for Bluestone anymore. That email address above does not work. The one at Rockhopper Technologies works. You'd have to be nuts to work for Bluestone anymore unless you're a sales or marketing leech. Steve Mikes disappeared and never paid me for this article and the "X Advisor" dropped off the face of the earth also. So, here is a copy of the original article. I may find the tar file with the source sometime. Right now I don't have it handy.
    Send comments to mailto:gene@rockhoppertech.com