/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package de.cismet.cismap.commons.gui.layerwidget.test;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
/**
* Demonstrates how to display a 'drag image' when using drag and drop on those platforms whose JVMs do not support it
* natively (eg Win32).
*
* @version $Revision$, $Date$
*/
public class CTree extends JTree implements DragSourceListener, DragGestureListener, Autoscroll, TreeModelListener {
//~ Static fields/initializers ---------------------------------------------
// Fields...
// Autoscroll Interface...
// The following code was borrowed from the book:
// Java Swing
// By Robert Eckstein, Marc Loy & Dave Wood
// Paperback - 1221 pages 1 Ed edition (September 1998)
// O'Reilly & Associates; ISBN: 156592455X
//
// The relevant chapter of which can be found at:
// http://www.oreilly.com/catalog/jswing/chapter/dnd.beta.pdf
private static final int AUTOSCROLL_MARGIN = 12;
//~ Instance fields --------------------------------------------------------
private TreePath _pathSource; // The path being dragged
private BufferedImage _imgGhost; // The 'drag image'
private Point _ptOffset = new Point(); // Where, in the drag image, the mouse was clicked
//~ Constructors -----------------------------------------------------------
/**
* Constructors...
*/
public CTree() // Use the default JTree constructor so that we get a sample
// TreeModel built for us
{
putClientProperty("JTree.lineStyle", "Angled"); // I like this look//NOI18N
// Make this JTree a drag source
final DragSource dragSource = DragSource.getDefaultDragSource();
dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, this);
// Also, make this JTree a drag target
final DropTarget dropTarget = new DropTarget(this, new CDropTargetListener());
dropTarget.setDefaultActions(DnDConstants.ACTION_COPY_OR_MOVE);
}
//~ Methods ----------------------------------------------------------------
/**
* Test harness...
*
* @param argv DOCUMENT ME!
*/
public static void main(final String[] argv) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
}
final CTree tree = new CTree();
tree.setPreferredSize(new Dimension(300, 300));
final JScrollPane scrollPane = new JScrollPane(tree);
final JFrame frame = new JFrame(org.openide.util.NbBundle.getMessage(
CTree.class,
"CTree.main(String[]).frame.title")); // NOI18N
frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
frame.pack();
final Dimension dimScreen = Toolkit.getDefaultToolkit().getScreenSize();
final Dimension dimFrame = frame.getSize();
frame.setLocation((dimScreen.width - dimFrame.width) / 2,
(dimScreen.height - dimFrame.height)
/ 2);
frame.addWindowListener(
new WindowAdapter() {
@Override
public void windowClosing(final WindowEvent e) {
System.exit(0);
}
});
frame.show();
}
// Interface: DragGestureListener
@Override
public void dragGestureRecognized(final DragGestureEvent e) {
final Point ptDragOrigin = e.getDragOrigin();
final TreePath path = getPathForLocation(ptDragOrigin.x, ptDragOrigin.y);
if (path == null) {
return;
}
if (isRootPath(path)) {
return; // Ignore user trying to drag the root node
}
// Work out the offset of the drag point from the TreePath bounding rectangle origin
final Rectangle raPath = getPathBounds(path);
_ptOffset.setLocation(ptDragOrigin.x - raPath.x, ptDragOrigin.y - raPath.y);
// Get the cell renderer (which is a JLabel) for the path being dragged
final JLabel lbl = (JLabel)getCellRenderer().getTreeCellRendererComponent(
this, // tree
path.getLastPathComponent(), // value
false, // isSelected (dont want a colored background)
isExpanded(path), // isExpanded
getModel().isLeaf(path.getLastPathComponent()), // isLeaf
0, // row (not important for rendering)
false // hasFocus (dont want a focus rectangle)
);
lbl.setSize((int)raPath.getWidth(), (int)raPath.getHeight()); // <-- The layout manager would normally do this
// Get a buffered image of the selection for dragging a ghost image
_imgGhost = new BufferedImage((int)raPath.getWidth(), (int)raPath.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE);
final Graphics2D g2 = _imgGhost.createGraphics();
// Ask the cell renderer to paint itself into the BufferedImage
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.5f)); // Make the image ghostlike
lbl.paint(g2);
// Now paint a gradient UNDER the ghosted JLabel text (but not under the icon if any)
// Note: this will need tweaking if your icon is not positioned to the left of the text
final Icon icon = lbl.getIcon();
final int nStartOfText = (icon == null) ? 0 : (icon.getIconWidth() + lbl.getIconTextGap());
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OVER, 0.5f)); // Make the gradient ghostlike
g2.setPaint(new GradientPaint(
nStartOfText,
0,
SystemColor.controlShadow,
getWidth(),
0,
new Color(255, 255, 255, 0)));
g2.fillRect(nStartOfText, 0, getWidth(), _imgGhost.getHeight());
g2.dispose();
setSelectionPath(path); // Select this path in the tree
System.out.println("DRAGGING: " + path.getLastPathComponent()); // NOI18N
// Wrap the path being transferred into a Transferable object
final Transferable transferable = new CTransferableTreePath(path);
// Remember the path being dragged (because if it is being moved, we will have to delete it later)
_pathSource = path;
// We pass our drag image just in case it IS supported by the platform
e.startDrag(null, _imgGhost, new Point(5, 5), transferable, this);
}
// Interface: DragSourceListener
@Override
public void dragEnter(final DragSourceDragEvent e) {
}
@Override
public void dragOver(final DragSourceDragEvent e) {
}
@Override
public void dragExit(final DragSourceEvent e) {
}
@Override
public void dropActionChanged(final DragSourceDragEvent e) {
}
@Override
public void dragDropEnd(final DragSourceDropEvent e) {
if (e.getDropSuccess()) {
final int nAction = e.getDropAction();
if (nAction == DnDConstants.ACTION_MOVE) { // The dragged item (_pathSource) has been inserted at the
// target selected by the user.
// Now it is time to delete it from its original location.
System.out.println("REMOVING: " + _pathSource.getLastPathComponent()); // NOI18N
// .
// .. ask your TreeModel to delete the node
// .
_pathSource = null;
}
}
}
// Ok, we've been told to scroll because the mouse cursor is in our
// scroll zone.
@Override
public void autoscroll(final Point pt) {
// Figure out which row we're on.
int nRow = getRowForLocation(pt.x, pt.y);
// If we are not on a row then ignore this autoscroll request
if (nRow < 0) {
return;
}
final Rectangle raOuter = getBounds();
// Now decide if the row is at the top of the screen or at the
// bottom. We do this to make the previous row (or the next
// row) visible as appropriate. If we're at the absolute top or
// bottom, just return the first or last row respectively.
nRow = ((pt.y + raOuter.y) <= AUTOSCROLL_MARGIN) // Is row at top of screen?
? ((nRow <= 0) ? 0 : (nRow - 1)) // Yes, scroll up one row
: ((nRow < (getRowCount() - 1)) ? (nRow + 1) : nRow); // No, scroll down one row
scrollRowToVisible(nRow);
}
// Calculate the insets for the *JTREE*, not the viewport
// the tree is in. This makes it a bit messy.
@Override
public Insets getAutoscrollInsets() {
final Rectangle raOuter = getBounds();
final Rectangle raInner = getParent().getBounds();
return new Insets(
raInner.y
- raOuter.y
+ AUTOSCROLL_MARGIN,
raInner.x
- raOuter.x
+ AUTOSCROLL_MARGIN,
raOuter.height
- raInner.height
- raInner.y
+ raOuter.y
+ AUTOSCROLL_MARGIN,
raOuter.width
- raInner.width
- raInner.x
+ raOuter.x
+ AUTOSCROLL_MARGIN);
}
/*
// Use this method if you want to see the boundaries of the
// autoscroll active region. Toss it out, otherwise.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Rectangle raOuter = getBounds();
Rectangle raInner = getParent().getBounds();
g.setColor(Color.red);
g.drawRect(-raOuter.x + 12, -raOuter.y + 12,
raInner.width - 24, raInner.height - 24);
}
*/
// TreeModelListener interface...
@Override
public void treeNodesChanged(final TreeModelEvent e) {
System.out.println("treeNodesChanged"); // NOI18N
sayWhat(e);
// We dont need to reset the selection path, since it has not moved
}
@Override
public void treeNodesInserted(final TreeModelEvent e) {
System.out.println("treeNodesInserted "); // NOI18N
sayWhat(e);
// We need to reset the selection path to the node just inserted
final int nChildIndex = e.getChildIndices()[0];
final TreePath pathParent = e.getTreePath();
setSelectionPath(getChildPath(pathParent, nChildIndex));
}
@Override
public void treeNodesRemoved(final TreeModelEvent e) {
System.out.println("treeNodesRemoved "); // NOI18N
sayWhat(e);
}
@Override
public void treeStructureChanged(final TreeModelEvent e) {
System.out.println("treeStructureChanged "); // NOI18N
sayWhat(e);
}
/**
* More helpers...
*
* @param pathParent DOCUMENT ME!
* @param nChildIndex DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private TreePath getChildPath(final TreePath pathParent, final int nChildIndex) {
final TreeModel model = getModel();
return pathParent.pathByAddingChild(model.getChild(pathParent.getLastPathComponent(), nChildIndex));
}
/**
* DOCUMENT ME!
*
* @param path DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
private boolean isRootPath(final TreePath path) {
return isRootVisible() && (getRowForPath(path) == 0);
}
/**
* DOCUMENT ME!
*
* @param e DOCUMENT ME!
*/
private void sayWhat(final TreeModelEvent e) {
System.out.println(e.getTreePath().getLastPathComponent());
final int[] nIndex = e.getChildIndices();
for (int i = 0; i < nIndex.length; i++) {
System.out.println(i + ". " + nIndex[i]); // NOI18N
}
}
//~ Inner Classes ----------------------------------------------------------
/**
* DropTargetListener interface object...
*
* @version $Revision$, $Date$
*/
class CDropTargetListener implements DropTargetListener {
//~ Instance fields ----------------------------------------------------
// Fields...
private TreePath _pathLast = null;
private Rectangle2D _raCueLine = new Rectangle2D.Float();
private Rectangle2D _raGhost = new Rectangle2D.Float();
private Color _colorCueLine;
private Point _ptLast = new Point();
private Timer _timerHover;
private int _nLeftRight = 0; // Cumulative left/right mouse movement
private BufferedImage _imgRight = new CArrowImage(15, 15, CArrowImage.ARROW_RIGHT);
private BufferedImage _imgLeft = new CArrowImage(15, 15, CArrowImage.ARROW_LEFT);
private int _nShift = 0;
//~ Constructors -------------------------------------------------------
/**
* Constructor...
*/
public CDropTargetListener() {
_colorCueLine = new Color(
SystemColor.controlShadow.getRed(),
SystemColor.controlShadow.getGreen(),
SystemColor.controlShadow.getBlue(),
64);
// Set up a hover timer, so that a node will be automatically expanded or collapsed
// if the user lingers on it for more than a short time
_timerHover = new Timer(1000, new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
_nLeftRight = 0; // Reset left/right movement trend
if (isRootPath(_pathLast)) {
return; // Do nothing if we are hovering over the root node
}
if (isExpanded(_pathLast)) {
collapsePath(_pathLast);
} else {
expandPath(_pathLast);
}
}
});
_timerHover.setRepeats(false); // Set timer to one-shot mode
}
//~ Methods ------------------------------------------------------------
// DropTargetListener interface
@Override
public void dragEnter(final DropTargetDragEvent e) {
if (!isDragAcceptable(e)) {
e.rejectDrag();
} else {
e.acceptDrag(e.getDropAction());
}
}
@Override
public void dragExit(final DropTargetEvent e) {
if (!DragSource.isDragImageSupported()) {
repaint(_raGhost.getBounds());
}
}
/**
* This is where the ghost image is drawn.
*
* @param e DOCUMENT ME!
*/
@Override
public void dragOver(final DropTargetDragEvent e) {
// Even if the mouse is not moving, this method is still invoked 10 times per second
final Point pt = e.getLocation();
if (pt.equals(_ptLast)) {
return;
}
// Try to determine whether the user is flicking the cursor right or left
final int nDeltaLeftRight = pt.x - _ptLast.x;
if (((_nLeftRight > 0) && (nDeltaLeftRight < 0)) || ((_nLeftRight < 0) && (nDeltaLeftRight > 0))) {
_nLeftRight = 0;
}
_nLeftRight += nDeltaLeftRight;
_ptLast = pt;
final Graphics2D g2 = (Graphics2D)getGraphics();
// If a drag image is not supported by the platform, then draw my own drag image
if (!DragSource.isDragImageSupported()) {
paintImmediately(_raGhost.getBounds()); // Rub out the last ghost image and cue line
// And remember where we are about to draw the new ghost image
_raGhost.setRect(pt.x - _ptOffset.x, pt.y - _ptOffset.y, _imgGhost.getWidth(), _imgGhost.getHeight());
g2.drawImage(_imgGhost, AffineTransform.getTranslateInstance(_raGhost.getX(), _raGhost.getY()), null);
} else { // Just rub out the last cue line
paintImmediately(_raCueLine.getBounds());
}
final TreePath path = getClosestPathForLocation(pt.x, pt.y);
if (!(path == _pathLast)) {
_nLeftRight = 0; // We've moved up or down, so reset left/right movement trend
_pathLast = path;
_timerHover.restart();
}
// In any case draw (over the ghost image if necessary) a cue line indicating where a drop will occur
final Rectangle raPath = getPathBounds(path);
_raCueLine.setRect(0, raPath.y + (int)raPath.getHeight(), getWidth(), 2);
g2.setColor(_colorCueLine);
g2.fill(_raCueLine);
// Now superimpose the left/right movement indicator if necessary
if (_nLeftRight > 20) {
g2.drawImage(
_imgRight,
AffineTransform.getTranslateInstance(pt.x - _ptOffset.x, pt.y - _ptOffset.y),
null);
_nShift = +1;
} else if (_nLeftRight < -20) {
g2.drawImage(
_imgLeft,
AffineTransform.getTranslateInstance(pt.x - _ptOffset.x, pt.y - _ptOffset.y),
null);
_nShift = -1;
} else {
_nShift = 0;
}
// And include the cue line in the area to be rubbed out next time
_raGhost = _raGhost.createUnion(_raCueLine);
/*
// Do this if you want to prohibit dropping onto the drag source
if (path.equals(_pathSource))
e.rejectDrag();
else
e.acceptDrag(e.getDropAction());
*/
}
@Override
public void dropActionChanged(final DropTargetDragEvent e) {
if (!isDragAcceptable(e)) {
e.rejectDrag();
} else {
e.acceptDrag(e.getDropAction());
}
}
@Override
public void drop(final DropTargetDropEvent e) {
_timerHover.stop(); // Prevent hover timer from doing an unwanted expandPath or collapsePath
if (!isDropAcceptable(e)) {
e.rejectDrop();
return;
}
e.acceptDrop(e.getDropAction());
final Transferable transferable = e.getTransferable();
final DataFlavor[] flavors = transferable.getTransferDataFlavors();
for (int i = 0; i < flavors.length; i++) {
final DataFlavor flavor = flavors[i];
if (flavor.isMimeTypeEqual(DataFlavor.javaJVMLocalObjectMimeType)) {
try {
final Point pt = e.getLocation();
final TreePath pathTarget = getClosestPathForLocation(pt.x, pt.y);
final TreePath pathSource = (TreePath)transferable.getTransferData(flavor);
System.out.println("DROPPING: " + pathSource.getLastPathComponent()); // NOI18N
final TreeModel model = getModel();
final TreePath pathNewChild = null;
// . .. Add your code here to ask your TreeModel to copy the node and act on the mouse
// gestures... .
// For example:
// If pathTarget is an expanded BRANCH, then insert source UNDER it (before the first child if
// any) If pathTarget is a collapsed BRANCH (or a LEAF), then insert source AFTER it Note: a
// leaf node is always marked as collapsed You ask the model to do the copying... ...and you
// supply the copyNode method in the model as well of course. if (_nShift == 0) pathNewChild =
// model.copyNode(pathSource, pathTarget, isExpanded(pathTarget)); else if (_nShift > 0) //
// The mouse is being flicked to the right (so move the node right) pathNewChild =
// model.copyNodeRight(pathSource, pathTarget); else // The
// mouse is being flicked to the left (so move the node left) pathNewChild =
// model.copyNodeLeft(pathSource);
if (pathNewChild != null) {
setSelectionPath(pathNewChild); // Mark this as the selected path in the tree
}
break; // No need to check remaining flavors
} catch (UnsupportedFlavorException ufe) {
System.out.println(ufe);
e.dropComplete(false);
return;
} catch (IOException ioe) {
System.out.println(ioe);
e.dropComplete(false);
return;
}
}
}
e.dropComplete(true);
}
/**
* Helpers...
*
* @param e DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public boolean isDragAcceptable(final DropTargetDragEvent e) {
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if ((e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return false;
}
// Only accept this particular flavor
if (!e.isDataFlavorSupported(CTransferableTreePath.TREEPATH_FLAVOR)) {
return false;
}
/*
// Do this if you want to prohibit dropping onto the drag source...
Point pt = e.getLocation();
TreePath path = getClosestPathForLocation(pt.x, pt.y);
if (path.equals(_pathSource))
return false;
*/
/*
// Do this if you want to select the best flavor on offer...
DataFlavor[] flavors = e.getCurrentDataFlavors();
for (int i = 0; i < flavors.length; i++ )
{
DataFlavor flavor = flavors[i];
if (flavor.isMimeTypeEqual(DataFlavor.javaJVMLocalObjectMimeType))
return true;
}
*/
return true;
}
/**
* DOCUMENT ME!
*
* @param e DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public boolean isDropAcceptable(final DropTargetDropEvent e) {
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if ((e.getDropAction() & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return false;
}
// Only accept this particular flavor
if (!e.isDataFlavorSupported(CTransferableTreePath.TREEPATH_FLAVOR)) {
return false;
}
/*
// Do this if you want to prohibit dropping onto the drag source...
Point pt = e.getLocation();
TreePath path = getClosestPathForLocation(pt.x, pt.y);
if (path.equals(_pathSource))
return false;
*/
/*
// Do this if you want to select the best flavor on offer...
DataFlavor[] flavors = e.getCurrentDataFlavors();
for (int i = 0; i < flavors.length; i++ )
{
DataFlavor flavor = flavors[i];
if (flavor.isMimeTypeEqual(DataFlavor.javaJVMLocalObjectMimeType))
return true;
}
*/
return true;
}
}
}