/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "Ant" and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant.gui.modules.edit;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
/**
* A tree which allows reorganization via drop and drag
*
* @version $Revision: 1.5 $
* @author Nick Davis<a href="mailto:nick_home_account@yahoo.com">nick_home_account@yahoo.com</a>
*/
public class DragTree extends JTree implements DragSourceListener,
DragGestureListener, DropTargetListener {
/**
* The <code>DragTreeListener</code>
* associated with this <code>DragTree</code>.
*/
private DragTreeListener _dragTreeListener;
/**
* Holds the position where the dropped item should be
* placed. Possible values are DROP_BEFORE, DROP_ON
* or DROP_AFTER.
*/
private int _dropPosition;
/**
* The point where the drop line should be drawn.
*/
private Point _point;
/**
* The object the drop occured on.
*/
private Object _dropOn;
/**
* The path of where the drop occured.
*/
private TreePath _dropOnPath;
/**
* The path of the item being dropped.
*/
private TreePath _droppedPath;
/**
* The item being dragged should be placed before (or above)
* the item it is dropped on.
*/
final static protected int DROP_BEFORE = 0;
/**
* The item being dragged should be placed on (as a child)
* the item it is dropped on.
*/
final static protected int DROP_ON = 1;
/**
* The item being dragged should be placed on (or after)
* the item it is dropped on.
*/
final static protected int DROP_AFTER = 2;
/**
* Default Constuctor
*/
public DragTree() {
DragSource dragSource = DragSource.getDefaultDragSource();
// Use the default gesture recognizer
dragSource.createDefaultDragGestureRecognizer(
this,
DnDConstants.ACTION_COPY_OR_MOVE,
this);
// Setup to be a drop target
new DropTarget(this,
DnDConstants.ACTION_COPY_OR_MOVE,
this);
}
/**
* Starts the drag operation.
* <P>
* @param e the <code>DragGestureEvent</code> describing
* the gesture that has just occurred
*/
public void dragGestureRecognized(DragGestureEvent e) {
// Find the path for the cursor position.
Point p = e.getDragOrigin();
_droppedPath = getPathForLocation(p.x, p.y);
if (_droppedPath == null) {
return;
}
// Select the item.
setSelectionPath(_droppedPath);
// Wrap the object and start the drag.
Object obj = _droppedPath.getLastPathComponent();
Wrapper wrapper = new Wrapper(obj);
e.startDrag(DragSource.DefaultMoveNoDrop, wrapper, this);
}
//
// DragSourceListener methods
//
public void dragDropEnd(DragSourceDropEvent e) {}
public void dragEnter(DragSourceDragEvent e) {}
public void dragExit(DragSourceEvent e) {}
public void dragOver(DragSourceDragEvent e) {}
public void dropActionChanged(DragSourceDragEvent e) {}
//
// DropTargetListener methods
//
public void dropActionChanged(java.awt.dnd.DropTargetDragEvent e) {}
public void dragEnter(DropTargetDragEvent e) {
dragOver(e);
}
/**
* Called when a drag operation is ongoing
* on the <code>DropTarget</code>.
* <P>
* @param e the <code>DropTargetDragEvent</code>
*/
public void dragOver(DropTargetDragEvent e) {
checkAutoScroll(e.getLocation());
Point p = computeDropLocation(e.getLocation());
// Don't allow a parent to be dropped on one of its children.
if (_droppedPath.isDescendant(_dropOnPath)) {
_point = null;
_dropOn = null;
p = null;
e.rejectDrag();
} else {
e.acceptDrag(e.getDropAction());
}
// If the point has changed, repaint the display.
if (_point == null || !p.equals(_point)) {
_point = p;
repaint();
}
}
/**
* Determines where the item will be dropped.
*/
private Point computeDropLocation(Point p) {
int rowCount = getRowCount();
int height = findCellHeight();
int row = (p.y / height);
int offset = (p.y % height);
// Move the point to the top of the cell.
p.y -= offset;
int delta = 0;
// Is the point at or past the end of the list?
if (row > (rowCount - 1) ) {
p.y = (rowCount - 1) * height;
row = rowCount - 1;
delta = height;
_dropPosition = DROP_AFTER;
}
// Is the point at the begining of the list?
else if (row <= 0) {
p.y = 0;
delta = height;
if (rowCount > 1) {
row = 1;
_dropPosition = DROP_BEFORE;
} else {
row = 0;
_dropPosition = DROP_ON;
}
}
// The point is in the middle of the tree.
else {
// Is the point on the top third of the cell?
if (offset < height * 0.333) {
// Set the line to the top of the cell.
delta = 0;
_dropPosition = DROP_BEFORE;
}
// Is the point on the bottom third of the cell?
else if (offset > height * 0.666){
// Set the line to the bottom of the cell.
delta = height;
_dropPosition = DROP_AFTER;
}
// The point is in the middle of the cell?
else {
// Set the line to the middle of the cell.
delta = height / 2;
_dropPosition = DROP_ON;
}
}
// Find the object to use for the drop.
_dropOnPath = getPathForRow(row);
if (_dropOnPath != null) {
_dropOn = _dropOnPath.getLastPathComponent();
} else {
_dropOn = null;
}
// Adjust the point used to draw the drop line.
p.y += delta;
p.x = 0;
return p;
}
/**
* Process the drop
*
* @param e the <code>DropTargetDropEvent</code>
* @see DropTargetListener#drop
*/
public void drop(DropTargetDropEvent e) {
if (_dropOn == null || _dropOnPath == null) {
return;
}
// Get the object being transfered.
Object obj = null;
Transferable t = e.getTransferable();
try {
obj = t.getTransferData(_flavors[0]);
} catch (Exception exp) {
System.out.println(exp);
}
if (obj != null) {
Object droppedObj = obj;
Object droppedOnObj = _dropOn;
Object parentObj = getParentOfDroppedOnObject();
if (_dropPosition == DROP_ON) {
fireAppendChild(droppedOnObj, droppedObj);
setExpandedState(_dropOnPath, true);
}
else if (_dropPosition == DROP_BEFORE) {
fireInsertBefore(parentObj, droppedOnObj, droppedObj);
}
else if (_dropPosition == DROP_AFTER) {
// If the cell is exanded, add the new item before our
// first child.
if (isExpanded(getRowForPath(_dropOnPath))) {
parentObj = droppedOnObj;
}
// Find the next sibling
int index = getModel().getIndexOfChild(parentObj, droppedOnObj);
int count = getModel().getChildCount(parentObj);
if (index == count-1) {
fireAppendChild(parentObj, droppedObj);
} else {
Object sibling = getModel().getChild(parentObj, index+1);
fireInsertBefore(parentObj, sibling, droppedObj);
}
}
if (e.getDropAction() == DnDConstants.ACTION_MOVE) {
fireRemoveChild(droppedObj);
}
this.updateUI();
}
}
/**
* Returns the parent of the dropped on object.
*/
private Object getParentOfDroppedOnObject() {
int count = _dropOnPath.getPathCount();
return _dropOnPath.getPathComponent(count-2);
}
/**
* Repaint the display to cleanup any lines.
*/
public void dragExit(DropTargetEvent e) {
_point = null;
repaint();
}
/**
* Draws the line which shows where the dropped
* item will land.
*
* @see JComponent#paintComponent
*/
public void paintComponent(Graphics g){
super.paintComponent(g);
if (_point != null) {
if (_dropPosition == DROP_ON) {
// If the drop is on another item, draw
// two short lines.
g.drawLine(0, _point.y, 10, _point.y);
g.drawLine(getWidth()-10, _point.y, getWidth(), _point.y);
} else {
// If the drop is above or below an item, draw
// one long line.
g.drawLine(0, _point.y, getWidth(), _point.y);
}
}
}
/**
* Wrapper holds the object to transfer
*/
protected class Wrapper implements Transferable {
/** The object to transfer */
private Object _obj;
/**
* Creates a wrapper for the input object.
*
* @param obj object to wrap
*/
public Wrapper(Object obj) {
_obj = obj;
}
/**
* Return out object if the DataFlavor is correct.
*
* @param flavor only javaJVMLocalObjectMimeType is supported
*/
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException {
if (isDataFlavorSupported(flavor)) {
return _obj;
}
throw new UnsupportedFlavorException(flavor);
}
/**
* Return true if the input flavor is support.
*
* @param flavor DataFlavor to test
*/
public boolean isDataFlavorSupported(DataFlavor flavor) {
return flavor.equals(_flavors[0]);
}
/**
* Return true if the input flavor is support.
*/
public DataFlavor[] getTransferDataFlavors() {
return _flavors;
}
}
/**
* DataFlavors which are support for transfer
*/
private static final DataFlavor[] _flavors = {
createConstant(DataFlavor.javaJVMLocalObjectMimeType)
};
/**
* Returns a new DataFlavor or null
*
* @param flavor the flavor
*/
static private DataFlavor createConstant(String flavor) {
try {
return new DataFlavor(flavor);
} catch (Exception e) {
return null;
}
}
/**
* Returns the cell height for the tree.
*/
protected int findCellHeight() {
DefaultTreeCellRenderer renderer =
(DefaultTreeCellRenderer) getCellRenderer();
return renderer.getPreferredSize().height;
}
/**
* Register a new <code>DragTreeListener</code>.
* <P>
* @param dtl the <code>DragTreeListener</code> to register
* with this <code>DragTree</code>.
*/
public synchronized void addDragTreeListener(DragTreeListener dtl) {
_dragTreeListener = dtl;
}
/**
* Unregister the current DragTreeListener.
*
* @param dtl the <code>DragTreeListener</code> to unregister
* <P>
* @throws IllegalArgumentException if
* dtl is not (equal to) the currently registered
* <code>DragTreeListener</code>.
*/
public synchronized void removeDragTreeListener(DragTreeListener dtl) {
if (_dragTreeListener == null || !_dragTreeListener.equals(dtl))
throw new IllegalArgumentException();
else {
_dragTreeListener = null;
}
}
/**
* Notify the DragTreeListener that an <code>appendChild</code> has
* been requested.
*/
protected synchronized Object fireAppendChild(Object parent,
Object newChild) {
if (_dragTreeListener != null) {
return _dragTreeListener.appendChild(parent, newChild);
}
return null;
}
/**
* Notify the DragTreeListener that an <code>insertBefore</code> has
* been requested.
*/
protected synchronized Object fireInsertBefore(Object parent, Object index,
Object newChild) {
if (_dragTreeListener != null) {
return _dragTreeListener.insertBefore(parent, index, newChild);
}
return null;
}
/**
* Notify the DragTreeListener that an <code>removeChild</code> has
* been requested.
*/
protected synchronized void fireRemoveChild(Object child) {
if (_dragTreeListener != null) {
_dragTreeListener.removeChild(child);
}
}
/**
* The method checkAutoScroll scrolls the tree based upon the Point
* of the cursor which triggered the scroll operation.
* <P>
* @param p A <code>Point</code> indicating the
* location of the cursor that triggered this operation.
*/
protected void checkAutoScroll(Point p) {
Point locn = new Point(p);
javax.swing.SwingUtilities.convertPointToScreen(locn, this);
javax.swing.SwingUtilities.convertPointFromScreen(locn, getParent());
Rectangle outer = new Rectangle();
Rectangle inner = new Rectangle();
Insets i = new java.awt.Insets(10, 10, 10, 10);
Dimension size = getParent().getSize();
if (size.width != outer.width || size.height != outer.height)
outer.setBounds(0, 0, size.width, size.height);
if (inner.x != i.left || inner.y != i.top)
inner.setLocation(i.left, i.top);
int newWidth = size.width - (i.left + i.right);
int newHeight = size.height - (i.top + i.bottom);
if (newWidth != inner.width || newHeight != inner.height)
inner.setSize(newWidth, newHeight);
if (outer.contains(locn) && !inner.contains(locn)) {
if (locn.y >= inner.height) {
scrollDown();
} else {
scrollUp();
}
}
}
/**
* Scroll the tree up one cell
*/
public void scrollUp() {
Rectangle visibleRect = getVisibleRect();
int height = findCellHeight();
visibleRect.y -= height;
visibleRect.height -= height;
scrollRectToVisible(visibleRect);
}
/**
* Scroll the tree down one cell
*/
public void scrollDown() {
Rectangle visibleRect = getVisibleRect();
int height = findCellHeight();
visibleRect.y += height;
visibleRect.height += height;
scrollRectToVisible(visibleRect);
}
}