/* Copyright (c) 2006-2007 Timothy Wall, All Rights Reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * <p/> * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package furbelow; import java.awt.AlphaComposite; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLayeredPane; import javax.swing.RootPaneContainer; import javax.swing.SwingUtilities; import javax.swing.Timer; /** Provide a ghosted drag image which will appear on any instances * of {@link RootPaneContainer} in the current VM. Its location in * screen coordinates may be set via {@link #move}.<p> * When the image is no longer needed, invoke {@link #dispose}, which * hides the graphic immediately, or {@link #returnToOrigin}, which * moves the image to its original location prior to invoking * {@link #dispose}. */ public class GhostedDragImage extends AbstractComponentDecorator { private Icon icon; // drag origin, relative to drag source private Point origin; // current drag location, relative to drag source private Point location; // offset of the image from the cursor private Point imageOffset; private List ghosts = new ArrayList(); private Component dragSource; private float ghostAlpha = DragHandler.DEFAULT_GHOST_ALPHA; /** Create a ghosted drag image, using the given icon. * @param dragSource source of the drag * @param screenLocation screen location where the drag started * @param icon image to be drawn * @param imageOffset offset of the image from the cursor */ public GhostedDragImage(JComponent dragSource, Point screenLocation, Icon icon, Point imageOffset) { this(dragSource, dragSource.getRootPane().getLayeredPane(), screenLocation, icon, imageOffset, true); } /** Create a ghosted drag image. * @param dragSource source of the drag * @param root layered pane on which ghosted image is drawn * @param screenLocation initial location of image, in screen coordinates * @param icon icon to use for the ghost image * @param imageOffset offset of the image within the drag source * @param trackFrames if true, creates additional ghosts for all extant * frames which contain a {@link JLayeredPane} where the image can be * painted. */ protected GhostedDragImage(JComponent dragSource, JLayeredPane root, Point screenLocation, Icon icon, Point imageOffset, boolean trackFrames) { super(root); this.dragSource = dragSource; this.icon = icon; this.imageOffset = imageOffset; Point loc = root.getLocationOnScreen(); setLocation(screenLocation.x - loc.x + imageOffset.x, screenLocation.y - loc.y + imageOffset.y); if (trackFrames) { RootPaneContainer rpc = (RootPaneContainer) SwingUtilities.getAncestorOfClass(RootPaneContainer.class, root); Frame[] frames = Frame.getFrames(); //Log.debug("track " + frames.length + " other frames"); for (int i=0;i < frames.length;i++) { Frame frame = frames[i]; if (frame instanceof RootPaneContainer && frame.isShowing() && frame != rpc) { //Log.debug("Track " + frame); JLayeredPane p = ((RootPaneContainer)frame).getLayeredPane(); GhostedDragImage slave = new GhostedDragImage(dragSource, p, screenLocation, icon, imageOffset, false); ghosts.add(slave); slave.move(screenLocation); } } } } /** Set the transparency of the ghosted image. */ public void setAlpha(float alpha) { ghostAlpha = alpha; } /** Ensure the decorator cursor matches the drag cursor, or we get * cursor flicker when autoscrolling. */ public void setCursor(Cursor cursor) { super.setCursor(cursor); for (Iterator i=ghosts.iterator();i.hasNext();) { GhostedDragImage slave = (GhostedDragImage)i.next(); slave.setCursor(cursor); } } /** Make all ghosted images go away. */ public void dispose() { location = origin; super.dispose(); for (Iterator i=ghosts.iterator();i.hasNext();) { GhostedDragImage slave = (GhostedDragImage)i.next(); slave.dispose(); } } /** Sets the location of the decoration within the layered pane. * Keeps track of the location relative to the drag source to * facilitate moving back with {@link #returnToOrigin}. */ private void setLocation(int x, int y) { location = SwingUtilities.convertPoint(getComponent(), x, y, dragSource); if (origin == null) origin = location; setDecorationBounds(x, y, icon.getIconWidth(), icon.getIconHeight()); } // Setting this non-zero provides some buffer around the drag image // which helps avoid cursor flicker as the cursor moves (especially // repomgr, probably due to slow repaint times). private static final int CURSOR_SIZE = 32; /** Adjust the bounds of the painting component to allow some buffer * to avoid cursor flicker when moving. */ protected Rectangle clipDecorationBounds(Rectangle decorated) { decorated.x -= CURSOR_SIZE; decorated.y -= CURSOR_SIZE; decorated.width += CURSOR_SIZE*2; decorated.height += CURSOR_SIZE*2; return super.clipDecorationBounds(decorated); } /** Move the ghosted image to the requested location. * @param screen Where to draw the image, in screen coordinates */ public void move(Point screen) { //Log.debug("Move to " + screen); Point loc = getComponent().getLocationOnScreen(); setLocation(screen.x - loc.x + imageOffset.x, screen.y - loc.y + imageOffset.y); for (Iterator i=ghosts.iterator();i.hasNext();) { GhostedDragImage g = (GhostedDragImage)i.next(); //Log.debug("Move on other frame"); g.move(screen); } } /** Paint the supplied image with transparency. */ public void paint(Graphics graphics) { Rectangle r = getDecorationBounds(); Graphics2D g = (Graphics2D)graphics.create(); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ghostAlpha)); g.translate(CURSOR_SIZE, CURSOR_SIZE); icon.paintIcon(getPainter(), g, r.x, r.y); g.dispose(); } private static final int SLIDE_INTERVAL = 1000/24; /** Animate the ghosted image returning to its origin. */ public void returnToOrigin() { setCursor(null); final Timer timer = new Timer(SLIDE_INTERVAL, null); timer.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int dx = (origin.x - location.x)/2; int dy = (origin.y - location.y)/2; if (dx != 0 || dy != 0) { Point loc = dragSource.getLocationOnScreen(); Point where = new Point(loc.x + location.x + dx - imageOffset.x, loc.y + location.y + dy - imageOffset.y); move(where); } else { timer.stop(); dispose(); } } }); timer.start(); } }