Dragging and Dropping Tree Nodes
In IDL versions 6.3 and later, you can create applications that allow users to drag tree nodes within a single tree widget, between tree widgets, or from a tree widget to a draw widget. Depending on the circumstances, the dragged tree node is either copied to the new location (leaving the source node intact) or moved to the new location (removing the source node). IDL provides a variety of controls that allow you to define the exact behavior of the application when a user drags and drops tree nodes.
This section discusses the following topics:
- The Drag and Drop User Interface
- Implementing Drag and Drop Functionality
- Tree Widget Drag and Drop Examples
The Drag and Drop User Interface
To the user of an IDL program that supports drag and drop functionality, the activity of dragging and dropping conforms to platform guidelines. The user selects one or more nodes using the left mouse button and drags them while holding the mouse button down. When dragging a node, the cursor indicates where the drop is allowed (above, on, below) or not allowed. Optionally, the cursor can include a + symbol to indicate the different between copy and move operations.
Note: On Windows platforms, the cursor shows an opaque copy of the node under the mouse pointer, but does not show all selected nodes, even though they are selected and are dragged. On UNIX platforms, a cursor reflecting the active drag is all that is shown.
In addition to the default platform-specific drag and drop behavior, IDL tree widgets implement the following:
- If the tree widget includes a vertical scroll bar, dragging nodes into the region at the top or bottom of the widget will automatically scroll to bring new nodes into view.
- If the user has dragged one or more nodes to a new location, but presses the Escape key before releasing the mouse button, the drag operation is canceled.
As the user drags the selected nodes, the drag and drop cursor changes to indicate whether a drop is allowed at the current position. The drag and drop cursor is displayed differently on different platforms:
Node |
Windows |
UNIX |
Above |
Horizontal line above target node |
Arrow bent and pointed up |
On |
Target node highlighted |
Straight arrow |
Below |
Horizontal line below target node |
Arrow bent and pointed down |
Not Allowed |
Circle with slash through |
Circle with slash through |
The following figure shows the appearance of the drag and drop cursor when inserting a node (here named Leopard) after a node named Jaguar, but within the node category Spotted.
Note: The IDL application ultimately controls which nodes to copy or move, where they are placed, and the destination tree’s final state. For example, IDL may override the restoration of a previous selection and instead select newly copied nodes.
Implementing Drag and Drop Functionality
Drag and drop functionality is not enabled by default. When creating an IDL application that incorporates a tree widget, you can enable drag and drop behavior to copy, move, or otherwise rearrange tree widget nodes. This section discusses the steps necessary to implement drag and drop functionality in your application.
Implementing drag and drop functionality in your tree widget application entails three steps:
- Making Nodes Draggable. You must explicitly specify that a node or group of nodes can be dragged.
- Responding to Drag Notifications (Callbacks). When the user drags a node, IDL generates a notification, which is passed to a callback function. You can use the default callback function for simple situations, or create your own callback function to handle special or complex situations. Drag notifications allow you to control if and where drops are allowed.
- Responding to Drop Events. When the user releases the mouse button to drop the selected nodes, IDL generates a drop event. You can use the information contained in the drop event structure to copy, move, or otherwise modify the tree widget.
Drag and Drop Properties are Inheritable
Drag and drop-related properties of a tree widget node (the values of the DRAG_NOTIFY, DRAGGABLE, and DROP_EVENTS keywords) are inheritable. This means that unless the value of one of these keywords is set specifically for a given tree node, that node will inherit the value of its parent. This means that if you set these values on the root node of a tree, but not on any child node, all nodes will have the values specified for the root node.
Inheritance is dynamic. This means that if the value of one of the inherited properties changes after the tree widget has been created (via a change of parent, due to a drag and drop operation, or via a call to WIDGET_CONTROL), the values for all of the inheriting nodes will change as well. One advantage of this type of inheritance is that nodes don’t keep track of their own property settings as they are copied and moved. For example, this allows you to create a folder that allows items to be dropped into it, but not dragged out of it, by setting properties on the folder.
The drag and drop-related properties can all be queried using the WIDGET_INFO function.
Making Nodes Draggable
The value of the DRAGGABLE property of a tree widget node (as set via the DRAGGABLE keyword to WIDGET_TREE or the SET_DRAGGABLE keyword to WIDGET_CONTROL) determines whether or not it can be used to initiate drag and drop operations.
Note: The value of a tree node’s draggability is independent of its dropability. Making a node draggable does make it droppable, but it is possible to have no allowable place to drop it. See Responding to Drag Notifications (Callbacks) for information on allowing users to drop nodes.
If a tree widget allows multiple selection (if the MULTIPLE keyword was set on the root node of the tree), it is possible that a user could select a mixture of draggable and non-draggable nodes. If the user attempts to drag this mixed selection by moving a draggable node, your IDL application will have to determine whether to allow a drop. You have several possible options to respond to this situation:
- Prevent the problem: Prevent the user from creating a mixed selection by responding to selection events and then programmatically altering the selection to make it legal.
- Deny all drops: Use a drag notification callback to inspect the selection (the dragged items) and reject all drops if any of the selected items are non-draggable.
- Allow the drag but only drop a subset of the nodes: Create a routine that checks (and possibly modifies) the list of selected nodes before calling the WIDGET_TREE_MOVE routine. Alternately, create your own copy/move routine.
Responding to Drag Notifications (Callbacks)
When the user drags a group of selected nodes over another node, IDL automatically calls the routine that is defined as the drag notification callback for the node over which the selection was dragged. The purpose of the drag notification callback is to provide the widget system with information about where dragged nodes can be dropped, allowing it to change the cursor display to indicate to the user whether nodes can be dropped at the current position. You, as an IDL application programmer, can choose to specify your own version of the callback function to override the default behavior. Drag notification callbacks are specified via the DRAG_NOTIFY keyword to WIDGET_TREE, or the SET_DRAG_NOTIFY keyword to WIDGET_CONTROL.
Drag notifications are also generated when the state of a drag modifier key changes (either up or down). If you override the default drag notification callback, you can use this information to update the drag cursor with a plus symbol (+
).
You can specify a unique drag notification callback function for each node. If you choose not to specify a callback for a particular node, it will inherit the callback defined for its parent node. If no callback is defined for any of a particular node’s ancestors, the default callback will be used.
Drag Notification Callback Return Values
The drag notification callback function returns an integer value calculated by performing an OR operation on the following values:
Value |
Meaning |
0 |
User cannot drop |
1 |
User can drop above |
2 |
User can drop onto |
4 |
User can drop below |
8 |
Show the plus indicator |
For example, if the drag notification callback returns 7, this means that dragged nodes can be dropped above, onto, or below the currently selected node. If the callback returns 10, the dragged nodes can be dropped onto (but not above or below) the current node, and the plus-sign indicator is included in the cursor.
The Default Drag Notification Callback
The default drag notification callback function is used if no function is specified for a given node or any of its ancestors. The return values for the default callback depend on the location of the node being targeted and (if it is a folder) whether it is expanded or not:
Tree Widget |
Expanded |
Return Value |
Meaning |
Root |
|
2 |
Onto |
Folder |
No |
7 |
Above, Onto, Below |
Yes |
3 |
Above, Onto |
|
Leaf |
|
5 |
Above, Below |
The default callback also compares the dragged nodes with the destination. If the destination matches or is a descendant of any of the dragged nodes then the default callback returns 0. Finally, if the destination will not generate drop events (DROP_EVENTS = 0) then the default callback will return 0.
Writing Custom Drag Notification Callbacks
The signature of a drag notification callback function is:
FUNCTION Callback_Function_Name, Destination, Source, $
Modifiers, Default
where:
- Callback_Function_Name is the name of the callback function
- Destination is the widget ID of the node over which the drag cursor is currently positioned
- Source is the widget ID of the source tree, from which a list of widget IDs representing the list of selected nodes can be retrieved using the TREE_SELECT or TREE_DRAG_SELECT keywords to WIDGET_INFO.
-
Modifiers is an integer value calculated by ORing the following values together, depending on which modified keys are currently depressed:
Value
Modifier Key
1
Shift
2
Control
4
Caps Lock
8
Alt
- Default is the value that would be returned by the default drag notification callback. If your criteria are similar to the default criteria for where nodes can be dropped, using this value can greatly simplify your callback code.
The return value should indicate where a drop is allowed to take place relative to the destination widget and whether the “+” symbol should appear with the drag cursor, as described in the above table.
When you write drag notify callbacks, remember that “above” one node may be “below” another node. Also, the concepts of “above,” “on,” and “below” are relative to the level of the destination node. For example, if a node is the final (bottom) node, then its defined “below” is a different position than it would be for its parent. The default callback takes these differences into account and does not allow you to drop below an open folder, preventing confusion over whether the dropped nodes will got into or below the folder.
When writing drag notification callbacks, keep the following in mind:
- Drag callbacks should execute quickly. If a callback takes too long to drag, events may be skipped. Remember that the drag callback is invoked after every change in position of the cursor.
- The source and destination trees should not be modified during the drag. Callbacks should not select, unselect, create, move, or delete nodes of the source or destination trees. Additionally, layout changes affecting the trees are also strongly discouraged.
- The drag callback should be tested thoroughly using a CATCH statement. Although the widget system will do its best to recover from errors that occur in a drag callback function, errors inside the callback function could lead to loss of keyboard and mouse input.
Note: In Windows recovery an error in the callback function is simple: click away from IDL and then back on IDL. Recovery on UNIX systems may require that the IDL session be killed from another terminal session.
The following code shows a callback function that intentionally generates an error, along with a CATCH statement that can be used to prevent the error from freezing IDL:
FUNCTION bad_callback, dest, source, modifiers, default
; The following CATCH statement protects against UI freezes.
CATCH, Error_status
IF Error_status NE 0 THEN BEGIN
CATCH, /CANCEL
PRINT, 'Error index: ', Error_status
PRINT, 'Error message: ', !ERROR_STATE.MSG
RETURN, 0
ENDIF
; The undefined variable caused an IDL interpreter error.
IF (undefined EQ 0) THEN RETURN, 7 $
ELSE RETURN, 0
END
In this example, an error occurs because the variable undefined
is undefined. The catch block handles this error and prevents loss of keyboard and mouse control.
You can also test your callback functions by explicitly calling them before the widget system does. This would test the callbacks from a safe state where the implications of errors are minor.
Tree Widget Drag and Drop Examples shows several uses of the default and custom callbacks. All of these examples have reliable static callbacks, allowing for safe removal of CATCH statements.
Responding to Drop Events
When the user releases the mouse button over a valid drop target, a WIDGET_DROP event is generated. Your application’s event handler should recognize this drop event and perform some action. In most cases, the event handler will call code to move or copy the selected nodes (see the WIDGET_TREE_MOVE routine for a ready-made move/copy routine), but you can execute any action you wish when the drop event is generated.
The drop event’s information is contained in a WIDGET_DROP structure. (See DROP_EVENTS in the reference section for WIDGET_TREE for a full definition of the WIDGET_DROP structure.) The important components of the structure when responding to drop events are:
- ID: The widget ID of the destination node. You can use the INDEX keyword to WIDGET_INFO along with this widget ID to determine the index of the destination node within the tree widget.
- DRAG_ID: The widget ID of the source tree widget. The selected nodes of this tree are the nodes that are being dragged. You can use the TREE_DRAG_SELECT and TREE_SELECT keywords to WIDGET_INFO along with this widget ID to retrieve the list of selected nodes or TREE_DRAG_SELECT.
- POSITION: The drop position (above, on, or below) relative to the drop target (returned in the ID field). Use this value, along with index of the destination node, to determine the index of the location where the dropped nodes should be inserted.
- MODIFIERS: An integer representing the state of the modifier keys, calculated by performing an OR operation on the values shown in the table under "Writing Custom Drag Notification Callbacks" above. On some platforms it is common for the Ctrl key to be used as the copy key, with simple move operations being performed when Ctrl is not pressed.
Issues Related to Dropping Nodes
IDL’s drag and drop functionality is quite general, because applications can have diverse requirements. Trees might allow only a single node to be selected, or may allow multiple selection. The application might use the Ctrl key to distinguish between copy and move operations. Other drag and drop issues that need to be solved by your specific application include:
- Copying nodes that are not marked as DRAGGABLE: IDL’s widget system does not mandate what can or will be copied. The DRAGGABLE keyword controls only the initiation of dragging. Applications can choose not to copy any node that is not DRAGGABLE.
- Dragging a node to one of its descendants: The default drag notification callback invalidates all drops that occur on a drag source or any of the drag source’s descendants. If you write your own drag notification callback, be sure to reject drops onto a source node (or any of its descendents) to avoid infinite recursion.
-
Copying unselected children of selected parents: This is shown in the following figure.
If treeNode12 is dragged and dropped, should treeNode121 also be copied, if we know that treeNode122 has been specifically selected and treeNode121 has not? One solution to this could be an individual copy of only the selected nodes. Another solution could be to attempt to preserve the hierarchy. Also, an application could choose to use the drag callback to reject the selection as unsuitable for dropping anywhere. The examples used here assume that a folder is dragged and dropped because all descendents are wished to be copied along with that folder, regardless of the selection state.
The following code illustrates one way to handle drop events:
PRO handle_drop_event, event
; figure out the new node's parent and the index
;
; The key to this is to know whether or not the drop took
; place directly on a folder. If it was then the new node
; will be created within the folder as the last child.
; Otherwise the new node will be created as a sibling of
; the drop target and the index must be computed based on
; the index of the destination widget and the position
; information (below or above/on).
IF (( event.position EQ 2 && $
WIDGET_INFO( event.id, /TREE_FOLDER ))) THEN BEGIN
wParent = event.id
index = -1
ENDIF ELSE BEGIN
wParent = WIDGET_INFO( event.id, /PARENT )
index = WIDGET_INFO( event.id, /TREE_INDEX )
IF ( event.position EQ 4 ) THEN index++
ENDELSE
; move the dragged node (single selection tree)
wDraggedNode = WIDGET_INFO( event.drag_id, /TREE_SELECT )
WIDGET_TREE_MOVE, wDraggedNode, wParent, INDEX = index
END
This code does the following things:
- Determines the parent and insertion position (index) for the new node: Drops can be above, on, or below, and the destination node can be a folder or a leaf. This example determines where to place the new node. Dropping onto a folder is the simplest option, but other situations require a knowledge of where the destination sits relative to its siblings. The INDEX group of tree widget keywords allows us to query and set the position of tree widget nodes. For more information on these keywords, see WIDGET_TREE.
- Moves the selected node to the new position: It first determines the widget ID of the dragged node, then uses the WIDGET_TREE_MOVE procedure to move it to the new location.
The above example works well for single selection trees that use the default drag notification callback. Situations involving multiple selection should use the TREE_DRAG_SELECT keyword to WIDGET_INFO rather than TREE_SELECT.
A more complete version of the previous example and more complex examples involving multiple selection and custom callbacks can be found in the next section.