Friday, April 2, 2010

GHOST drag and drop, over multiple windows

I guess many people seen the example(the photocollage application) from Romain Guy, when he was still playing with java, about ghost drag and drop using the glass pane to render the ghost image:
  http://www.jroller.com/gfx/entry/drag_and_drop_effects_the


One problem with this implementation is that it used the glasspane, a unique resource of a window.
If that’s a problem for you, you can simply fix it by using JLayeredPane or JXlayer (I won’t talk about this implementation in this article).

The biggest problem is (and even more lately considering that palettes/multi windows application are vastly used now), how to allow ghost DnD through multiple windows?
You can easily imagine, in the photocollage application of Romain, that the photo thumbnails would be inside a different window than the drop location.
To do that, it’s not possible to use any kind of trick that span over only a window to draw the ghost image.

The solution I came up with is to use a non opaque window on which the ghost image is drawn and that is dragged around

While implementing this solution I ran into a unexpected problem: the drop method from DropTarget was never call, so in the DragGestureListener.dragDropEnd(DragSourceDropEvent) , DragSourceDropEvent.getDropSuccess() was always returning false.
I didn’t investigate much on that but I guess it’s because when the drop happen the cursor is over a window (the window where the ghost image is drawn) and not actually over the component that has the drop target.
So my first fix for this, was to add an offset of 1 pixel between the cursor and the ghost window, it worked well. But what I actually wanted is to have the cursor in the middle of the ghost window.
So the true problem was how to get the mouse event to be consumed by the component below the ghost window.
At this time I remember the testing I have done with non opaque window, you could click through a non opaque window if nothing was painting were the click takes place.

In the example below ,it contains 2 buttons with a bit of space between them, if you click between them the window will lose focus and it’s actually the component behind the window that will grab the focus.





JDialog d = new JDialog();
WindowsUtils.setOpaque(d, false);
JButton b1 = new JButton("button 1");
JButton b2 = new JButton("button 2");
d.getContentPane().setLayout(new FlowLayout());
d.getContentPane().add(b1);
d.getContentPane().add(b2);
d.pack();
d.setVisible(true);

So how could I apply this to my ghost window?

I need the mouse release event to get through the window, so that’s mean the pixel behind the cursor should not be painted. So that’s lead in my implementation to change the clip of the graphics when I am painting the image to keep away one pixel from being painted:


Area a1 = new Area(getBounds());
//remove the center point (where the cursor is)
a1.subtract(new Area(new Rectangle(getWidth() / 2, getHeight() / 2, 1, 1)));
g2.setClip(a1);

The missing pixel is normaly not visible by the user as the cursor is over it, but you can see it by taking a screen shot that does not contain the cursor.






To allow a component to be drag:

DnDGhostManager.enableDrag(JComponent,Transferable)

to declare a drop location

DnDGhostManager.enableDrop(JComponent,DataFlavor,DnDSuccessCallBack)

where DnDSuccessCallBack is an interface containing a single method

void dropSuccess(DataFlavor,DropTargetContext,Transferable,Point);


Demo: