/* $Id: DnDExplorerTree.java 17843 2010-01-12 19:23:29Z linus $
*****************************************************************************
* Copyright (c) 2009 Contributors - see below
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* tfmorris
*****************************************************************************
*
* Some portions of this file was previously release using the BSD License:
*/
// Copyright (c) 1996-2008 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
package org.argouml.ui.explorer;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.Autoscroll;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
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.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import org.apache.log4j.Logger;
import org.argouml.model.Model;
import org.argouml.ui.TransferableModelElements;
import org.argouml.ui.targetmanager.TargetManager;
import org.argouml.uml.diagram.Relocatable;
import org.argouml.uml.diagram.ui.ActionSaveDiagramToClipboard;
/**
* This class extends the default Argo JTree with Drag and drop capabilities.<p>
* See <a
* href="http://java.sun.com/j2se/1.4.2/docs/guide/dragndrop/spec/dnd1.html">
* dnd1</a> and <a
* href="http://java.sun.com/products/jfc/tsc/articles/dragndrop/index.html">
* dnd2</a><p>
*
* And it adds the 'copy to clipboard' capability for diagrams. See
* <a href="http://java.sun.com/j2se/1.3/docs/guide/swing/KeyBindChanges.html">
* KeyBindChanges</a><p>
*
* The ghosted images code originates from <p><a
* href="http://www.javaworld.com/javaworld/javatips/jw-javatip114.html">
* javatip114</a><p>
*
* Interesting may also be the following: <p><a
* href="http://forum.java.sun.com/thread.jspa?threadID=296255&start=30">
* thread</a>
*
* @author alexb
* @since Created on 16 April 2003
*/
public class DnDExplorerTree
extends ExplorerTree
implements DragGestureListener,
DragSourceListener,
Autoscroll {
/**
* Logger.
*/
private static final Logger LOG =
Logger.getLogger(DnDExplorerTree.class);
private static final String DIAGRAM_TO_CLIPBOARD_ACTION =
"export Diagram as GIF";
/**
* Where, in the drag image, the mouse was clicked.
*/
private Point clickOffset = new Point();
/**
* The path being dragged.
*/
private TreePath sourcePath;
/**
* The 'drag image'.
*/
private BufferedImage ghostImage;
/**
* The selected node.
*/
private TreePath selectedTreePath;
/**
* dnd source.
*/
private DragSource dragSource;
/**
* Creates a new instance of DnDArgoJTree.
*/
public DnDExplorerTree() {
super();
this.addTreeSelectionListener(new DnDTreeSelectionListener());
dragSource = DragSource.getDefaultDragSource();
/*
* The drag gesture recognizer fires events
* in response to drag gestures in a component.
*/
DragGestureRecognizer dgr =
dragSource
.createDefaultDragGestureRecognizer(
this,
DnDConstants.ACTION_COPY_OR_MOVE, //specifies valid actions
this);
// Eliminates right mouse clicks as valid actions
dgr.setSourceActions(
dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK);
// First argument: Component to associate the target with
// Second argument: DropTargetListener
new DropTarget(this, new ArgoDropTargetListener());
KeyStroke ctrlC = KeyStroke.getKeyStroke("control C");
this.getInputMap().put(ctrlC, DIAGRAM_TO_CLIPBOARD_ACTION);
this.getActionMap().put(DIAGRAM_TO_CLIPBOARD_ACTION,
new ActionSaveDiagramToClipboard());
}
/**
* The drag gesture listener is notified of drag gestures by a recognizer.
* The typical response is to initiate a drag by invoking
* DragSource.startDrag().
* <p>
*
* TODO: find a way to show a different image when multiple elements are
* dragged.
*
* @param dragGestureEvent
* the DragGestureEvent describing the gesture that has just
* occurred
* @see java.awt.dnd.DragGestureListener#dragGestureRecognized(java.awt.dnd.DragGestureEvent)
*/
public void dragGestureRecognized(
DragGestureEvent dragGestureEvent) {
/*
* Get the selected targets (UML ModelElements)
* from the TargetManager.
*/
Collection targets = TargetManager.getInstance().getModelTargets();
if (targets.size() < 1) {
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Drag: start transferring " + targets.size()
+ " targets.");
}
TransferableModelElements tf =
new TransferableModelElements(targets);
Point ptDragOrigin = dragGestureEvent.getDragOrigin();
TreePath path =
getPathForLocation(ptDragOrigin.x, ptDragOrigin.y);
if (path == null) {
return;
}
Rectangle raPath = getPathBounds(path);
clickOffset.setLocation(ptDragOrigin.x - raPath.x,
ptDragOrigin.y - raPath.y);
/*
* Get the cell renderer (which is a JLabel)
* for the path being dragged.
*/
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)
);
/* The layout manager would normally do this: */
lbl.setSize((int) raPath.getWidth(), (int) raPath.getHeight());
// Get a buffered image of the selection for dragging a ghost image
ghostImage =
new BufferedImage(
(int) raPath.getWidth(), (int) raPath.getHeight(),
BufferedImage.TYPE_INT_ARGB_PRE);
Graphics2D g2 = ghostImage.createGraphics();
/*
* Ask the cell renderer to paint itself into the BufferedImage.
* Make the image ghostlike.
*/
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC, 0.5f));
lbl.paint(g2);
/*
* Now paint a gradient UNDER the ghosted JLabel text
* (but not under the icon if any).
*/
Icon icon = lbl.getIcon();
int nStartOfText =
(icon == null) ? 0
: icon.getIconWidth() + lbl.getIconTextGap();
/* Make the gradient ghostlike: */
g2.setComposite(AlphaComposite.getInstance(
AlphaComposite.DST_OVER, 0.5f));
g2.setPaint(new GradientPaint(nStartOfText, 0,
SystemColor.controlShadow,
getWidth(), 0, new Color(255, 255, 255, 0)));
g2.fillRect(nStartOfText, 0, getWidth(), ghostImage.getHeight());
g2.dispose();
/*
* Remember the path being dragged (because if it is being moved,
* we will have to delete it later).
*/
sourcePath = path;
/*
* We pass our drag image just in case
* it IS supported by the platform.
*/
dragGestureEvent.startDrag(null, ghostImage,
new Point(5, 5), tf, this);
}
private boolean isValidDrag(TreePath destinationPath,
Transferable tf) {
if (destinationPath == null) {
LOG.debug("No valid Drag: no destination found.");
return false;
}
if (selectedTreePath.isDescendant(destinationPath)) {
LOG.debug("No valid Drag: move to descendent.");
return false;
}
if (!tf.isDataFlavorSupported(
TransferableModelElements.UML_COLLECTION_FLAVOR)) {
LOG.debug("No valid Drag: flavor not supported.");
return false;
}
Object dest =
((DefaultMutableTreeNode) destinationPath
.getLastPathComponent()).getUserObject();
/* TODO: support other types of drag.
* Here you set the owner by dragging into a namespace.
* An alternative could be to drag states into composite states...
*/
/* If the destination is not a NameSpace, then abort: */
if (!Model.getFacade().isANamespace(dest)) {
LOG.debug("No valid Drag: not a namespace.");
return false;
}
/* We are sure "dest" is a Namespace now. */
if (Model.getModelManagementHelper().isReadOnly(dest)) {
LOG.debug("No valid Drag: "
+ "this is not an editable UML element (profile?).");
return false;
}
/* If the destination is a DataType, then abort: */
// TODO: Any Namespace can contain other elements. Why don't we allow
// this? - tfm
/*
* MVW: These are the WFRs for DataType:
* [1] A DataType can only contain Operations,
* which all must be queries.
* self.allFeatures->forAll(f |
* f.oclIsKindOf(Operation) and f.oclAsType(Operation).isQuery)
* [2] A DataType cannot contain any other ModelElements.
* self.allContents->isEmpty
* IMHO we should enforce these WFRs here.
* ... so it is still possible to copy or move query operations,
* hence we should allow this.
*/
if (Model.getFacade().isADataType(dest)) {
LOG.debug("No valid Drag: destination is a DataType.");
return false;
}
/*
* Let's check all dragged elements - if one of these
* may be dropped, then the drag is valid.
* The others will be ignored when dropping.
*/
try {
Collection transfers =
(Collection) tf.getTransferData(
TransferableModelElements.UML_COLLECTION_FLAVOR);
for (Object element : transfers) {
if (Model.getFacade().isAUMLElement(element)) {
if (!Model.getModelManagementHelper().isReadOnly(element)) {
if (Model.getFacade().isAModelElement(dest)
&& Model.getFacade().isANamespace(element)
&& Model.getCoreHelper().isValidNamespace(
element, dest)) {
LOG.debug("Valid Drag: namespace " + dest);
return true;
}
if (Model.getFacade().isAFeature(element)
&& Model.getFacade().isAClassifier(dest)) {
return true;
}
}
}
if (element instanceof Relocatable) {
Relocatable d = (Relocatable) element;
if (d.isRelocationAllowed(dest)) {
LOG.debug("Valid Drag: diagram " + dest);
return true;
}
}
}
} catch (UnsupportedFlavorException e) {
LOG.debug(e);
} catch (IOException e) {
LOG.debug(e);
}
LOG.debug("No valid Drag: not a valid namespace.");
return false;
}
/*
* @see java.awt.dnd.DragSourceListener#dragDropEnd(java.awt.dnd.DragSourceDropEvent)
*/
public void dragDropEnd(
DragSourceDropEvent dragSourceDropEvent) {
sourcePath = null;
ghostImage = null;
}
/*
* @see java.awt.dnd.DragSourceListener#dragEnter(java.awt.dnd.DragSourceDragEvent)
*/
public void dragEnter(DragSourceDragEvent dragSourceDragEvent) {
// empty implementation - not used.
}
/*
* @see java.awt.dnd.DragSourceListener#dragExit(java.awt.dnd.DragSourceEvent)
*/
public void dragExit(DragSourceEvent dragSourceEvent) {
// empty implementation - not used.
}
/*
* This is not the correct location to set the cursor.
* The commented out code illustrates the calculation
* of coordinates.
*
* @see java.awt.dnd.DragSourceListener#dragOver(java.awt.dnd.DragSourceDragEvent)
*/
public void dragOver(DragSourceDragEvent dragSourceDragEvent) {
// Transferable tf =
// dragSourceDragEvent.getDragSourceContext().getTransferable();
// /* This is the mouse location on the screen: */
// Point dragLoc = dragSourceDragEvent.getLocation();
// /* This is the JTree location on the screen: */
// Point treeLoc = getLocationOnScreen();
// /* Now substract to find the location within the JTree: */
// dragLoc.translate(- treeLoc.x, - treeLoc.y);
// TreePath destinationPath =
// getPathForLocation(dragLoc.x, dragLoc.y);
// if (isValidDrag(destinationPath, tf)) {
//// dragSourceDragEvent.getDragSourceContext()
//// .setCursor(DragSource.DefaultMoveDrop);
// } else {
//// dragSourceDragEvent.getDragSourceContext()
//// .setCursor(DragSource.DefaultCopyNoDrop);
// }
}
/*
* @see java.awt.dnd.DragSourceListener#dropActionChanged(java.awt.dnd.DragSourceDragEvent)
*/
public void dropActionChanged(
DragSourceDragEvent dragSourceDragEvent) {
// empty implementation - not used.
}
/**
* records the selected path for later use during dnd.
*/
class DnDTreeSelectionListener implements TreeSelectionListener {
public void valueChanged(
TreeSelectionEvent treeSelectionEvent) {
selectedTreePath = treeSelectionEvent.getNewLeadSelectionPath();
}
}
// 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;
/*
* Ok, we've been told to scroll because the mouse cursor is in our
* scroll zone.
* @see java.awt.dnd.Autoscroll#autoscroll(java.awt.Point)
*/
public void autoscroll(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;
}
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 were at the absolute top or
// bottom, just return the first or last row respectively.
// Is row at top of screen?
nRow =
(pt.y + raOuter.y <= AUTOSCROLL_MARGIN)
?
// Yes, scroll up one row
(nRow <= 0 ? 0 : nRow - 1)
:
// No, scroll down one row
(nRow < getRowCount() - 1 ? nRow + 1 : nRow);
scrollRowToVisible(nRow);
}
/*
* Calculate the insets for the *JTREE*, not the viewport the tree is in.
* This makes it a bit messy.
*
* @see java.awt.dnd.Autoscroll#getAutoscrollInsets()
*/
public Insets getAutoscrollInsets() {
Rectangle raOuter = getBounds();
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);
}
/**
* The DropTargetListener.
* Handles drop target events including the drop itself.
*/
class ArgoDropTargetListener implements DropTargetListener {
private TreePath lastPath;
private Rectangle2D cueLine = new Rectangle2D.Float();
private Rectangle2D ghostRectangle = new Rectangle2D.Float();
private Color cueLineColor;
private Point lastMouseLocation = new Point();
private Timer hoverTimer;
/**
* The constructor.
*/
public ArgoDropTargetListener() {
cueLineColor =
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.
*/
hoverTimer =
new Timer(1000, new ActionListener() {
/*
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent e) {
if (getPathForRow(0).equals/*isRootPath*/(lastPath)) {
return;
}
if (isExpanded(lastPath)) {
collapsePath(lastPath);
} else {
expandPath(lastPath);
}
}
});
hoverTimer.setRepeats(false); // Set timer to one-shot mode
}
/*
* @see java.awt.dnd.DropTargetListener#dragEnter(java.awt.dnd.DropTargetDragEvent)
*/
public void dragEnter(
DropTargetDragEvent dropTargetDragEvent) {
LOG.debug("dragEnter");
if (!isDragAcceptable(dropTargetDragEvent)) {
dropTargetDragEvent.rejectDrag();
} else {
dropTargetDragEvent.acceptDrag(
dropTargetDragEvent.getDropAction());
}
}
/*
* @see java.awt.dnd.DropTargetListener#dragExit(java.awt.dnd.DropTargetEvent)
*/
public void dragExit(DropTargetEvent dropTargetEvent) {
LOG.debug("dragExit");
if (!DragSource.isDragImageSupported()) {
repaint(ghostRectangle.getBounds());
}
}
/**
* Called when a drag operation is ongoing, while the mouse pointer
* is still over the operable part of the drop site
* for the <code>DropTarget</code> registered with this listener.
*
* @see java.awt.dnd.DropTargetListener#dragOver(java.awt.dnd.DropTargetDragEvent)
*/
public void dragOver(DropTargetDragEvent dropTargetDragEvent) {
Point pt = dropTargetDragEvent.getLocation();
if (pt.equals(lastMouseLocation)) {
return;
}
/* Many many of these events .. this slows things down: */
// LOG.debug("dragOver");
lastMouseLocation = pt;
Graphics2D g2 = (Graphics2D) getGraphics();
/*
* The next condition becomes false when dragging in
* something from another application.
*/
if (ghostImage != null) {
/*
* If a drag image is not supported by the platform,
* then draw my own drag image.
*/
if (!DragSource.isDragImageSupported()) {
/* Rub out the last ghost image and cue line: */
paintImmediately(ghostRectangle.getBounds());
/* And remember where we are about to draw
* the new ghost image:
*/
ghostRectangle.setRect(pt.x - clickOffset.x,
pt.y - clickOffset.y,
ghostImage.getWidth(),
ghostImage.getHeight());
g2.drawImage(ghostImage,
AffineTransform.getTranslateInstance(
ghostRectangle.getX(),
ghostRectangle.getY()), null);
} else {
// Just rub out the last cue line
paintImmediately(cueLine.getBounds());
}
}
TreePath path = getPathForLocation(pt.x, pt.y);
if (!(path == lastPath)) {
lastPath = path;
hoverTimer.restart();
}
/*
* In any case draw (over the ghost image if necessary)
* a cue line indicating where a drop will occur.
*/
Rectangle raPath = getPathBounds(path);
if (raPath != null) {
cueLine.setRect(0,
raPath.y + (int) raPath.getHeight(),
getWidth(),
2);
}
g2.setColor(cueLineColor);
g2.fill(cueLine);
// And include the cue line in the area to be rubbed out next time
ghostRectangle = ghostRectangle.createUnion(cueLine);
/* Testcase: drag something from another
* application into ArgoUML,
* and the explorer shows the drop icon, instead of the noDrop.
*/
try {
if (!dropTargetDragEvent.isDataFlavorSupported(
TransferableModelElements.UML_COLLECTION_FLAVOR)) {
dropTargetDragEvent.rejectDrag();
return;
}
} catch (NullPointerException e) {
dropTargetDragEvent.rejectDrag();
return;
}
if (path == null) {
dropTargetDragEvent.rejectDrag();
return;
}
// to prohibit dropping onto the drag source:
if (path.equals(sourcePath)) {
dropTargetDragEvent.rejectDrag();
return;
}
if (selectedTreePath.isDescendant(path)) {
dropTargetDragEvent.rejectDrag();
return;
}
Object dest =
((DefaultMutableTreeNode) path
.getLastPathComponent()).getUserObject();
/* If the destination is not a NameSpace, then reject: */
if (!Model.getFacade().isANamespace(dest)) {
if (LOG.isDebugEnabled()) {
String name;
if (Model.getFacade().isAUMLElement(dest)) {
name = Model.getFacade().getName(dest);
} else if (dest == null) {
name = "<null>";
} else {
name = dest.toString();
}
LOG.debug("No valid Drag: "
+ (Model.getFacade().isAUMLElement(dest)
? name + " not a namespace."
: " not a UML element."));
}
dropTargetDragEvent.rejectDrag();
return;
}
/* We are sure "dest" is a Namespace now. */
if (Model.getModelManagementHelper().isReadOnly(dest)) {
LOG.debug("No valid Drag: "
+ "not an editable UML element (profile?).");
return;
}
/* If the destination is a DataType, then reject: */
if (Model.getFacade().isADataType(dest)) {
LOG.debug("No valid Drag: destination is a DataType.");
dropTargetDragEvent.rejectDrag();
return;
}
dropTargetDragEvent.acceptDrag(
dropTargetDragEvent.getDropAction());
}
/**
* The drop: what is done when the mousebutton is released.
*
* @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent)
*/
public void drop(DropTargetDropEvent dropTargetDropEvent) {
LOG.debug("dropping ... ");
/* Prevent hover timer from doing an unwanted
* expandPath or collapsePath:
*/
hoverTimer.stop();
/* Clear the ghost image: */
repaint(ghostRectangle.getBounds());
if (!isDropAcceptable(dropTargetDropEvent)) {
dropTargetDropEvent.rejectDrop();
return;
}
try {
Transferable tr = dropTargetDropEvent.getTransferable();
//get new parent node
Point loc = dropTargetDropEvent.getLocation();
TreePath destinationPath = getPathForLocation(loc.x, loc.y);
if (LOG.isDebugEnabled()) {
LOG.debug("Drop location: x=" + loc.x + " y=" + loc.y);
}
if (!isValidDrag(destinationPath, tr)) {
dropTargetDropEvent.rejectDrop();
return;
}
//get the model elements that are being transfered.
Collection modelElements =
(Collection) tr.getTransferData(
TransferableModelElements.UML_COLLECTION_FLAVOR);
if (LOG.isDebugEnabled()) {
LOG.debug("transfer data = " + modelElements);
}
Object dest =
((DefaultMutableTreeNode) destinationPath
.getLastPathComponent()).getUserObject();
Object src =
((DefaultMutableTreeNode) sourcePath
.getLastPathComponent()).getUserObject();
int action = dropTargetDropEvent.getDropAction();
/* The user-DropActions are:
* Ctrl + Shift -> ACTION_LINK
* Ctrl -> ACTION_COPY
* Shift -> ACTION_MOVE
* (none) -> ACTION_MOVE
*/
boolean copyAction =
(action == DnDConstants.ACTION_COPY);
boolean moveAction =
(action == DnDConstants.ACTION_MOVE);
if (!(moveAction || copyAction)) {
dropTargetDropEvent.rejectDrop();
return;
}
if (Model.getFacade().isAUMLElement(dest)) {
if (Model.getModelManagementHelper().isReadOnly(dest)) {
dropTargetDropEvent.rejectDrop();
return;
}
}
if (Model.getFacade().isAUMLElement(src)) {
if (Model.getModelManagementHelper().isReadOnly(src)) {
dropTargetDropEvent.rejectDrop();
return;
}
}
// TODO: Really should be Element/ModelElement, but we don't
// have a type which is portable for this
Collection<Object> newTargets = new ArrayList<Object>();
try {
dropTargetDropEvent.acceptDrop(action);
for (Object me : modelElements) {
if (Model.getFacade().isAUMLElement(me)) {
if (Model.getModelManagementHelper().isReadOnly(me)) {
continue;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug((moveAction ? "move " : "copy ") + me);
}
if (Model.getCoreHelper().isValidNamespace(me, dest)) {
if (moveAction) {
Model.getCoreHelper().setNamespace(me, dest);
newTargets.add(me);
}
if (copyAction) {
try {
newTargets.add(Model.getCopyHelper()
.copy(me, dest));
} catch (RuntimeException e) {
/* TODO: The copy function is not yet
* completely implemented - so we will
* have some exceptions here and there.*/
LOG.error("Exception", e);
}
}
}
if (me instanceof Relocatable) {
Relocatable d = (Relocatable) me;
if (d.isRelocationAllowed(dest)) {
if (d.relocate(dest)) {
ExplorerEventAdaptor.getInstance()
.modelElementChanged(src);
ExplorerEventAdaptor.getInstance()
.modelElementChanged(dest);
/*TODO: Make the tree refresh and expand
* really work in all cases!
*/
makeVisible(destinationPath);
expandPath(destinationPath);
newTargets.add(me);
}
}
}
if (Model.getFacade().isAFeature(me)
&& Model.getFacade().isAClassifier(dest)) {
if (moveAction) {
Model.getCoreHelper().removeFeature(
Model.getFacade().getOwner(me), me);
Model.getCoreHelper().addFeature(dest, me);
newTargets.add(me);
}
if (copyAction) {
newTargets.add(
Model.getCopyHelper().copy(me, dest));
}
}
}
dropTargetDropEvent.getDropTargetContext()
.dropComplete(true);
TargetManager.getInstance().setTargets(newTargets);
} catch (java.lang.IllegalStateException ils) {
LOG.debug("drop IllegalStateException");
dropTargetDropEvent.rejectDrop();
}
dropTargetDropEvent.getDropTargetContext()
.dropComplete(true);
} catch (IOException io) {
LOG.debug("drop IOException");
dropTargetDropEvent.rejectDrop();
} catch (UnsupportedFlavorException ufe) {
LOG.debug("drop UnsupportedFlavorException");
dropTargetDropEvent.rejectDrop();
}
}
/*
* @see java.awt.dnd.DropTargetListener#dropActionChanged(java.awt.dnd.DropTargetDragEvent)
*/
public void dropActionChanged(
DropTargetDragEvent dropTargetDragEvent) {
if (!isDragAcceptable(dropTargetDragEvent)) {
dropTargetDragEvent.rejectDrag();
} else {
dropTargetDragEvent.acceptDrag(
dropTargetDragEvent.getDropAction());
}
}
/**
* @param dropTargetEvent the droptargetevent
* @return true if the drag is acceptable
*/
public boolean isDragAcceptable(
DropTargetDragEvent dropTargetEvent) {
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if ((dropTargetEvent.getDropAction()
& DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return false;
}
// Do this if you want to prohibit dropping onto the drag source...
Point pt = dropTargetEvent.getLocation();
TreePath path = getPathForLocation(pt.x, pt.y);
if (path == null) {
return false;
}
if (path.equals(sourcePath)) {
return false;
}
return true;
}
/**
* @param dropTargetDropEvent the droptargetdropevent
* @return true if the drop is acceptable
*/
public boolean isDropAcceptable(
DropTargetDropEvent dropTargetDropEvent) {
// Only accept COPY or MOVE gestures (ie LINK is not supported)
if ((dropTargetDropEvent.getDropAction()
& DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return false;
}
// Do this if you want to prohibit dropping onto the drag source...
Point pt = dropTargetDropEvent.getLocation();
TreePath path = getPathForLocation(pt.x, pt.y);
if (path == null) {
return false;
}
if (path.equals(sourcePath)) {
return false;
}
return true;
}
} /* end class */
/**
* The UID.
*/
private static final long serialVersionUID = 6207230394860016617L;
}