An application prepares for drag and drop by creating a source adapter on the widgets that acts as the drag source. It then hooks each source adapter's dragStartCallback and dragCompleteCallback and, optionally, the dragChangeCallback and dragCancelCallback.
An application also creates a target adapter on each widget that can act as a drop target, including any source widgets that can also act as targets. An application then hooks each target adapter's dragOverCallback and dropCallback and, optionally, the dragLeaveCallback and dragCancelCallback.
In the following example, a window is created with a single EwIconList on it. This list is configured to do drag and drop to and from another such window. This portion of the example creates the widgets and adapters, hooks the necessary callbacks, and realizes the window. The visualInfoCallback handler is also shown for completeness. The application containing the class should have EwDragAndDrop for a prerequisite.
Object subclass: #DragAndDropExample instanceVariableNames: 'icon' classVariableNames: " poolDictionaries: 'CwConstants EwConstants '
openOn: aCollectionOfObjects "Open the example, showing a list of objects." | shell iconList sourceAdapter targetAdapter | shell := CwTopLevelShell createApplicationShell: 'shell' argBlock: [:w | w title: 'Drag and Drop Example'; width: 250; height: 300].
iconList := shell createScrolledIconList: 'iconList' argBlock: [:w | w selectionPolicy: XmEXTENDEDSELECT; items: aCollectionOfObjects asOrderedCollection]. iconList manageChild. iconList addCallback: XmNvisualInfoCallback receiver: self selector: #visualInfo:clientData:callData: clientData: nil.
sourceAdapter := EwSourceAdapter on: iconList. sourceAdapter addCallback: XmNdragStartCallback receiver: self selector: #dragStart:clientData:callData: clientData: nil;
addCallback: XmNdragCompleteCallback receiver: self selector: #dragComplete:clientData:callData: clientData: nil.
targetAdapter := EwTargetAdapter on: iconList. targetAdapter addCallback: XmNdragOverCallback receiver: self selector: #dragOver:clientData:callData: clientData: nil;
addCallback: XmNdropCallback receiver: self selector: #drop:clientData:callData: clientData: nil.
"Note: The destroyCallback is not shown." icon := shell screen getIcon: 'default_xm_information' foregroundColor: CgRGBColor black. shell realizeWidget.
visualInfo: widget clientData: clientData callData: callData "Provide the icon and label for the object contained in callData." callData icon: icon; label: callData item.
The user initiates a drag by pressing the drag mouse button (ButtonDrag) over a drag source widget and moving the mouse a certain nominal distance. (The ButtonDrag is button 1 on Windows and UNIX platfoms, and button 2 on OS/2. Note that these buttons are settable.) The source adapter, having hooked the mouse events on the source widget, detects that a drag is starting and activates the dragStartCallback to the application. The callData for the dragStartCallback includes the mouse event that triggered the drag start, the items in the source widget that are being dragged, and each item's offset from the mouse location. It also contains a default image for each item to be used to represent the item during the drag. The images and offsets can be modified by the application to cause alternate images and offsets to be used. For example, an application might wish to use a multi-file icon to represent all items being dragged rather than a single icon for each item.
The images and offsets that the adapter provides as defaults depend on the API of the source widget. For example, because CwList provides no API to allow the source adapter to determine which item in the list is under the cursor when the drag starts, the adapter simply provides the selected items as the drag items. Similarly, because the source adapter cannot determine the offsets of the selected items, it provides offsets that cause the drag items to be beveled up and to the right from the mouse.
The dragStartCallback callData also contains a doit flag that is set to true by default. The application can change this flag to false if it determines that dragging is not allowed for some reason. The callData also includes a default vote (see "Voting and cursors"), which is an array of operations that the source allows for these items. The application can change this vote to an array of operations that it will allow, for the source items being dragged.
Finally, the dragStartCallback callData contains a slot for the source model. The application can place any value in this slot, but typically it is used to hold the underlying object that contains the items being dragged. This value will be passed along to the drag target in the callData of the target-related callbacks.
This portion of the example shows a sample dragStartCallback handler:
dragStart: sourceAdapter clientData: clientData callData: callData "The items in the callData are being dragged. Allow only move operations, not copy or link." callData doit: true; vote: (Array with: XmMOVE)
Each time the mouse moves, the system determines which widget is currently under the mouse. The system keeps a registry of all target adapters and their widgets. This enables it to map the widget under the mouse (if any) to its corresponding target adapter (if any).
If a widget is not under the mouse or if a target adapter does not exist for a widget, the cursor which indicates that the operation is not allowed (that is, the "No Parking" cursor) is automatically shown.
If a target adapter is found, the dragOverCallback is sent to its application. The dragOverCallback callData contains the items being dragged, the source widget, and the mouse event. The target adapter also determines which item in the target widget is under the mouse and supplies this item in the callData. This is only possible on widgets that provide the necessary API to determine this information. Because CW widgets provide no API to support this, the item under the cursor is always nil for target adapters on base widgets.
If there is an API to determine which item is under the cursor (as is the case in some extended widgets), the application must determine whether the item under the cursor is itself capable of accepting a drop. For example, a trash can or a printer typically would be able to accept a drop.
The callData also contains an emphasis flag that an application can set to specify which kind of emphasis should be shown on the target widget. Again, this is not possible on any of the CW widgets; only EwIconList, EwIconTree, EwFlowedIconList, EwTableList and EwTableTree use the emphasis flag. If an application has determined that the item under the cursor is capable of receiving the drop, it sets the emphasis flag to XmTARGETEMPHASIS. If it determines that the item is not capable of receiving the drop, then it can set the emphasis flag to either XmINSERTIONEMPHASIS or XmNOEMPHASIS (default). XmINSERTIONEMPHASIS indicates that if the drag items are dropped, they will be inserted into the target widget at the index determined by the mouse position.
The dragOverCallback callData also contains the source model and a field for the target model. The source model is whatever value was last placed in the sourceModel slot by the source in either the dragStartCallback or dragChangeCallback callData. The application can set the target model to be any object, but this is typically the object that would contain the drag items if they were to be dropped on the target.
The dragOverCallback callData also contains a vote. The application can set this to be an array of operations that it will allow given the current target widget, target item (if any), and the items being dragged.
Finally, the dragOverCallback callData contains a repeat flag. This Boolean variable determines whether another dragOverCallback should be activated even if the item under the cursor does not change. By default, this value is false for performance reasons. If the application needs the dragOverCallback to be triggered continuously, then the repeat field must be set to true each time the callback is triggered. For example, if an application uses a drawing area to present an image, the application might need the dragOverCallback to be triggered repeatedly so that it can detect when the items are being dragged over the various parts of the image.
This portion of the example shows a sample dragOverCallback handler:
dragOver: targetAdapter clientData: clientData callData: calldata "The items in the calldata are being dragged over the receiver. These items might be from another source. Allow only move operations between different lists, not copy or link, and not within the same list."
calldata sourceWidget == targetAdapter widget ifTrue: [calldata vote: #()] ifFalse: [ calldata vote: (Array with: XmMOVE); emphasis: XmINSERTIONEMPHASIS]