The Source for Java Technology Collaboration
User: Password:
Register | Login help    

Search

Online Books:
java.net on MarkMail:


Choosing the Drop Action, and Further Changes to Swing Drag and Drop

Posted by shan_man on February 15, 2006 at 8:25 AM PST

Greetings Gentle Reader; I'm thrilled that you've come back to visit! As you might imagine, we're still working very hard on Mustang, aiming to make it the best Java SE release in history. And now with the Mustang Beta release live on the web, we eagerly await your questions, feedback and more of the excellent code contributions you've been sending in. While we wait, of course, we're still deeply involved in resolving our own bug lists and polishing off the features we've released, and we're already fixing things in post-beta builds. With all of this going on, I'm truly happy for the opportunity to bring you up to date with one of my little corners of Java SE.

In this new world, where you can read blogs about and download Java SE changes as fast as we can make them, an interesting scenario arises: You have access to code that is evolving; stuff that has the potential to change (for the better). This is an extremely positive thing! We want the code in your hands as quickly as possible, allowing you to test drive it, provide feedback, and to help us improve. But this implies a certain obligation for those of us who blog about our evolving technology - we need to keep you current with any changes to what we've already shared. It's only fair :-)

Which brings me to the purpose of this particular blog entry. I'd like to introduce you to the new support for choosing the drop action with Swing Drag and Drop, and narrate how the addition of this support prompted positive change to one aspect of the API I've already spoken of. The story begins with drop actions...

Every drag source (Java based or otherwise) advertises the set of actions it supports when exporting data. If it supports data being copied, it advertises the copy action; if it supports data being moved from it, then it advertises the move action; etc. (With Swing components, source actions are advertised via the TransferHandler's getSourceActions method.) When a drag is initiated, the user has some control over which of the source actions is chosen for the transfer, by way of keyboard modifiers used in conjunction with their drag gesture. Typically, an ordinary drag indicates a preference for moving, holding control while dragging indicates a preference for copying, and holding both shift and control gives a preference to linking. The so-called user action is determined by the system by choosing an appropriate value from the source actions based on this preference.

Prior to Mustang, this nifty little system could only get you so far with Swing Drag and Drop. When accepting a transfer, TransferHandler would always choose the user action as the action for the transfer. This would result in a copy being performed for a user copy action, a move of the data for a move action, and so on. But two things were missing. The first was a way to accept or reject transfers based on the drop action. As I explained in my previous blog entry on First Class Drag and Drop Support in Mustang, this ability was added with the introduction of the TransferInfo class in Mustang. Excellent! The second thing missing was the ability for the developer to explicitly choose an action; for why should the developer be limited to importing solely with the action chosen by the user?

Consider a component that wants to accept copied data, but doesn't want the responsibility that goes with allowing data to be moved to it. And consider a drag source that advertises supporting copy as one of its actions. Shouldn't the developer be able to support dragging from this source to our copy-to-only component, regardless of what the user prefers with their drag gesture? Of course they should! I discovered this limitation while composing an upcoming blog entry, and immediately filed bug number 6379813 (TransferHandler should allow the developer to choose the drop action) on the issue. With the fix for this bug comes the changes I'm about to share with you.

In order to support choosing the drop action, we determined that the following were needed:

  • A method to query the source's drop actions, so you know which actions are supported
  • A method to query the user drop action, which may be of interest when choosing an action
  • A method to query the actual drop action that TransferHandler will use (the explicitly chosen action, if applicable; otherwise the user action)
  • A way to explicitly choose the actual drop action

Where to add the first three methods was immediately obvious - we already had TransferInfo to hold this kind of information. So we added getSourceDropActions and getUserDropAction for the first two items respectively, and then updated the existing getDropAction to reflect the actual drop action. Now, where to add the ability to choose the drop action required some thinking. An initial thought was to provide a new protected method on TransferHandler that developers could override to return their choice of drop action. But when a use case was considered, this seemed less than ideal: Developer overrides canImport to determine the suitability of a transfer; inspecting the source actions for the action they want, and returning true only if that action is supported. Developer also must override our new method to choose and return the action they want, possibly inspecting the source actions again. Yuck! Not very tidy: A need to override two separate methods, and potentially write the same logic twice. No, it became clear that the right solution would allow the developer to do everything in one place.

Knowing that every TransferHandler supporting drop must implement the workhorse canImport method, and that it's the place where developers are already inspecting details of the transfer, it made sense to us that it is also the ideal place for any logic dealing with configuring the drop action. As such, providing support for choosing the drop action became as simple as adding one additional method to TransferInfo, to be called by the developer from canImport. Gentle Reader, it was the addition of this new setDropAction method that began the chain of events leading to the API changes I'm here to update you on.

You see, with this one tiny change, it came about that TransferInfo was no longer just an informational class - it had suddenly gained behavior. As such, referring to it as an "Info" no longer felt appropriate. And so the name TransferSupport was born. With a quick search and replace in our source base, the TransferInfo name has gone to the recycling bin, and the apt TransferSupport name has taken its place. Same little helper, just a little more power and three extra letters to type ;-)

Considering what I've discussed so far, let's see how one implementation of canImport might look in terms of the updated API. This implementation, which uses the new TransferSupport name, accepts drops of Strings and explicitly chooses the COPY action if it's supported by the source; it rejects the transfer otherwise:

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

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

    // check if the source actions (a bitwise-OR of supported actions)
    // contains the COPY action
    boolean copySupported =
        (COPY & support.getSourceDropActions()) == COPY;

    // if COPY is supported, choose COPY and accept the transfer
    if (copySupported) {
        support.setDropAction(COPY);
        return true;
    }

    // COPY isn't supported, so reject the transfer
    return false;
}

As I mentioned, this implementation rejects the transfer if COPY isn't a supported action of the drag source. Alternatively, the last line could instead return true, in which case the drop is accepted with the user drop action (the default if an action isn't explicitly chosen) if the COPY action isn't available. Let's try this out with a demo. As these are up to the minute changes that I'm discussing, you'll need to wait until build 76 or later of Mustang is available to run the demo. At the time of this writing, the current weekly build available for download is 71. Once you have the correct build, click here to launch the demo.

When it launches, you're presented with a frame containing three JList components. The larger list on the left, labelled "Drag from here" acts as a drag source for the demo, supporting both copying and moving of its data, and advertising this via its TransferHandler's getSourceActions method. On the right side are two smaller lists that act as targets for drops. The top one, labelled "Drop to COPY here", has a TransferHandler that always chooses a copy action, with a canImport implementation exactly the same as the code snippet I've shown above. Similarly, the bottom one, labelled "Drop to MOVE here", always chooses a move action, with the same code, but using MOVE in place of COPY.

Go ahead and initiate a drag from the source list and drag into the upper target list. Notice while you're dragging over the target that the use of the copy action is indicated by the copy-drop mouse cursor, which shows a little plus sign. This can be seen in Screen Shot 1 below. Screen Shot 2 illustrates the effect of dropping into this list: The dragged item is inserted into the target, but not removed from the source - a perfect copy drop.

Screen Shot 1 Screen Shot 2
 

Now drag again from the source list, and into the lower target list. While you're dragging over this target, notice that the mouse cursor indicates a drag action without the plus sign - indicating that the move action is in use (see Note below - a known, and soon to be fixed, bug may cause a no-drop cursor to be shown instead). This is shown in Screen Shot 3 below. When you drop into this list, notice that the dragged item is inserted, and removed from the source - the effect of a move drop. The result is illustrated in Screen Shot 4 below.

Screen Shot 3 Screen Shot 4
 

For a little bit of added fun, try dragging a snippet of text from your favorite native text editor into the target lists to see the same results. Most text editors support dragging text as both copy and move, and the support I've discussed works for both Java and non-Java components alike!

Note: Before we move on, one final note on behavior you may encounter while trying out this demo, depending on what build you are using to run it. I've just discovered a small bug in the underlying drag and drop subsystem, such that explicitly choosing an action other than the user action causes the no-drop mouse cursor to be shown when the drag source is Java based. Until this bug is fixed, you'll unfortunately see the no-drop cursor instead of the copy-drop cursor when dragging into the bottom target list. This does not affect the functionality of the demo, or the ability to actually drop into the component. I've filed this as a high priority bug and expect to see it fixed shortly. For your reference, the bug number is 6385534 (For Java drag source, choosing an action other than the user action shows a no-drop cursor).

Okay, so now you've learned about choosing the drop action, and the name change from TransferInfo to TransferSupport. Recalling what I mentioned earlier about upcoming changes to API that has already been released, you may be wondering if I've already covered it - and if a search and replace of the term TransferInfo in your code will bring you up to date. Well, the answer is "it depends". If you happen to have read my blog entry on Location-Sensitive Drag and Drop in Mustang, and have already started taking advantage of the new shouldIndicate method in TransferHandler, to specify whether or not the drop location should be indicated by the target of a transfer, you'll need to prepare for another small change.

You see, the line of thought that saw setDropAction added to TransferSupport, rather than providing another overridable method to TransferHandler for choosing the drop action, led to a re-evaluation of our placement of the recently added shouldIndicate method. Like choosing a drop action, deciding whether or not to show the drop location likely involves inspecting many of the same things that a developer would already be looking at in canImport. With a fresh look, it now seemed silly to force the developer to override a separate method for the purpose of changing the drop location indication. Do you see where I'm going?

TransferHandler.shouldIndicate has been removed and in its place a new method, setShowDropLocation(boolean), has been added to TransferSupport. This new method is to be called from TransferHandler.canImport any time the developer wishes to change the default displayability of the drop location. Let's look at an example of how this simplifies things by contrasting the code for a TransferHandler under the old API and the new API. Consider a TransferHandler implemented to accept drops into a JList only when the data is a String and the drop location is on top of items 0 through 5. Furthermore, let's say that it also wishes to always indicate the drop location, regardless of whether or not it accepts the transfer. Here's the code with the old API:

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

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

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

    // return true for index 0 through 5, and false otherwise
    return (dl.getIndex() >= 0 && dl.getIndex() <= 5) {
}

public boolean shouldIndicate(TransferInfo info, boolean canImport) {
    // always show the drop location
    return true;
}

It's not too bad in this case, but remember that in reality shouldIndicate and canImport will likely have more in common, such as conditionalizing on the drop location or the data flavors available. This makes the following new approach much more appealing:

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

    // always show the drop location
    support.setShowDropLocation(true);

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

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

    // return true for index 0 through, 5 and false otherwise
    return (dl.getIndex() >= 0 && dl.getIndex() <= 5) {
}

Simplicity - removing an override and keeping associated logic together. I like it and I like it a lot! One of the benefits to regular blogging about ongoing Java SE development is that it keeps me constantly re-evaluating and using our new features. Hopefully, it also keeps you excited, so you can help do the same. The end result being a more feature-complete, bug free API. And of course, the best release in history.


For your reference, the updated TransferSupport API:

Method Description
Component getComponent() Returns the target component for the transfer.
DataFlavor[] getDataFlavors() Returns the data flavors for the transfer.
boolean isDataFlavorSupported(DataFlavor) Returns whether or not the given data flavor is supported.
Transferable getTransferable() Returns the Transferable associated with the transfer.
boolean isDrop() Returns whether or not the transfer represents a drop operation.
     Note: The following methods throw IllegalStateException if the transfer is not a drop.
TransferHandler.DropLocation getDropLocation() Returns the current drop location for the component
void setShowDropLocation(boolean) Sets whether or not the drop location should be visually indicated for the transfer.
int getUserDropAction() Returns the user action for the drop.
int getSourceDropActions() Returns the drag source's supported actions.
int getDropAction() Returns the action chosen for the drop (the user action unless explicitly set).
void setDropAction(int) Sets the drop action for the transfer to the given action. Throws IllegalArgumentException if the action isn't supported.
Related Topics >> Swing      
Comments
Comments are listed in date ascending order (oldest first)