Programming for the OS/2 PM in C:Drag & Drop

by Rick Papo

<< The Clipboard -- Index

Part IX. - Drag & Drop
I was starting to expand the painter program to handle additional types of graphic objects (like bitmaps), but found it becoming an exercise in trying to get C to behave like C++, and that is not the point of this series of articles. Instead, we will try something different: making use of the WorkPlace Shell's drag-and-drop facilities.

What is Drag & Drop?
The term 'drag and drop' was coined as a short description of how many object-oriented systems using pointing devices tend to manipulate data. We select a data object with the pointing device and then drag it over a program object, thereby invoking the program with the data object as a parameter. The program object could be something quite large (like a directory folder), or it could be a single item within a folder (like a program). The data object could be a single item within a folder, or the contents of an object's work area. You 'drag' the data to the processor, and 'drop' it there.

What can we do that the system hasn't already done for us?
Since the common operations of moving or copying files, or of invoking programs to process files, are already handled by the WorkPlace Shell (WPS) already, and handled nicely in fact, what is there left for us to do? One thing would be to enable the painter program to accept files dragged onto it from a folder. Another would be to save a file by dragging it to a folder direct from the painter work area. In a similar fashion, an image copy could be performed between two open painter windows.

Accepting Dropped Files
This is the easiest task. We only need to deal with three messages provided by the system: DM_DRAGOVER, DM_DRAGLEAVE and DM_DROP. The first message is used to notify the window that an object is being dragged over it. The window must decide whether it is a valid drop target for the object and return a status, which is then used to set the dragged object's highlighting. There are a variety of criteria upon which this decision can be made, but we will examine only the simplest in this article.

When the DM_DRAGOVER message arrives, its first parameter contains a pointer to a description of the drag operation in progress. We must obtain access to the entire description in this way: PDRAGINFO pDraginfo = (PDRAGINFO) mp1; DrgAccessDraginfo ( pDraginfo ); With this information, the first test that may be performed is: what kind of operation is this? The normal operation types are Move, Copy and Link. There is also a 'default' operation type allowed, whatever that might mean. In our sample program we will permit Copy and Default. The code for this becomes a switch statement like this: switch ( pDraginfo->usOperation ) { case DO_COPY: case DO_DEFAULT: { break; } default: DrgFreeDraginfo ( pDraginfo ); return ( MRFROM2SHORT ( DOR_NODROPOP, 0 ) ); } /* endswitch */ Notice that when we find an operation type we don't like, we release the drag information at once and return a status indicating that nodrop operation will be permitted to this window.

That was the general operation. What about the specific items being dropped? They need validation too, like this: int i; for ( i=0; ifsSupportedOps & DO_COPYABLE ) ) {                  Indicator = DOR_NODROPOP;                   Operation = 0;                   break; 	} /* endif */ 	if ( !DrgVerifyRMF ( pDragitem, "DRM_OS2FILE", "DRF_TEXT" ) ) {                   Indicator = DOR_NEVERDROP;                   break; 	} /* endif */ } /* endfor */ In this case, we loop over all the items in the drag operation, getting specific descriptions of each. Since we are supporting the copy operation only, we first check that each item is copyable. Secondly, we check that the rendering mechanism and format are acceptable to this program. We do this with the DrgVerifyRMF function. We want only files, which use the mechanism defined as DRM_OS2FILE, and for some reason I have not been able to determine the system is insisting that I use the DRF_TEXT format specifier. Notice that when the operation is not right we simply return a 'no drop operation' status, because the user can still change the operation with the Control key. When the object transfer mechanism or rendering format are wrong, however, we return a 'never drop' status, because we will never be able to accept such objects.

Once we are done determining our drop accept/reject status, we return it to the calling program. Before all this we had initialized the final status to Drop & Copy. We might have altered this to NeverDrop & NoOperation or DontDrop and NoOperation. The two components to the return status are returned together as separate components of the full 32-bit return code, like this: return ( MRFROM2SHORT ( Indicator, Operation ) ); When the DM_DRAGLEAVE message arrives, we will do nothing in this program. Some programs will react to DM_DRAGOVER by highlighting themselves as drag targets. If your program does this, then the proper way to react to DM_DRAGLEAVE is to remove that highlight. We will not do this in this program.

When the DM_DROP message arrives, we must make a final validation of the items being dropped, and then accept them if they pass the test. The initial procedure for getting access to the drag information is the same as for DM_DRAGOVER. Likewise, the procedure for getting information about the individual items being dropped is the same. Since we are only dealing with disk files, the remainder of the work becomes easy. You must first construct the file's source locations: DrgQueryStrName ( pDragitem->hstrContainerName, sizeof(Container), Container ); DrgQueryStrName ( pDragitem->hstrSourceName, sizeof(Source), Source ); strcpy ( State->Filename, Container ); strcat ( State->Filename, Source ); With the source file name's constructed, you need only load them in the normal fashion, in this case being the program's 'Load' function. Note that in this program, if multiple files are dropped on the window, each file will be loaded in sequence, destroying its predecessors without complaint. Minor problem...

Acting as a Drag Source
Allowing something to be dragged from your program window is not very difficult. The Workplace Shell keeps an eye out for users dragging with the mouse right-button, and generates an additional message to the application window: WM_BEGINDRAG. If your application ignores this message, nothing special happens, no dragging occurs. Processing this message provides a nice way to implement a Save operation.

At the time of the drag operation we might have a drawing in progress that hasn't been saved before, or resaved, and so its contents may be different from the disk version of the file being worked on (if the file even exists yet). For that reason, the first thing to do is to create a temporary version of the current file on disk. While we are at it, we can obtain the source directory name and suggest a name for the dropped file (the target). The code for doing this is: // Get the file name (less it's path). if ( State->Filename[0] ) { _splitpath ( State->Filename, Drive, Dir, FName, Ext ); strcpy ( Container, Drive ) ; strcat ( Container, Dir ); strcpy ( Target, FName ) ; strcat ( Target, Ext ); } else { strcpy ( Target, "PICTURE.PNT" ); } /* endif */ // Save to a temporary file. strcpy ( SavedFilename, State->Filename ); tmpnam ( TempFileName ); _fullpath ( State->Filename, TempFileName, sizeof(State->Filename) ); _splitpath ( State->Filename, Drive, Dir, FName, Ext ); strcpy ( Container, Drive ) ; strcat ( Container, Dir ); strcpy ( Source, FName ) ; strcat ( Source, Ext ); Save ( State ); Having dispensed with these preliminaries, we now need to allocate a drag information block. This is done with a system call: pDragInfo = DrgAllocDraginfo (1); In this case we allocated space for just one drag item. The painter program has only one picture to provide. Now we have to describe that picture as a drag item: DRAGITEM DragItem; memset ( &DragItem, 0, sizeof(DragItem) ); DragItem.hwndItem = hwnd; DragItem.hstrType = DrgAddStrHandle (DRT_TEXT); DragItem.hstrRMF = DrgAddStrHandle (""); DragItem.hstrContainerName = DrgAddStrHandle (Container); DragItem.hstrSourceName = DrgAddStrHandle (Source); DragItem.hstrTargetName = DrgAddStrHandle (Target); DragItem.fsSupportedOps = DO_COPYABLE; DrgSetDragitem ( pDragInfo, &DragItem, sizeof(DragItem), 0 ); We provide the handle of the window serving as the drag source. We specify the item type as DRT_TEXT, for no better reason than that I could not get this program to work any other way... We specify the rendering method as DRM_OS2FILE because we are providing a file for dragging, and the rendering format as DRF_TEXT for reasons already given. We provide global string handles for the container directory name, the source (temporary) file name and for the suggested target file name. Finally, we indicate that this file is copyable. When all this is ready, we use the DrgSetDragItem function to tell the system all this.

Another thing we have to do in preparation for the drag operation is to describe just how the mouse cursor will be altered to show the operation in progress. This is done through the DRAGIMAGE structure. In our case we will do this the easy way: DRAGIMAGE DragImage; memset ( &DragImage, 0, sizeof(DragImage) ); DragImage.cb = sizeof(DragImage); DragImage.hImage = WinQuerySysPointer ( HWND_DESKTOP, SPTR_FILE, FALSE ); DragImage.fl = DRG_ICON | DRG_TRANSPARENT; DragImage.cxOffset = DragImage.cyOffset = 0; After clearing the structure (so as to insure default behaviour), we set the structure size (a sort of version control) and indicate that the system default pointer for file transfers is to be used. This is an icon, and its background is to be transparent. Additionally, it will not be offset from the mouse pointer position.

Once all this preparation is complete, performing the drag is easy. Simply execute the following statement: DrgDrag ( WinQueryWindow(hwnd,QW_PARENT), pDragInfo, &DragImage, 1, VK_BUTTON2, NULL ); This statement indicates that a drag operation is now to take place with the application window frame as a base-point. We provide the drag information structure and the drag image description, the number of drag images (1), which mouse button to use for terminating the drag (right) and a reserved pointer (currently must be NULL). This function does not return until the drag operation completes or is aborted.

Wrapup
There are other messages that could be serviced, like DM_DROPHELP, but we will ignore them for now. The program now has a nice, though simplified, drag/drop interface. Try drawing a picture, then dragging with the right mouse button from the picture area to somewhere on the desktop. Watch how a new file object gets created. Try it a second time and watch how the duplicate name detection function gets invoked automatically. Try drawing a bit more, and then dragging one of the picture objects you created a moment ago into the painter window and dropping it there. Neat stuff.

<< The Clipboard -- Index