 |
Choosing the Drop Action, and Further Changes to Swing Drag and Drop
Posted by shan_man on February 15, 2006 at 08:25 AM | Comments (15)
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. |
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Good article. I am happy to see these improvement in the new JSE 6. I wonder if a feature I was long wish to be able to use was that when I drag something from my application, and drop on the desktop, that would be a file drop then. However, I don't want to do a file copy, but I want to write the file to where the user drops at. A work around would be to write to a temp directory, then when drop, I would move it from there to the drop place. However, this is hack at best, and there's many issue with it including security (if the file content is sensitive), etc. I am really disappointed if this is not addressed in the new release.
Posted by: st946tbf_3 on February 15, 2006 at 12:41 PM
-
st946tbf_3, thanks for posting your comment and asking this question, one that we've actually received quite often. I'm really sorry to answer that support for what you wish to accomplish is not yet available or targeted for Mustang.
Implementing this requires the addition of new API in the underlying AWT drag and drop system (which Swing sits on top of), and unfortunately this feature hasn't made the cut for the upcoming release.
If it's any consolation, please know that both the Swing and AWT teams know the importance of this issue and it's been tabled to be considered as a feature for the Dolphin release. Please help show your support by voting for the associated RFE,
4808793. Also, if you're interested, someone recently initiated a conversation with the AWT drag and drop owner on this exact issue, in this forum thread.
Thanks!
Posted by: shan_man on February 15, 2006 at 08:06 PM
-
Thank you for the reply and the links. Please don't be offended by my comment althought it's frank. I write this not to offend anyone, but point out a serious problem at Sun and Java. I think Sun has to reconsider their priority here. Waiting 2 years for Mustang and another 2 years for Dolphin would mean 4 years of waiting for a simple problem like this (think Winzip type application). 4 years often equals to a rise a fall of many platforms, technology. With the bug filed in Jan 2003 (ofcourse, people having problem with this since 1997, 1998), I think someone at Sun need to resign for the benefit of Sun and Java community. This is not a developer problem, it's the manager's who is managing the Java development.
Posted by: st946tbf_3 on February 16, 2006 at 09:45 AM
-
Nice.
One note regarding D&D on Linux. I've observed that when dropping files from Nautilus (Gnome file mananger) on Java app only accesible flavor is DataFlavor.stringFlavor (not DataFlavor.javaFileListFlavor...). That mean that you can get only string like 'file:///home/joe/droppedfile.txt'.
For example when running app posted by RomainGuy herehttp://www.jroller.com/page/gfx?entry=drag_and_drop_effects_part D&D don't work on my Ubuntu (funny thing that RomainGuy tested it on Ubuntu under VMWare and according to his words everything was ok).
This behavior was observed on Java <= 1.5 and previous snapshots of Mustang. Not sure about current state, I will test it and post message if it works or not...
Posted by: spk on February 17, 2006 at 03:36 AM
-
Thanks for the note on this Linux behavior, spk. I've passed this along to the engineers responsible for the AWT Drag and Drop subsystem, and asked them to either follow-up here directly, or fill me in so I can post a response to you concern.
Posted by: shan_man on February 20, 2006 at 10:40 AM
-
Let me answer the above-mentioned question concerning javaFileListFlavor on Gnome. It's a known issue, for which
I proposed a solid workaround; refer to bug 4899516 and also
see my advice at http://forums.java.net/jive/thread.jspa?forumID=74&threadID=8509.
Hope this helps.
Posted by: alexander1 on March 01, 2006 at 08:22 AM
-
I spent a lot of time getting this sort of behavior to work under 1.4. The behaviors I was looking for were the following:
* if no modifier keys are pressed, choose an action based on the intersection of source and target actions
* if a modifier key is pressed, set the action to NONE if the user-requested action is not allowed
The only thing I couldn't do with the existing classes was change the drop target's acceptance if the modifier keys were changed during a drag (the source has access to this info, the drop target does not -- once the drop target says "no", it stops getting messages). With the addition of a global flag from the source drag handler, the drop target could adjust on the fly.
I also tricked out the drag handler to automatically paint a drag image on any frame in the same VM, but that was just for fun.
Posted by: twalljava on April 15, 2006 at 07:08 AM
-
br/
is the code of this demo available for download ?
Posted by: kaba on May 10, 2006 at 03:26 AM
-
I've just been perusing the 1.6 source, specifically around customizing drop support when using a TransferHandler.
It strikes me a bit odd that "dropLocationForPoint" is package-protected, and that the notes for TransferHandler.DropLocation suggest that it wouldn't be used by developers.
Those seem the likely extension points if I'm writing a custom component with drop support. Don't I need to create a custom DropLocation that adequately translates a Point into something meaningful for my new drop target?
Every time DnD gets changed to make things "simpler", it seems a chunk of the original DnD extensibility/customizability gets made inaccessible. With TransferHandler, it was the ability to customize the DragSourceListener (or even to add one). Why can't the DnD support be extensible rather than requiring total replacement? Why aren't TransferHandler.DragHandler and TransferHandler.DropHandler extensible? Granted, I can add global listsners to DragSource, and suppress the default drop support, but then I'm back to square one, and all this DnD "simplification" has bought me nothing.
Posted by: twalljava on July 17, 2006 at 04:03 AM
-
kaba: The code for this demo has not been posted. However, I'm looking into how I can make it available. Thanks!
Posted by: shan_man on November 22, 2006 at 12:45 PM
-
twalljava: We're always cautious about adding new public/protected API. We don't want to accidentally release something that is overly confusing, or not extensible, or limits future enhancements. Some of the enhancements we've been able to do for Java SE 6 were possible because we don't expose too much implementation.
However, we know there's need for balance. And I agree with you about "dropLocationForPoint". We currently have an RFE tracking this issue: 6448332 (allow custom components to support drop location exchange). As always with the Swing API, if you have other areas that you think need to be opened up, please file RFEs and we'll address them. Thanks!
Posted by: shan_man on November 22, 2006 at 12:56 PM
-
I am VERY confused by all of this. I think I'm missing a very important article that describes all of these various "actions."
I do not understand how to get any drag and drop to work with the old or new API. It seems like there is always a circular reference that is created to support this. (TransferHandler knows about JPanel which knows about the TransferHandler)
Can anyone point me to any tutorial anywhere that fully describes Drag and Drop in Java 1.6?
Posted by: ronak2121 on December 08, 2006 at 07:44 PM
-
Can anyone please describe what a "linking" action is?
Posted by: ronak2121 on December 08, 2006 at 07:46 PM
-
Clipboard Actions in Mustang
Hi all. I am working on an extensible framework for drag and drop in Swing which uses the new enhancements brought in by Mustang. Firstly, a huge thanks to all you guys who fixed the drag and drop system for Mustang - it was a right pain to get location-sensitive drop authorisation in Java 5. Now it is a considerably more pleasant experience. Thanks for making things so much easier.
However I have a question regarding the clipboard actions in Mustang. I don't want to file this as a bug, because I feel I may be missing the point. So if anyone can help me out, I'd be much obliged.
It seems that when one is performing a CUT clipboard operation, Swing wants you to remove the CUTted data before allowing you to paste it. This is due to two factors: firstly, the default implementation of
TransferHandler.exportToClipboard()
places the data on the clipboard, then immediately calls
TransferHandler.exportDone(comp, tr, CUT).
This is then the queue for the transfer data to be removed. When the user requests the paste opration later, all that happens is that
TransferHandler.importData(support)
is called, which will import the data, but no subsequent call to exportDone is made. The obvious problem with this is that if the user cuts some data to the clipboard and then changes their mind, the data is gone (it's already been removed from the component).
Secondly, it is illegal to ask the TransferSupport which action is being performed during a clipboard data transfer operation. This means that all the nice action-sensitive "authorisation" (for lack of a better word) that Mustang provides for drag and drop is unavailable for clipboard operations. In fact, it seems that Mustang views clipboard operations as fundamentally different to drag and drop operations, and doesn't treat them with the same respect.
Is this summary correct, or have I missed the aim of the implementation. I'd appreciate any help. As it is, this issue (or non-issue as the case may be) is fairly easy to fix, even with a TransferSupport which has been made final ;-p I would propose that clipboard transfer operations and drag and drop transfer operations are essentailly the same thing, and should be (as far as possible) treated as the same. The main issue seems to be that while there is a (very useful !!) DropLocation, there is no ClipboardInsertLocation ...
Posted by: jacquesdutoit on March 12, 2007 at 02:30 AM
-
I'd like to reiterate kaba's request. Is the demo source available as a training aid ? Thanks.
Posted by: namestka on March 14, 2007 at 08:38 AM
|