Coverage Report - org.argouml.ui.explorer.DnDExplorerTree
 
Classes in this File Line Coverage Branch Coverage Complexity
DnDExplorerTree
13%
13/98
0%
0/48
6.8
DnDExplorerTree$ArgoDropTargetListener
4%
8/178
0%
0/104
6.8
DnDExplorerTree$ArgoDropTargetListener$1
14%
1/7
0%
0/4
6.8
DnDExplorerTree$DnDTreeSelectionListener
100%
3/3
N/A
6.8
 
 1  
 /* $Id: DnDExplorerTree.java 17843 2010-01-12 19:23:29Z linus $
 2  
  *****************************************************************************
 3  
  * Copyright (c) 2009 Contributors - see below
 4  
  * All rights reserved. This program and the accompanying materials
 5  
  * are made available under the terms of the Eclipse Public License v1.0
 6  
  * which accompanies this distribution, and is available at
 7  
  * http://www.eclipse.org/legal/epl-v10.html
 8  
  *
 9  
  * Contributors:
 10  
  *    tfmorris
 11  
  *****************************************************************************
 12  
  *
 13  
  * Some portions of this file was previously release using the BSD License:
 14  
  */
 15  
 
 16  
 // Copyright (c) 1996-2008 The Regents of the University of California. All
 17  
 // Rights Reserved. Permission to use, copy, modify, and distribute this
 18  
 // software and its documentation without fee, and without a written
 19  
 // agreement is hereby granted, provided that the above copyright notice
 20  
 // and this paragraph appear in all copies.  This software program and
 21  
 // documentation are copyrighted by The Regents of the University of
 22  
 // California. The software program and documentation are supplied "AS
 23  
 // IS", without any accompanying services from The Regents. The Regents
 24  
 // does not warrant that the operation of the program will be
 25  
 // uninterrupted or error-free. The end-user understands that the program
 26  
 // was developed for research purposes and is advised not to rely
 27  
 // exclusively on the program for any reason.  IN NO EVENT SHALL THE
 28  
 // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 29  
 // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 30  
 // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 31  
 // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 32  
 // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
 33  
 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 34  
 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 35  
 // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 36  
 // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 37  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 38  
 
 39  
 package org.argouml.ui.explorer;
 40  
 
 41  
 import java.awt.AlphaComposite;
 42  
 import java.awt.Color;
 43  
 import java.awt.GradientPaint;
 44  
 import java.awt.Graphics2D;
 45  
 import java.awt.Insets;
 46  
 import java.awt.Point;
 47  
 import java.awt.Rectangle;
 48  
 import java.awt.SystemColor;
 49  
 import java.awt.datatransfer.Transferable;
 50  
 import java.awt.datatransfer.UnsupportedFlavorException;
 51  
 import java.awt.dnd.Autoscroll;
 52  
 import java.awt.dnd.DnDConstants;
 53  
 import java.awt.dnd.DragGestureEvent;
 54  
 import java.awt.dnd.DragGestureListener;
 55  
 import java.awt.dnd.DragGestureRecognizer;
 56  
 import java.awt.dnd.DragSource;
 57  
 import java.awt.dnd.DragSourceDragEvent;
 58  
 import java.awt.dnd.DragSourceDropEvent;
 59  
 import java.awt.dnd.DragSourceEvent;
 60  
 import java.awt.dnd.DragSourceListener;
 61  
 import java.awt.dnd.DropTarget;
 62  
 import java.awt.dnd.DropTargetDragEvent;
 63  
 import java.awt.dnd.DropTargetDropEvent;
 64  
 import java.awt.dnd.DropTargetEvent;
 65  
 import java.awt.dnd.DropTargetListener;
 66  
 import java.awt.event.ActionEvent;
 67  
 import java.awt.event.ActionListener;
 68  
 import java.awt.event.InputEvent;
 69  
 import java.awt.geom.AffineTransform;
 70  
 import java.awt.geom.Rectangle2D;
 71  
 import java.awt.image.BufferedImage;
 72  
 import java.io.IOException;
 73  
 import java.util.ArrayList;
 74  
 import java.util.Collection;
 75  
 
 76  
 import javax.swing.Icon;
 77  
 import javax.swing.JLabel;
 78  
 import javax.swing.KeyStroke;
 79  
 import javax.swing.Timer;
 80  
 import javax.swing.event.TreeSelectionEvent;
 81  
 import javax.swing.event.TreeSelectionListener;
 82  
 import javax.swing.tree.DefaultMutableTreeNode;
 83  
 import javax.swing.tree.TreePath;
 84  
 
 85  
 import org.apache.log4j.Logger;
 86  
 import org.argouml.model.Model;
 87  
 import org.argouml.ui.TransferableModelElements;
 88  
 import org.argouml.ui.targetmanager.TargetManager;
 89  
 import org.argouml.uml.diagram.Relocatable;
 90  
 import org.argouml.uml.diagram.ui.ActionSaveDiagramToClipboard;
 91  
 
 92  
 /**
 93  
  * This class extends the default Argo JTree with Drag and drop capabilities.<p>
 94  
  * See <a 
 95  
  * href="http://java.sun.com/j2se/1.4.2/docs/guide/dragndrop/spec/dnd1.html">
 96  
  * dnd1</a> and <a 
 97  
  * href="http://java.sun.com/products/jfc/tsc/articles/dragndrop/index.html">
 98  
  * dnd2</a><p>
 99  
  *
 100  
  * And it adds the 'copy to clipboard' capability for diagrams. See
 101  
  * <a href="http://java.sun.com/j2se/1.3/docs/guide/swing/KeyBindChanges.html">
 102  
  * KeyBindChanges</a><p>
 103  
  *
 104  
  * The ghosted images code originates from <p><a 
 105  
  * href="http://www.javaworld.com/javaworld/javatips/jw-javatip114.html">
 106  
  * javatip114</a><p>
 107  
  *
 108  
  * Interesting may also be the following: <p><a 
 109  
  * href="http://forum.java.sun.com/thread.jspa?threadID=296255&start=30">
 110  
  * thread</a>
 111  
  *
 112  
  * @author  alexb
 113  
  * @since Created on 16 April 2003
 114  
  */
 115  19
 public class DnDExplorerTree
 116  
     extends ExplorerTree
 117  
     implements DragGestureListener,
 118  
         DragSourceListener,
 119  
         Autoscroll {
 120  
     /**
 121  
      * Logger.
 122  
      */
 123  900
     private static final Logger LOG =
 124  
             Logger.getLogger(DnDExplorerTree.class);
 125  
 
 126  
     private static final String DIAGRAM_TO_CLIPBOARD_ACTION =
 127  
         "export Diagram as GIF";
 128  
 
 129  
     /**
 130  
      * Where, in the drag image, the mouse was clicked.
 131  
      */
 132  900
     private Point        clickOffset = new Point();
 133  
     /**
 134  
      * The path being dragged.
 135  
      */
 136  
     private TreePath                sourcePath;
 137  
     /**
 138  
      * The 'drag image'.
 139  
      */
 140  
     private BufferedImage        ghostImage;
 141  
 
 142  
 
 143  
     /**
 144  
      * The selected node.
 145  
      */
 146  
     private TreePath selectedTreePath;
 147  
 
 148  
     /**
 149  
      * dnd source.
 150  
      */
 151  
     private DragSource dragSource;
 152  
 
 153  
     /**
 154  
      * Creates a new instance of DnDArgoJTree.
 155  
      */
 156  
     public DnDExplorerTree() {
 157  
 
 158  900
         super();
 159  
 
 160  900
         this.addTreeSelectionListener(new DnDTreeSelectionListener());
 161  
 
 162  900
         dragSource = DragSource.getDefaultDragSource();
 163  
 
 164  
         /*
 165  
          * The drag gesture recognizer fires events
 166  
          * in response to drag gestures in a component.
 167  
          */
 168  900
         DragGestureRecognizer dgr =
 169  
             dragSource
 170  
                 .createDefaultDragGestureRecognizer(
 171  
                     this,
 172  
                     DnDConstants.ACTION_COPY_OR_MOVE, //specifies valid actions
 173  
                     this);
 174  
 
 175  
         // Eliminates right mouse clicks as valid actions
 176  900
         dgr.setSourceActions(
 177  
                         dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK);
 178  
 
 179  
         // First argument:  Component to associate the target with
 180  
         // Second argument: DropTargetListener
 181  900
         new DropTarget(this, new ArgoDropTargetListener());
 182  
 
 183  900
         KeyStroke ctrlC = KeyStroke.getKeyStroke("control C");
 184  900
         this.getInputMap().put(ctrlC, DIAGRAM_TO_CLIPBOARD_ACTION);
 185  900
         this.getActionMap().put(DIAGRAM_TO_CLIPBOARD_ACTION,
 186  
                 new ActionSaveDiagramToClipboard());
 187  900
     }
 188  
 
 189  
     /**
 190  
      * The drag gesture listener is notified of drag gestures by a recognizer.
 191  
      * The typical response is to initiate a drag by invoking
 192  
      * DragSource.startDrag().
 193  
      * <p>
 194  
      * 
 195  
      * TODO: find a way to show a different image when multiple elements are
 196  
      * dragged.
 197  
      * 
 198  
      * @param dragGestureEvent
 199  
      *            the DragGestureEvent describing the gesture that has just
 200  
      *            occurred
 201  
      * @see java.awt.dnd.DragGestureListener#dragGestureRecognized(java.awt.dnd.DragGestureEvent)
 202  
      */
 203  
     public void dragGestureRecognized(
 204  
             DragGestureEvent dragGestureEvent) {
 205  
 
 206  
         /*
 207  
          * Get the selected targets (UML ModelElements)
 208  
          * from the TargetManager.
 209  
          */
 210  0
         Collection targets = TargetManager.getInstance().getModelTargets();
 211  0
         if (targets.size() < 1) {
 212  0
             return;
 213  
         }
 214  0
         if (LOG.isDebugEnabled()) {
 215  0
             LOG.debug("Drag: start transferring " + targets.size()
 216  
                     + " targets.");
 217  
         }
 218  0
         TransferableModelElements tf =
 219  
             new TransferableModelElements(targets);
 220  
 
 221  0
         Point ptDragOrigin = dragGestureEvent.getDragOrigin();
 222  0
         TreePath path =
 223  
             getPathForLocation(ptDragOrigin.x, ptDragOrigin.y);
 224  0
         if (path == null) {
 225  0
             return;
 226  
         }
 227  0
         Rectangle raPath = getPathBounds(path);
 228  0
         clickOffset.setLocation(ptDragOrigin.x - raPath.x,
 229  
                 ptDragOrigin.y - raPath.y);
 230  
 
 231  
         /*
 232  
          * Get the cell renderer (which is a JLabel)
 233  
          * for the path being dragged.
 234  
          */
 235  0
         JLabel lbl =
 236  
             (JLabel) getCellRenderer().getTreeCellRendererComponent(
 237  
                     this,                        // tree
 238  
                     path.getLastPathComponent(), // value
 239  
                     false,        // isSelected        (dont want a colored background)
 240  
                     isExpanded(path),                  // isExpanded
 241  
                     getModel().isLeaf(path.getLastPathComponent()), // isLeaf
 242  
                     0,                 // row        (not important for rendering)
 243  
                     false        // hasFocus (dont want a focus rectangle)
 244  
             );
 245  
         /* The layout manager would normally do this: */
 246  0
         lbl.setSize((int) raPath.getWidth(), (int) raPath.getHeight());
 247  
 
 248  
         // Get a buffered image of the selection for dragging a ghost image
 249  0
         ghostImage =
 250  
             new BufferedImage(
 251  
                 (int) raPath.getWidth(), (int) raPath.getHeight(),
 252  
                 BufferedImage.TYPE_INT_ARGB_PRE);
 253  0
         Graphics2D g2 = ghostImage.createGraphics();
 254  
 
 255  
         /*
 256  
          * Ask the cell renderer to paint itself into the BufferedImage.
 257  
          * Make the image ghostlike.
 258  
          */
 259  0
         g2.setComposite(AlphaComposite.getInstance(
 260  
                 AlphaComposite.SRC, 0.5f));
 261  0
         lbl.paint(g2);
 262  
 
 263  
         /*
 264  
          * Now paint a gradient UNDER the ghosted JLabel text
 265  
          * (but not under the icon if any).
 266  
          */
 267  0
         Icon icon = lbl.getIcon();
 268  0
         int nStartOfText =
 269  
             (icon == null) ? 0
 270  
                 : icon.getIconWidth() + lbl.getIconTextGap();
 271  
         /* Make the gradient ghostlike: */
 272  0
         g2.setComposite(AlphaComposite.getInstance(
 273  
                 AlphaComposite.DST_OVER, 0.5f));
 274  0
         g2.setPaint(new GradientPaint(nStartOfText,        0,
 275  
                 SystemColor.controlShadow,
 276  
                 getWidth(), 0, new Color(255, 255, 255, 0)));
 277  0
         g2.fillRect(nStartOfText, 0, getWidth(), ghostImage.getHeight());
 278  
 
 279  0
         g2.dispose();
 280  
 
 281  
         /*
 282  
          * Remember the path being dragged (because if it is being moved,
 283  
          * we will have to delete it later).
 284  
          */
 285  0
         sourcePath = path;
 286  
 
 287  
         /*
 288  
          * We pass our drag image just in case
 289  
          * it IS supported by the platform.
 290  
          */
 291  0
         dragGestureEvent.startDrag(null, ghostImage,
 292  
                 new Point(5, 5), tf, this);
 293  0
     }
 294  
 
 295  
     private boolean isValidDrag(TreePath destinationPath,
 296  
                     Transferable tf) {
 297  0
         if (destinationPath == null) {
 298  0
             LOG.debug("No valid Drag: no destination found.");
 299  0
             return false;
 300  
         }
 301  0
         if (selectedTreePath.isDescendant(destinationPath)) {
 302  0
             LOG.debug("No valid Drag: move to descendent.");
 303  0
             return false;
 304  
         }
 305  0
         if (!tf.isDataFlavorSupported(
 306  
                 TransferableModelElements.UML_COLLECTION_FLAVOR)) {
 307  0
             LOG.debug("No valid Drag: flavor not supported.");
 308  0
             return false;
 309  
         }
 310  0
         Object dest =
 311  
             ((DefaultMutableTreeNode) destinationPath
 312  
                 .getLastPathComponent()).getUserObject();
 313  
 
 314  
         /* TODO: support other types of drag.
 315  
          * Here you set the owner by dragging into a namespace.
 316  
          * An alternative could be to drag states into composite states...
 317  
          */
 318  
 
 319  
         /* If the destination is not a NameSpace, then abort: */
 320  0
         if (!Model.getFacade().isANamespace(dest)) {
 321  0
             LOG.debug("No valid Drag: not a namespace.");
 322  0
             return false;
 323  
         }
 324  
 
 325  
         /* We are sure "dest" is a Namespace now. */
 326  0
         if (Model.getModelManagementHelper().isReadOnly(dest)) {
 327  0
             LOG.debug("No valid Drag: "
 328  
                     + "this is not an editable UML element (profile?).");
 329  0
             return false;
 330  
         }
 331  
 
 332  
         /* If the destination is a DataType, then abort: */
 333  
 
 334  
         // TODO: Any Namespace can contain other elements.  Why don't we allow
 335  
         // this? - tfm
 336  
         /* 
 337  
          * MVW: These are the WFRs for DataType:
 338  
          * [1] A DataType can only contain Operations, 
 339  
          * which all must be queries.
 340  
          * self.allFeatures->forAll(f |
 341  
          *  f.oclIsKindOf(Operation) and f.oclAsType(Operation).isQuery) 
 342  
          * [2] A DataType cannot contain any other ModelElements.
 343  
          *  self.allContents->isEmpty
 344  
          *  IMHO we should enforce these WFRs here.
 345  
          *  ... so it is still possible to copy or move query operations,
 346  
          *  hence we should allow this. 
 347  
          */
 348  0
         if (Model.getFacade().isADataType(dest)) {
 349  0
             LOG.debug("No valid Drag: destination is a DataType.");
 350  0
             return false;
 351  
         }
 352  
 
 353  
         /*
 354  
          * Let's check all dragged elements - if one of these
 355  
          * may be dropped, then the drag is valid.
 356  
          * The others will be ignored when dropping.
 357  
          */
 358  
         try {
 359  0
             Collection transfers =
 360  
                 (Collection) tf.getTransferData(
 361  
                     TransferableModelElements.UML_COLLECTION_FLAVOR);
 362  0
             for (Object element : transfers) {
 363  0
                 if (Model.getFacade().isAUMLElement(element)) {
 364  0
                     if (!Model.getModelManagementHelper().isReadOnly(element)) {
 365  0
                         if (Model.getFacade().isAModelElement(dest) 
 366  
                                 && Model.getFacade().isANamespace(element) 
 367  
                                 && Model.getCoreHelper().isValidNamespace(
 368  
                                         element, dest)) {
 369  0
                             LOG.debug("Valid Drag: namespace " + dest);
 370  0
                             return true;
 371  
                         }
 372  0
                         if (Model.getFacade().isAFeature(element) 
 373  
                                 && Model.getFacade().isAClassifier(dest)) {
 374  0
                             return true;
 375  
                         }
 376  
                     }
 377  
                 }
 378  0
                 if (element instanceof Relocatable) {
 379  0
                     Relocatable d = (Relocatable) element;
 380  0
                     if (d.isRelocationAllowed(dest)) {
 381  0
                         LOG.debug("Valid Drag: diagram " + dest);
 382  0
                         return true;
 383  
                     }
 384  0
                 }
 385  
             }
 386  0
         } catch (UnsupportedFlavorException e) {
 387  0
             LOG.debug(e);
 388  0
         } catch (IOException e) {
 389  0
             LOG.debug(e);
 390  0
         }
 391  0
         LOG.debug("No valid Drag: not a valid namespace.");
 392  0
         return false;
 393  
     }
 394  
 
 395  
     /*
 396  
      * @see java.awt.dnd.DragSourceListener#dragDropEnd(java.awt.dnd.DragSourceDropEvent)
 397  
      */
 398  
     public void dragDropEnd(
 399  
                     DragSourceDropEvent dragSourceDropEvent) {
 400  0
         sourcePath = null;
 401  0
         ghostImage = null;
 402  0
     }
 403  
 
 404  
     /*
 405  
      * @see java.awt.dnd.DragSourceListener#dragEnter(java.awt.dnd.DragSourceDragEvent)
 406  
      */
 407  
     public void dragEnter(DragSourceDragEvent dragSourceDragEvent) {
 408  
         // empty implementation - not used.
 409  0
     }
 410  
 
 411  
     /*
 412  
      * @see java.awt.dnd.DragSourceListener#dragExit(java.awt.dnd.DragSourceEvent)
 413  
      */
 414  
     public void dragExit(DragSourceEvent dragSourceEvent) {
 415  
         // empty implementation - not used.
 416  0
     }
 417  
 
 418  
     /*
 419  
      * This is not the correct location to set the cursor.
 420  
      * The commented out code illustrates the calculation
 421  
      * of coordinates.
 422  
      *
 423  
      * @see java.awt.dnd.DragSourceListener#dragOver(java.awt.dnd.DragSourceDragEvent)
 424  
      */
 425  
     public void dragOver(DragSourceDragEvent dragSourceDragEvent) {
 426  
 //        Transferable tf =
 427  
 //            dragSourceDragEvent.getDragSourceContext().getTransferable();
 428  
 //        /* This is the mouse location on the screen: */
 429  
 //        Point dragLoc = dragSourceDragEvent.getLocation();
 430  
 //        /* This is the JTree location on the screen: */
 431  
 //        Point treeLoc = getLocationOnScreen();
 432  
 //        /* Now substract to find the location within the JTree: */
 433  
 //        dragLoc.translate(- treeLoc.x, - treeLoc.y);
 434  
 //        TreePath destinationPath =
 435  
 //                getPathForLocation(dragLoc.x, dragLoc.y);
 436  
 //         if (isValidDrag(destinationPath, tf)) {
 437  
 ////           dragSourceDragEvent.getDragSourceContext()
 438  
 ////           .setCursor(DragSource.DefaultMoveDrop);
 439  
 //        } else {
 440  
 ////          dragSourceDragEvent.getDragSourceContext()
 441  
 ////          .setCursor(DragSource.DefaultCopyNoDrop);
 442  
 //        }
 443  0
     }
 444  
 
 445  
     /*
 446  
      * @see java.awt.dnd.DragSourceListener#dropActionChanged(java.awt.dnd.DragSourceDragEvent)
 447  
      */
 448  
     public void dropActionChanged(
 449  
                     DragSourceDragEvent dragSourceDragEvent) {
 450  
         // empty implementation - not used.
 451  0
     }
 452  
 
 453  
     /**
 454  
      * records the selected path for later use during dnd.
 455  
      */
 456  900
     class DnDTreeSelectionListener implements TreeSelectionListener {
 457  
         public void valueChanged(
 458  
                         TreeSelectionEvent treeSelectionEvent) {
 459  19
             selectedTreePath = treeSelectionEvent.getNewLeadSelectionPath();
 460  19
         }
 461  
     }
 462  
 
 463  
 
 464  
 //  Autoscroll Interface...
 465  
 //  The following code was borrowed from the book:
 466  
 //                 Java Swing
 467  
 //                 By Robert Eckstein, Marc Loy & Dave Wood
 468  
 //                 Paperback - 1221 pages 1 Ed edition (September 1998)
 469  
 //                 O'Reilly & Associates; ISBN: 156592455X
 470  
  //
 471  
 //  The relevant chapter of which can be found at:
 472  
 //                 http://www.oreilly.com/catalog/jswing/chapter/dnd.beta.pdf
 473  
 
 474  
     private static final int AUTOSCROLL_MARGIN = 12;
 475  
 
 476  
     /*
 477  
      * Ok, we've been told to scroll because the mouse cursor is in our
 478  
      * scroll zone.
 479  
      * @see java.awt.dnd.Autoscroll#autoscroll(java.awt.Point)
 480  
      */
 481  
     public void autoscroll(Point pt) {
 482  
         // Figure out which row we're on.
 483  0
         int nRow = getRowForLocation(pt.x, pt.y);
 484  
 
 485  
         // If we are not on a row then ignore this autoscroll request
 486  0
         if (nRow < 0) {
 487  0
             return;
 488  
         }
 489  
 
 490  0
         Rectangle raOuter = getBounds();
 491  
         // Now decide if the row is at the top of the screen or at the
 492  
         // bottom. We do this to make the previous row (or the next
 493  
         // row) visible as appropriate. If were at the absolute top or
 494  
         // bottom, just return the first or last row respectively.
 495  
 
 496  
         // Is row at top of screen?
 497  0
         nRow =
 498  
             (pt.y + raOuter.y <= AUTOSCROLL_MARGIN)
 499  
                 ?
 500  
                 // Yes, scroll up one row
 501  
                 (nRow <= 0 ? 0 : nRow - 1)
 502  
                 :
 503  
                     // No, scroll down one row
 504  
                     (nRow < getRowCount() - 1 ? nRow + 1 : nRow);
 505  
 
 506  0
         scrollRowToVisible(nRow);
 507  0
     }
 508  
 
 509  
     /*
 510  
      * Calculate the insets for the *JTREE*, not the viewport the tree is in.
 511  
      * This makes it a bit messy.
 512  
      *
 513  
      * @see java.awt.dnd.Autoscroll#getAutoscrollInsets()
 514  
      */
 515  
     public Insets getAutoscrollInsets() {
 516  0
         Rectangle raOuter = getBounds();
 517  0
         Rectangle raInner = getParent().getBounds();
 518  0
         return new Insets(
 519  
                 raInner.y - raOuter.y + AUTOSCROLL_MARGIN,
 520  
                 raInner.x - raOuter.x + AUTOSCROLL_MARGIN,
 521  
                 raOuter.height - raInner.height
 522  
                 - raInner.y + raOuter.y + AUTOSCROLL_MARGIN,
 523  
                 raOuter.width - raInner.width
 524  
                 - raInner.x + raOuter.x + AUTOSCROLL_MARGIN);
 525  
     }
 526  
 
 527  
     /**
 528  
      * The DropTargetListener.
 529  
      * Handles drop target events including the drop itself.
 530  
      */
 531  0
     class ArgoDropTargetListener implements DropTargetListener {
 532  
 
 533  
         private TreePath         lastPath;
 534  900
         private Rectangle2D cueLine = new Rectangle2D.Float();
 535  900
         private Rectangle2D ghostRectangle = new Rectangle2D.Float();
 536  
         private Color cueLineColor;
 537  900
         private Point lastMouseLocation = new Point();
 538  
         private Timer hoverTimer;
 539  
 
 540  
         /**
 541  
          * The constructor.
 542  
          */
 543  900
         public ArgoDropTargetListener() {
 544  900
             cueLineColor =
 545  
                 new Color(
 546  
                     SystemColor.controlShadow.getRed(),
 547  
                     SystemColor.controlShadow.getGreen(),
 548  
                     SystemColor.controlShadow.getBlue(),
 549  
                     64
 550  
                 );
 551  
 
 552  
             /* Set up a hover timer, so that a node will be
 553  
              * automatically expanded or collapsed
 554  
              * if the user lingers on it for more than a short time.
 555  
              */
 556  900
             hoverTimer =
 557  900
                 new Timer(1000, new ActionListener() {
 558  
                     /*
 559  
                      * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
 560  
                      */
 561  
                     public void actionPerformed(ActionEvent e) {
 562  0
                         if (getPathForRow(0).equals/*isRootPath*/(lastPath)) {
 563  0
                             return;
 564  
                         }
 565  0
                         if (isExpanded(lastPath)) {
 566  0
                             collapsePath(lastPath);
 567  
                         } else {
 568  0
                             expandPath(lastPath);
 569  
                         }
 570  0
                     }
 571  
                 });
 572  900
             hoverTimer.setRepeats(false);        // Set timer to one-shot mode
 573  900
         }
 574  
 
 575  
         /*
 576  
          * @see java.awt.dnd.DropTargetListener#dragEnter(java.awt.dnd.DropTargetDragEvent)
 577  
          */
 578  
         public void dragEnter(
 579  
                 DropTargetDragEvent dropTargetDragEvent) {
 580  0
             LOG.debug("dragEnter");
 581  0
             if (!isDragAcceptable(dropTargetDragEvent)) {
 582  0
                 dropTargetDragEvent.rejectDrag();
 583  
             } else {
 584  0
                 dropTargetDragEvent.acceptDrag(
 585  
                         dropTargetDragEvent.getDropAction());
 586  
             }
 587  0
         }
 588  
 
 589  
         /*
 590  
          * @see java.awt.dnd.DropTargetListener#dragExit(java.awt.dnd.DropTargetEvent)
 591  
          */
 592  
         public void dragExit(DropTargetEvent dropTargetEvent) {
 593  0
             LOG.debug("dragExit");
 594  0
             if (!DragSource.isDragImageSupported()) {
 595  0
                 repaint(ghostRectangle.getBounds());
 596  
             }
 597  0
         }
 598  
 
 599  
         /**
 600  
          * Called when a drag operation is ongoing, while the mouse pointer
 601  
          * is still over the operable part of the drop site
 602  
          * for the <code>DropTarget</code> registered with this listener.
 603  
          *
 604  
          * @see java.awt.dnd.DropTargetListener#dragOver(java.awt.dnd.DropTargetDragEvent)
 605  
          */
 606  
         public void dragOver(DropTargetDragEvent dropTargetDragEvent) {
 607  0
             Point pt = dropTargetDragEvent.getLocation();
 608  0
             if (pt.equals(lastMouseLocation)) {
 609  0
                 return;
 610  
             }
 611  
             /* Many many of these events .. this slows things down: */
 612  
 //            LOG.debug("dragOver");
 613  
 
 614  0
             lastMouseLocation = pt;
 615  
 
 616  0
             Graphics2D g2 = (Graphics2D) getGraphics();
 617  
 
 618  
             /*
 619  
              * The next condition becomes false when dragging in
 620  
              * something from another application.
 621  
              */
 622  0
             if (ghostImage != null) {
 623  
                 /*
 624  
                  * If a drag image is not supported by the platform,
 625  
                  * then draw my own drag image.
 626  
                  */
 627  0
                 if (!DragSource.isDragImageSupported()) {
 628  
                     /* Rub out the last ghost image and cue line: */
 629  0
                     paintImmediately(ghostRectangle.getBounds());
 630  
                     /* And remember where we are about to draw
 631  
                      * the new ghost image:
 632  
                      */
 633  0
                     ghostRectangle.setRect(pt.x - clickOffset.x,
 634  
                             pt.y - clickOffset.y,
 635  
                             ghostImage.getWidth(),
 636  
                             ghostImage.getHeight());
 637  0
                     g2.drawImage(ghostImage,
 638  
                             AffineTransform.getTranslateInstance(
 639  
                                     ghostRectangle.getX(),
 640  
                                     ghostRectangle.getY()), null);
 641  
                 } else {
 642  
                     // Just rub out the last cue line
 643  0
                     paintImmediately(cueLine.getBounds());
 644  
                 }
 645  
             }
 646  
 
 647  0
             TreePath path = getPathForLocation(pt.x, pt.y);
 648  0
             if (!(path == lastPath)) {
 649  0
                 lastPath = path;
 650  0
                 hoverTimer.restart();
 651  
             }
 652  
 
 653  
             /*
 654  
              * In any case draw (over the ghost image if necessary)
 655  
              * a cue line indicating where a drop will occur.
 656  
              */
 657  0
             Rectangle raPath = getPathBounds(path);
 658  0
             if (raPath != null) {
 659  0
                 cueLine.setRect(0,
 660  
                         raPath.y + (int) raPath.getHeight(),
 661  
                         getWidth(),
 662  
                         2);
 663  
             }
 664  
 
 665  0
             g2.setColor(cueLineColor);
 666  0
             g2.fill(cueLine);
 667  
 
 668  
             // And include the cue line in the area to be rubbed out next time
 669  0
             ghostRectangle = ghostRectangle.createUnion(cueLine);
 670  
 
 671  
             /* Testcase: drag something from another
 672  
              * application into ArgoUML,
 673  
              * and the explorer shows the drop icon, instead of the noDrop.
 674  
              */
 675  
             try {
 676  0
                 if (!dropTargetDragEvent.isDataFlavorSupported(
 677  
                         TransferableModelElements.UML_COLLECTION_FLAVOR)) {
 678  0
                     dropTargetDragEvent.rejectDrag();
 679  0
                     return;
 680  
                 }
 681  0
             } catch (NullPointerException e) {
 682  0
                 dropTargetDragEvent.rejectDrag();
 683  0
                 return;
 684  0
             }
 685  
 
 686  0
             if (path == null) {
 687  0
                 dropTargetDragEvent.rejectDrag();
 688  0
                 return;
 689  
             }
 690  
             // to prohibit dropping onto the drag source:
 691  0
             if (path.equals(sourcePath)) {
 692  0
                 dropTargetDragEvent.rejectDrag();
 693  0
                 return;
 694  
             }
 695  0
             if (selectedTreePath.isDescendant(path)) {
 696  0
                 dropTargetDragEvent.rejectDrag();
 697  0
                 return;
 698  
             }
 699  
 
 700  0
             Object dest =
 701  
                 ((DefaultMutableTreeNode) path
 702  
                     .getLastPathComponent()).getUserObject();
 703  
 
 704  
             /* If the destination is not a NameSpace, then reject: */
 705  0
             if (!Model.getFacade().isANamespace(dest)) {
 706  0
                 if (LOG.isDebugEnabled()) {
 707  
                     String name;
 708  0
                     if (Model.getFacade().isAUMLElement(dest)) {
 709  0
                         name = Model.getFacade().getName(dest);
 710  0
                     } else if (dest == null) {
 711  0
                         name = "<null>";
 712  
                     } else {
 713  0
                         name = dest.toString();
 714  
                     }
 715  0
                     LOG.debug("No valid Drag: "
 716  
                             + (Model.getFacade().isAUMLElement(dest) 
 717  
                                     ? name + " not a namespace."
 718  
                                     :  " not a UML element."));
 719  
                 }
 720  0
                 dropTargetDragEvent.rejectDrag();
 721  0
                 return;
 722  
             }
 723  
             /* We are sure "dest" is a Namespace now. */
 724  
 
 725  0
             if (Model.getModelManagementHelper().isReadOnly(dest)) {
 726  0
                 LOG.debug("No valid Drag: "
 727  
                         + "not an editable UML element (profile?).");
 728  0
                 return;
 729  
             }
 730  
 
 731  
             /* If the destination is a DataType, then reject: */
 732  0
             if (Model.getFacade().isADataType(dest)) {
 733  0
                 LOG.debug("No valid Drag: destination is a DataType.");
 734  0
                 dropTargetDragEvent.rejectDrag();
 735  0
                 return;
 736  
             }
 737  
 
 738  0
             dropTargetDragEvent.acceptDrag(
 739  
                     dropTargetDragEvent.getDropAction());
 740  0
         }
 741  
 
 742  
         /**
 743  
          * The drop: what is done when the mousebutton is released.
 744  
          *
 745  
          * @see java.awt.dnd.DropTargetListener#drop(java.awt.dnd.DropTargetDropEvent)
 746  
          */
 747  
         public void drop(DropTargetDropEvent dropTargetDropEvent) {
 748  0
             LOG.debug("dropping ... ");
 749  
             /* Prevent hover timer from doing an unwanted
 750  
              * expandPath or collapsePath:
 751  
              */
 752  0
             hoverTimer.stop();
 753  
 
 754  
             /* Clear the ghost image: */
 755  0
             repaint(ghostRectangle.getBounds());
 756  
 
 757  0
             if (!isDropAcceptable(dropTargetDropEvent)) {
 758  0
                 dropTargetDropEvent.rejectDrop();
 759  0
                 return;
 760  
             }
 761  
 
 762  
             try {
 763  0
                 Transferable tr = dropTargetDropEvent.getTransferable();
 764  
                 //get new parent node
 765  0
                 Point loc = dropTargetDropEvent.getLocation();
 766  0
                 TreePath destinationPath = getPathForLocation(loc.x, loc.y);
 767  0
                 if (LOG.isDebugEnabled()) {
 768  0
                     LOG.debug("Drop location: x=" + loc.x + " y=" + loc.y);
 769  
                 }
 770  
 
 771  0
                 if (!isValidDrag(destinationPath, tr)) {
 772  0
                     dropTargetDropEvent.rejectDrop();
 773  0
                     return;
 774  
                 }
 775  
 
 776  
                 //get the model elements that are being transfered.
 777  0
                 Collection modelElements =
 778  
                     (Collection) tr.getTransferData(
 779  
                         TransferableModelElements.UML_COLLECTION_FLAVOR);
 780  0
                 if (LOG.isDebugEnabled()) {
 781  0
                     LOG.debug("transfer data = " + modelElements);
 782  
                 }
 783  
                 
 784  0
                 Object dest =
 785  
                     ((DefaultMutableTreeNode) destinationPath
 786  
                         .getLastPathComponent()).getUserObject();
 787  0
                 Object src =
 788  
                     ((DefaultMutableTreeNode) sourcePath
 789  
                         .getLastPathComponent()).getUserObject();
 790  
 
 791  0
                 int action = dropTargetDropEvent.getDropAction();
 792  
                 /* The user-DropActions are:
 793  
                  * Ctrl + Shift -> ACTION_LINK
 794  
                  * Ctrl         -> ACTION_COPY
 795  
                  * Shift        -> ACTION_MOVE
 796  
                  * (none)       -> ACTION_MOVE
 797  
                  */
 798  0
                 boolean copyAction =
 799  
                     (action == DnDConstants.ACTION_COPY);
 800  0
                 boolean moveAction =
 801  
                     (action == DnDConstants.ACTION_MOVE);
 802  
 
 803  0
                 if (!(moveAction || copyAction)) {
 804  0
                     dropTargetDropEvent.rejectDrop();
 805  0
                     return;
 806  
                 }
 807  
 
 808  0
                 if (Model.getFacade().isAUMLElement(dest)) {
 809  0
                     if (Model.getModelManagementHelper().isReadOnly(dest)) {
 810  0
                         dropTargetDropEvent.rejectDrop();
 811  0
                         return;
 812  
                     }
 813  
                 }
 814  0
                 if (Model.getFacade().isAUMLElement(src)) {
 815  0
                     if (Model.getModelManagementHelper().isReadOnly(src)) {
 816  0
                         dropTargetDropEvent.rejectDrop();
 817  0
                         return;
 818  
                     }
 819  
                 }
 820  
                 
 821  
                 // TODO: Really should be Element/ModelElement, but we don't
 822  
                 // have a type which is portable for this
 823  0
                 Collection<Object> newTargets = new ArrayList<Object>();
 824  
                 try {
 825  0
                     dropTargetDropEvent.acceptDrop(action);
 826  0
                     for (Object me : modelElements) {
 827  0
                         if (Model.getFacade().isAUMLElement(me)) {
 828  0
                             if (Model.getModelManagementHelper().isReadOnly(me)) {
 829  0
                                 continue;
 830  
                             }
 831  
                         }
 832  0
                         if (LOG.isDebugEnabled()) {
 833  0
                             LOG.debug((moveAction ? "move " : "copy ") + me);
 834  
                         }
 835  0
                         if (Model.getCoreHelper().isValidNamespace(me, dest)) {
 836  0
                             if (moveAction) {
 837  0
                                 Model.getCoreHelper().setNamespace(me, dest);
 838  0
                                 newTargets.add(me);
 839  
                             }
 840  0
                             if (copyAction) {
 841  
                                 try {
 842  0
                                     newTargets.add(Model.getCopyHelper()
 843  
                                             .copy(me, dest));
 844  0
                                 } catch (RuntimeException e) {
 845  
                                     /* TODO: The copy function is not yet 
 846  
                                      * completely implemented - so we will 
 847  
                                      * have some exceptions here and there.*/
 848  0
                                     LOG.error("Exception", e);
 849  0
                                 }
 850  
                             }
 851  
                         }
 852  0
                         if (me instanceof Relocatable) {
 853  0
                             Relocatable d = (Relocatable) me;
 854  0
                             if (d.isRelocationAllowed(dest)) {
 855  0
                                 if (d.relocate(dest)) {
 856  0
                                     ExplorerEventAdaptor.getInstance()
 857  
                                         .modelElementChanged(src);
 858  0
                                     ExplorerEventAdaptor.getInstance()
 859  
                                         .modelElementChanged(dest);
 860  
                                     /*TODO: Make the tree refresh and expand
 861  
                                      * really work in all cases!
 862  
                                      */
 863  0
                                     makeVisible(destinationPath);
 864  0
                                     expandPath(destinationPath);
 865  0
                                     newTargets.add(me);
 866  
                                 }
 867  
                             }
 868  
                         }
 869  0
                         if (Model.getFacade().isAFeature(me) 
 870  
                                 && Model.getFacade().isAClassifier(dest)) {
 871  0
                             if (moveAction) {
 872  0
                                 Model.getCoreHelper().removeFeature(
 873  
                                         Model.getFacade().getOwner(me), me);
 874  0
                                 Model.getCoreHelper().addFeature(dest, me);
 875  0
                                 newTargets.add(me);
 876  
                             }
 877  0
                             if (copyAction) {
 878  0
                                 newTargets.add(
 879  
                                         Model.getCopyHelper().copy(me, dest));
 880  
                             }
 881  
                         }
 882  
                     }
 883  0
                     dropTargetDropEvent.getDropTargetContext()
 884  
                         .dropComplete(true);
 885  0
                     TargetManager.getInstance().setTargets(newTargets);
 886  0
                 } catch (java.lang.IllegalStateException ils) {
 887  0
                     LOG.debug("drop IllegalStateException");
 888  0
                     dropTargetDropEvent.rejectDrop();
 889  0
                 }
 890  
 
 891  0
                 dropTargetDropEvent.getDropTargetContext()
 892  
                     .dropComplete(true);
 893  0
             } catch (IOException io) {
 894  0
                 LOG.debug("drop IOException");
 895  0
                 dropTargetDropEvent.rejectDrop();
 896  0
             } catch (UnsupportedFlavorException ufe) {
 897  0
                 LOG.debug("drop UnsupportedFlavorException");
 898  0
                 dropTargetDropEvent.rejectDrop();
 899  0
             }
 900  0
         }
 901  
 
 902  
         /*
 903  
          * @see java.awt.dnd.DropTargetListener#dropActionChanged(java.awt.dnd.DropTargetDragEvent)
 904  
          */
 905  
         public void dropActionChanged(
 906  
                 DropTargetDragEvent dropTargetDragEvent) {
 907  0
             if (!isDragAcceptable(dropTargetDragEvent)) {
 908  0
                 dropTargetDragEvent.rejectDrag();
 909  
             } else {
 910  0
                 dropTargetDragEvent.acceptDrag(
 911  
                         dropTargetDragEvent.getDropAction());
 912  
             }
 913  0
         }
 914  
 
 915  
         /**
 916  
          * @param dropTargetEvent the droptargetevent
 917  
          * @return true if the drag is acceptable
 918  
          */
 919  
         public boolean isDragAcceptable(
 920  
                 DropTargetDragEvent dropTargetEvent) {
 921  
             // Only accept COPY or MOVE gestures (ie LINK is not supported)
 922  0
             if ((dropTargetEvent.getDropAction()
 923  
                     & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
 924  0
                 return false;
 925  
             }
 926  
 
 927  
             // Do this if you want to prohibit dropping onto the drag source...
 928  0
             Point pt = dropTargetEvent.getLocation();
 929  0
             TreePath path = getPathForLocation(pt.x, pt.y);
 930  0
             if (path == null) {
 931  0
                 return false;
 932  
             }
 933  0
             if (path.equals(sourcePath)) {
 934  0
                 return false;
 935  
             }
 936  0
             return true;
 937  
         }
 938  
 
 939  
         /**
 940  
          * @param dropTargetDropEvent the droptargetdropevent
 941  
          * @return true if the drop is acceptable
 942  
          */
 943  
         public boolean isDropAcceptable(
 944  
                 DropTargetDropEvent dropTargetDropEvent) {
 945  
             // Only accept COPY or MOVE gestures (ie LINK is not supported)
 946  0
             if ((dropTargetDropEvent.getDropAction()
 947  
                     & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
 948  0
                 return false;
 949  
             }
 950  
 
 951  
             // Do this if you want to prohibit dropping onto the drag source...
 952  0
             Point pt = dropTargetDropEvent.getLocation();
 953  0
             TreePath path = getPathForLocation(pt.x, pt.y);
 954  0
             if (path == null) {
 955  0
                 return false;
 956  
             }
 957  0
             if (path.equals(sourcePath)) {
 958  0
                 return false;
 959  
             }
 960  0
             return true;
 961  
         }
 962  
 
 963  
     } /* end class */
 964  
 
 965  
 
 966  
     /**
 967  
      * The UID.
 968  
      */
 969  
     private static final long serialVersionUID = 6207230394860016617L;
 970  
 }
 971  
 
 972