/* 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.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.Point;
import java.awt.Graphics;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.plaf.metal.MetalIconFactory;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
public class FancyDrop {
private static class ZoomRect extends AbstractComponentDecorator implements ActionListener {
private Timer timer;
private int alpha;
private volatile boolean zoom;
private Rectangle targetBounds;
final int MPY = 4;
final int INTERVAL = 1000/12;
public ZoomRect(JComponent target) {
super(target);
timer = new Timer(INTERVAL, this);
timer.start();
}
private void reset(Rectangle target) {
this.targetBounds = target;
alpha = 0;
int x = target.x - target.width*(MPY-1)/2;
int y = target.y - target.height*(MPY-1)/2;
super.setDecorationBounds(new Rectangle(x, y, target.width*MPY, target.height*MPY));
}
public void setDecorationBounds(Rectangle targetBounds) {
if (!targetBounds.equals(this.targetBounds)) {
reset(targetBounds);
}
}
final int SPEED = 2;
private void zoom() {
alpha += (255 - alpha) / SPEED;
Rectangle r1 = getDecorationBounds();
Rectangle r2 = targetBounds;
int dw = (r2.width - r1.width) / SPEED;
int dh = (r2.height - r1.height) / SPEED;
r1.x += (r2.x - r1.x) / SPEED;
r1.y += (r2.y - r1.y) / SPEED;
r1.width += dw;
r1.height += dh;
super.setDecorationBounds(r1);
}
public void paint(Graphics g) {
if (zoom) {
zoom();
zoom = false;
}
g = g.create();
Color tgt = UIManager.getColor("Table.selectionBackground");
g.setColor(new Color(tgt.getRed(), tgt.getGreen(), tgt.getBlue(), alpha));
((Graphics2D)g).setStroke(new BasicStroke(8f));
Rectangle r = getDecorationBounds();
g.drawRect(r.x, r.y, r.width-1, r.height-1);
g.dispose();
}
public void dispose() {
super.dispose();
timer.stop();
}
public void actionPerformed(ActionEvent e) {
zoom = true;
repaint();
}
}
/** Provide a marquee decoration. */
private static class Marquee extends AbstractComponentDecorator implements ActionListener {
private Timer timer;
private float phase;
final int LINE_WIDTH = 4;
final int INTERVAL = 1000/24;
final int SIZE = 8;
public Marquee(JComponent target) {
super(target);
timer = new Timer(INTERVAL, this);
timer.start();
}
public void setDecorationBounds(int x, int y, int w, int h) {
setDecorationBounds(new Rectangle(x, y, w, h));
}
public void setDecorationBounds(Rectangle r) {
r.y -= LINE_WIDTH/2;
r.height += LINE_WIDTH;
if (r.height == LINE_WIDTH)
r.height = LINE_WIDTH*2;
r.x -= LINE_WIDTH/2;
r.width += LINE_WIDTH;
super.setDecorationBounds(r);
}
private void light(Graphics g, Color color, int x, int y, int size) {
g.setColor(color.darker());
g.fillArc(x, y, size, size, 0, 360);
g.setColor(color);
g.fillArc(x+size/8, y+size/8, size/2, size/2, 0, 360);
g.setColor(color.brighter());
g.fillArc(x+size/8, y+size/4, size/4, size/4, 0, 360);
}
public void paint(Graphics g) {
Rectangle r = getDecorationBounds();
g.setColor(UIManager.getColor("Tree.selectionBackground"));
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int w = LINE_WIDTH/2;
int dw = LINE_WIDTH-w;
((Graphics2D)g).setStroke(new BasicStroke(LINE_WIDTH, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND, 10.0f,
new float[]{10f, 2f}, phase));
g.drawRect(r.x+w, r.y+w, r.width-2-dw, r.height-2-dw);
}
public void actionPerformed(ActionEvent e) {
phase += 2f;
getPainter().repaint();
}
public void dispose() {
timer.stop();
timer = null;
super.dispose();
}
}
public static void main(String[] args) {
JFrame dragFrame = new JFrame("Fancy Drops");
JPanel p = (JPanel)dragFrame.getContentPane();
final JLabel label = new JLabel("Drag Me");
Font font = label.getFont();
label.setForeground(Color.green.darker());
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setFont(font.deriveFont(Font.BOLD, font.getSize()*2));
label.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
new DragHandler(label, DnDConstants.ACTION_COPY) {
protected Transferable getTransferable(DragGestureEvent e) {
return new StringSelection(label.getText());
}
protected Icon getDragIcon(DragGestureEvent e, Point offset) {
return new ComponentIcon(label, true);
}
};
DataFlavor[] acceptableFlavors = {
DataFlavor.stringFlavor,
};
final JList list = new JList();
list.setFont(label.getFont());
new DropHandler(list, DnDConstants.ACTION_COPY, acceptableFlavors) {
private AbstractComponentDecorator dropArea;
/** Always drop at the end of the list. */
protected void drop(DropTargetDropEvent e, int action) throws UnsupportedFlavorException, IOException {
final List data = new ArrayList();
for (int i=0;i < list.getModel().getSize();i++) {
data.add(list.getModel().getElementAt(i));
}
data.add(e.getTransferable().getTransferData(DataFlavor.stringFlavor));
list.setModel(new AbstractListModel() {
public int getSize() {
return data.size();
}
public Object getElementAt(int index) {
return data.get(index);
}
});
}
protected void paintDropTarget(DropTargetEvent e, int action, Point location) {
if (action != DnDConstants.ACTION_NONE && location != null) {
if (dropArea == null) {
dropArea = new Marquee(list);
}
int count = list.getModel().getSize();
if (count == 0) {
Dimension size = list.getSize();
dropArea.setDecorationBounds(new Rectangle(0, 0, size.width, size.height));
}
else {
Rectangle r = list.getCellBounds(count-1, count-1);
r.y += r.height;
dropArea.setDecorationBounds(r);
}
}
else if (dropArea != null) {
dropArea.dispose();
dropArea = null;
}
}
};
final JTree tree = new JTree();
tree.setFont(label.getFont());
FontMetrics m = label.getFontMetrics(label.getFont());
int h = m.getHeight();
tree.setRowHeight(h);
new DropHandler(tree, DnDConstants.ACTION_COPY, acceptableFlavors) {
final int MARGIN = 4;
private AbstractComponentDecorator dropArea;
class DropLocation {
TreePath parent;
int index;
DropLocation(TreePath p, int idx) {
this.parent = p;
this.index = idx;
}
}
/** Here's the fancy logic that says where the drop should actually
* go.
*/
private DropLocation getDropLocation(Point location) {
TreePath path = tree.getClosestPathForLocation(location.x, location.y);
Object o = path.getLastPathComponent();
Rectangle bounds = tree.getPathBounds(path);
if (Math.abs(bounds.y - location.y) < MARGIN
|| Math.abs(bounds.y + bounds.height - location.y) < MARGIN) {
// Drop between paths
int row = tree.getRowForPath(path);
if (location.y < bounds.y + bounds.height / 2) {
if (row > 0) {
path = tree.getPathForRow(--row);
o = path.getLastPathComponent();
}
else if (!tree.isRootVisible()) {
o = tree.getModel().getRoot();
return new DropLocation(new TreePath(o), 0);
}
}
if (tree.getModel().isLeaf(o)) {
TreePath parentPath = path.getParentPath();
Object parent = parentPath.getLastPathComponent();
int idx = tree.getModel().getIndexOfChild(parent, o) + 1;
return new DropLocation(parentPath, idx);
}
return new DropLocation(path, 0);
}
else if (!tree.getModel().isLeaf(o)) {
int count = tree.getModel().getChildCount(o);
return new DropLocation(path, count);
}
return null;
}
/** Figure out where the insertion marker should be drawn. */
private Rectangle getInsertionBounds(DropLocation loc) {
final int HEIGHT = 4;
Rectangle r = tree.getPathBounds(loc.parent);
if (tree.isExpanded(loc.parent)) {
Object parent = loc.parent.getLastPathComponent();
if (tree.getModel().getChildCount(parent) > loc.index) {
Object child = tree.getModel().getChild(parent, loc.index);
r = tree.getPathBounds(loc.parent.pathByAddingChild(child));
r.height = 0;
}
else {
r.y += r.height;
r.height = 0;
}
}
return r;
}
/** Only allow drops on non-leaf nodes, or between sibling leaf
* nodes.
*/
protected boolean canDrop(DropTargetEvent e, int action, Point location) {
DropLocation loc = getDropLocation(location);
return loc != null;
}
/** Decorate the current drop target, removing the decoration if
* the action is {@link DnDConstants#ACTION_NONE} or if the location
* is <code>null</code>.
*/
protected void paintDropTarget(DropTargetEvent e, int action, Point location) {
DropLocation loc;
if (action != DnDConstants.ACTION_NONE && location != null
&& (loc = getDropLocation(location)) != null) {
if (dropArea == null) {
dropArea = new Marquee(tree);
}
Rectangle r = getInsertionBounds(loc);
dropArea.setDecorationBounds(r);
}
else {
if (dropArea != null) {
dropArea.dispose();
dropArea = null;
}
}
}
/** Handle the actual drop. */
protected void drop(DropTargetDropEvent e, int action) throws UnsupportedFlavorException, IOException {
DropLocation loc = getDropLocation(e.getLocation());
if (loc != null) {
DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
String value = (String)e.getTransferable().getTransferData(DataFlavor.stringFlavor);
DefaultMutableTreeNode child = new DefaultMutableTreeNode(value);
DefaultMutableTreeNode parent = (DefaultMutableTreeNode)
loc.parent.getLastPathComponent();
model.insertNodeInto(child, parent, loc.index);
}
}
};
p.add(label);
JLabel label2 = new JLabel("Drag the label");
label2.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
p.add(label2, BorderLayout.SOUTH);
dragFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
dragFrame.pack();
dragFrame.setSize(200, 300);
dragFrame.setLocation(100, 100);
dragFrame.setVisible(true);
JFrame dropFrame = new JFrame("Drop Over Here");
dropFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
dropFrame.getContentPane().add(new JScrollPane(list));
JLabel label3 = new JLabel("Onto the list");
label3.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
dropFrame.getContentPane().add(label3, BorderLayout.SOUTH);
dropFrame.pack();
dropFrame.setSize(200, 300);
dropFrame.setLocation(300, 100);
dropFrame.setVisible(true);
JFrame dropFrame2 = new JFrame("Drop Over Here");
dropFrame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
dropFrame2.getContentPane().add(new JScrollPane(tree));
JLabel label4 = new JLabel("Or onto the tree");
label4.setBorder(BorderFactory.createEmptyBorder(4,4,4,4));
dropFrame2.getContentPane().add(label4, BorderLayout.SOUTH);
dropFrame2.pack();
dropFrame2.setSize(200, 300);
dropFrame2.setLocation(500, 100);
dropFrame2.setVisible(true);
}
}