Skip to main content

Location-Sensitive Drag and Drop in Mustang

Posted by shan_man on January 17, 2006 at 12:19 PM PST

Hello again friends!

Prompted by an excellent question from wrandelshofer in the comments section of my recent
Swing Drag and Drop
blog entry,
I'm jumping on the opportunity to tell you more about the enhancements made in Mustang. In particular,
I'd like to talk about how these changes have enabled location-sensitive Drag and Drop.

In order to clarify the concept, I'll paraphrase the question that
I've just referred to: Is it possible to allow the user to drop into some locations of a JTree and not others, and have Swing indicate this by only showing the drop location when the mouse is over acceptable
drop locations?
The answer is a resounding YES; this is the default behavior in Mustang!
Let's take a look at how this has been enabled.

Prior to Mustang, developers could not implement this very important behavior due to oversights in the
Swing implementation. The first problem, as I discussed in my previous blog entry, was that the drop location
wasn't easily available to the TransferHandler during a drag operation. The astute developer could
often overcome this limitation, however, after discovering that Swing changed the selection instead.
But it was actually a second more subtle and hidden problem in the implementation that was the limiting factor:
a problem with when TransferHandler called the canImport method. As you know,
the canImport method gives you control over whether or not a component should
accept a particular drag operation. Prior to Mustang, though, canImport was only called when
a drag operation entered a component, and the returned value was then used for the duration of the mouse's visit
to that component. This gave the developer absolutely no mechanism to return different values based
on the drop location, or any other factors that might change throughout the operation.

Enter Mustang! With the recent enhancements made to Swing Drag and Drop, canImport is
now called continuously by a component's TransferHandler while a drag and drop
operation is occuring over it. As such, the acceptability of a transfer can change as many
times, and based on whatever criteria (including the drop location), you choose. You need only
return a different value from canImport and Swing will respond. I'll elaborate on
what that response is. When canImport returns true to accept a transfer,
Swing shows the normal drag and drop mouse cursor and visually indicates the drop location, as discussed
in my previous blog entry. When canImport rejects a drag, on the other hand,
the "no-drag" mouse cursor is shown and no drop location is indicated. Let's see this in action -
click here to launch a Java Webstart demo
(requires build 76 or greater of Mustang).

Below you'll see two screen shots of how the demo appears. At the very
top is a drag source, with the label "Drag from here:". You'll use this component to initiate
drags when playing with the demo. The middle component is a JTree that's been
configured to accept drops on any node except for "names" and its descendants.
At the bottom is a JComboBox that we'll talk about a bit later, when I introduce
another new method in Mustang to allow further customization of when drop locations
are shown. For now, simply note that it is set to "Default", meaning that nothing has been changed
from the default behavior of showing drop locations when accepted, and not showing them when
rejected.

Initiate a drag by pressing on top of the drag source and dragging the mouse a short distance.
Drag into the tree and start moving down on top of the nodes. As you hover the mouse over
most of the nodes, the drag acceptibility is indicated by both the mouse cursor and by
the node becoming highlighted. This is portrayed in Screen Shot 1, which shows
an acceptable drag over the "yellow" node. Likewise, Screen Shot 2 shows what
happens when you drag over the "names" node or any descendant, which I've mentioned have
been configured to reject the drag. The "no-drop" cursor is shown, and the drop location isn't indicated.

Screen Shot 1   Screen Shot 2
 

Here's the implementation of canImport from the demo, showing how
drops on top of "names" and descendants has been prevented:


Important Note: This blog was published before finalization of the Swing drag and drop API for Java SE 6.
As such, there have since been minor modifications.
In particular, TransferInfo, as shown in the following code snippet, has been renamed to TransferSupport.
For full details on these changes, please see my later blog entitled
Choosing the Drop Action, and Further Changes to Swing Drag and Drop.



public boolean canImport(TransferHandler.TransferInfo info) {
    // for the demo, we'll only support drops (not clipboard paste)
    if (!info.isDrop()) {
        return false;
    }

    // we only support importing Strings
    if (!info.isDataFlavorSupported(DataFlavor.stringFlavor)) {
        return false;
    }

    // fetch the drop location (it's a JTree.DropLocation for JTree)
    JTree.DropLocation dl = (JTree.DropLocation)info.getDropLocation();

    // fetch the path from the drop location
    TreePath dropPath = dl.getPath();

    // we'll reject invalid paths, and descendants of the "names" node
    if (dropPath == null || NAMESPATH.isDescendant(dropPath)) {
        return false;
    }

    return true;
}

So what if you want to change an aspect of this behavior from the default?
What if you want to always show the potential drop location,
regardless of the acceptability of the transfer, so that the user can
see what item they're hovering over? Or say for some reason you prefer not
to indicate the drop location for some items, even though you want to accept the
transfer itself?


Important Note: This blog was published before finalization of the Swing drag and drop API for Java SE 6.
As such, there have since been minor modifications.
In particular, shouldIndicate, as mentioned in the remainder of this document, has been removed in favor
of a new setShowDropLocation method on TransferSupport. For full details on these changes,
please see my later blog entitled
Choosing the Drop Action, and Further Changes to Swing Drag and Drop.


TransferHandler now calls a new method, allowing you to
control this too:

boolean shouldIndicate(TransferInfo info, boolean canImport)

This method returns whether or not the drop location should be indicated for the
given transfer, and is called after the canImport method.
The arguments are the TransferInfo for the transfer in question,
and the return value from the preceding call to canImport.
The default implementation simply returns the value of the canImport
argument, causing the drop location to be shown if and only if the
TransferHandler has already said that it can accept the
import; the behavior that I've described above. By simply overriding this
method, you can customize the drop location indication as it works for your application.
For example, the following simple override will cause the drop location to always
be shown, even if the canImport method rejects the transfer:


public boolean shouldIndicate(TransferInfo info, boolean canImport) {
    return true;
}

Likewise, returning false causes the drop location to never be shown.
Of course, the implementation can be more involved, possibly inspecting the
TransferInfo, depending on how specific your logic needs to be.
An important thing to note is that this method is designed specifically
for configuring the drop location indication, and it does not
affect how the mouse cursor appears. The mouse cursor needs to tell the user
whether or not the drag is acceptable and, as such, needs to stay true
to the return value of canImport. Let's have a look at this
in practice, by bringing up the
demo again.

This time we will play with the JComboBox at the bottom, which has
been set up to dictate how shouldIndicate will return. Switch
the option to "Always" to have shouldIndicate always return true.
Dragging over the JTree you'll see that the drop location is shown for
all nodes, including "names" and its descendants which won't actually accept the transfer. This
can be seen in Screen Shot 3 below. Switching the JComboBox to "Never"
configures shouldIndicate to always return false. This causes
the drop location to not be indicated for any node, regardless of whether or not the
transfer is acceptable. Screen Shot 4 shows this case, where the "yellow"
node is not highlighted even though the mouse cursor is showing an acceptable transfer.

Screen Shot 3   Screen Shot 4
 

Pretty neat! Location-sensitive Drag and Drop for Swing, new in Mustang. I encourage you to keep those questions
and comments coming. You may just prompt my next blog entry! All the best :)

Related Topics >>