Coverage Report - org.argouml.uml.diagram.ui.PathItemPlacement
 
Classes in this File Line Coverage Branch Coverage Complexity
PathItemPlacement
0%
0/173
0%
0/50
2.25
 
 1  
 /* $Id: PathItemPlacement.java 17865 2010-01-12 20:45:26Z 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  
  *    dthompson
 11  
  *****************************************************************************
 12  
  *
 13  
  * Some portions of this file was previously release using the BSD License:
 14  
  */
 15  
 
 16  
 // Copyright (c) 2008 Tom Morris and other contributors. 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 Contributors.
 22  
 // The software program and documentation are supplied "AS
 23  
 // IS", without any accompanying services from The Contributors. They
 24  
 // do 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  
 // CONTRIBUTORS 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 CONTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF
 32  
 // SUCH DAMAGE. THE CONTRIBUTORS SPECIFICALLY DISCLAIM 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 CONTRIBUTORS
 36  
 // HAVE NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 37  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 38  
 
 39  
 package org.argouml.uml.diagram.ui;
 40  
 
 41  
 import java.awt.Color;
 42  
 import java.awt.Dimension;
 43  
 import java.awt.Graphics;
 44  
 import java.awt.Point;
 45  
 import java.awt.Rectangle;
 46  
 import java.awt.geom.Line2D;
 47  
 
 48  
 import org.apache.log4j.Logger;
 49  
 import org.tigris.gef.base.Globals;
 50  
 import org.tigris.gef.base.PathConv;
 51  
 import org.tigris.gef.presentation.Fig;
 52  
 import org.tigris.gef.presentation.FigEdge;
 53  
 
 54  
 /**
 55  
  * This class implements the coordinate generation required for GEF's
 56  
  * FigEdge.addPathItem. It can be used to place labels at an offset relative to
 57  
  * an anchor position along the path described by a FigEdge. For example, a
 58  
  * label can be placed in the middle of a FigEdge by using 50% or near an end by
 59  
  * using 0% width an offset of +5 or 100% with an offset of -5.
 60  
  * <p>
 61  
  * The calculated anchor position along the path is then used as a base to which
 62  
  * additional offsets are added. This can be either in the form of a
 63  
  * displacement vector and distance specified using an angle relative to the
 64  
  * angle of the edge at that point or a fixed x,y offset.
 65  
  * <p>
 66  
  * This class tries to avoid placing the itemFig so that it intersects the
 67  
  * pathFig. Note that:<ul>
 68  
  * <li>itemFig must return correct size information for this to work properly,
 69  
  * which is not currently true of all GEF figs (eg. text figs).
 70  
  * <li>Only the path is considered, so you can still get overlaps with the
 71  
  * connected nodes on the ends of the edges or other labels on the same edge or
 72  
  * other figs in the diagram. Using a displacement angle of 135 or -135 degrees
 73  
  * is a good way to help avoid the connected nodes.
 74  
  * </ul>
 75  
  * 
 76  
  * @author Tom Morris <tfmorris@gmail.com>
 77  
  * @since 0.27.3
 78  
  */
 79  
 public class PathItemPlacement extends PathConv {
 80  
     
 81  0
     private static final Logger LOG = Logger.getLogger(PathItemPlacement.class);
 82  
 
 83  0
     private boolean useCollisionCheck = true;
 84  
     
 85  0
     private boolean useAngle = true;
 86  
 
 87  0
     private double angle = 90; // default angle is 90 deg.
 88  
     
 89  
     /**
 90  
      * the fig to be placed.
 91  
      */
 92  
     private Fig itemFig;
 93  
 
 94  
     /**
 95  
      * Percentage of the way along the path to place anchor.
 96  
      */
 97  
     private int percent;
 98  
 
 99  
     /**
 100  
      * Fixed delta offset from the computed percentage location.
 101  
      */
 102  
     private int pathOffset;
 103  
     
 104  
     /**
 105  
      * Distance along the displacement vector (ie distance from the edge)
 106  
      */
 107  
     private int vectorOffset;
 108  
 
 109  
     /**
 110  
      * Fixed offset to use for manual positioning.  Coordinates are interpreted
 111  
      * as an XY offset.
 112  
      */
 113  
     private Point offset;
 114  
     
 115  
     /**
 116  
      * Set true to keep items on same side (top or bottom) of path as
 117  
      * it rotates through vertical.
 118  
      */
 119  0
     private final boolean swap = true;
 120  
     
 121  
     /**
 122  
      * Construct a new path to coordinate conversion object which positions at a
 123  
      * percentage along a path with a given distance perpendicular to the path
 124  
      * at the anchor point.
 125  
      * 
 126  
      * @param pathFig fig representing the edge which will be used for
 127  
      *            positioning.
 128  
      * @param theItemFig the fig to be placed.
 129  
      * @param pathPercent distance in integer percentages along path for anchor
 130  
      *            point from which the offset is computed.. Beginning of path is
 131  
      *            0 and end of path is 100.
 132  
      * @param displacement distance from the edge to place the fig. This is
 133  
      *            computed along the normal.
 134  
      */
 135  
     public PathItemPlacement(FigEdge pathFig, Fig theItemFig, int pathPercent,
 136  
             int displacement) {
 137  
 
 138  0
         this(pathFig, theItemFig, pathPercent, 0, 90, displacement);
 139  0
     }
 140  
 
 141  
     
 142  
     /**
 143  
      * Construct a new path to coordinate conversion object which positions
 144  
      * an anchor point on the path at a percentage along a path with an offset, 
 145  
      * and from the anchor point at a distance measured at a given angle.
 146  
      * 
 147  
      * @param pathFig fig representing the edge which will be used for
 148  
      *            positioning.
 149  
      * @param theItemFig the fig to be placed.
 150  
      * @param pathPercent distance in integer percentages along path for anchor
 151  
      *            point from which the offset is computed. Beginning of path is
 152  
      *            0 and end of path is 100.
 153  
      * @param pathDelta delta distance in coordinate space units to add to the
 154  
      *            computed percentage position
 155  
      * @param displacementAngle angle to add to computed line slope when
 156  
      *            computing the displacement vector
 157  
      * @param displacementDistance distance from the edge to place the fig. This
 158  
      *            is computed along the normal from the anchor position using
 159  
      *            pathPercent & pathDelta.
 160  
      */
 161  
     public PathItemPlacement(FigEdge pathFig, Fig theItemFig, int pathPercent,
 162  
             int pathDelta,
 163  
             int displacementAngle,
 164  
             int displacementDistance) {
 165  0
         super(pathFig);
 166  0
         itemFig = theItemFig;
 167  0
         setAnchor(pathPercent, pathDelta);
 168  0
         setDisplacementVector(displacementAngle + 180, displacementDistance);
 169  0
     }
 170  
 
 171  
     /**
 172  
      * Construct a new path to coordinate conversion object which positions
 173  
      * an anchor point on the path at a percentage along a path with an offset, 
 174  
      * and from the anchor point at a distance measured in X, Y coordinates.
 175  
      * 
 176  
      * @param pathFig fig representing the edge which will be used for
 177  
      *            positioning.
 178  
      * @param theItemFig the fig to be placed.
 179  
      * @param pathPercent distance in integer percentages along path for anchor
 180  
      *            point from which the offset is computed. Beginning of path is
 181  
      *            0 and end of path is 100.
 182  
      * @param pathDelta delta distance in coordinate space units to add to the
 183  
      *            computed percentage position
 184  
      * @param absoluteOffset point representing XY offset from anchor to use for
 185  
      *            positioning.
 186  
      */
 187  
     public PathItemPlacement(FigEdge pathFig, Fig theItemFig, int pathPercent,
 188  
             int pathDelta, Point absoluteOffset) {
 189  0
         super(pathFig);
 190  0
         itemFig = theItemFig;
 191  0
         setAnchor(pathPercent, pathDelta);
 192  0
         setAbsoluteOffset(absoluteOffset);
 193  0
     }
 194  
     
 195  
     /**
 196  
      * Returns the Fig that this PathItemPlacement places.
 197  
      * To get the Fig of the Edge which owns this fig, use use getPathFig()
 198  
      * @see org.tigris.gef.base.PathConv#getPathFig()
 199  
      * @note Used by PGML.tee.
 200  
      * @return The fig that this path item places.
 201  
      */
 202  
     public Fig getItemFig() {
 203  0
         return itemFig;
 204  
     }
 205  
 
 206  
     /**
 207  
      * Compute a position.  This strangely named method computes a
 208  
      * position using the current set of parameters and returns the result
 209  
      * by updating the provided Point.
 210  
      * 
 211  
      * @param result Point in which to return result.  Not read as input.
 212  
      * 
 213  
      * @see org.tigris.gef.base.PathConv#stuffPoint(java.awt.Point)
 214  
      */
 215  
     public void stuffPoint(Point result) {
 216  0
         result = getPosition(result);
 217  0
     }
 218  
 
 219  
     /**
 220  
      * Get the computed target position based on the current set of parameters.
 221  
      * 
 222  
      * @return the computed position
 223  
      */
 224  
     public Point getPosition() {
 225  0
         return getPosition(new Point());
 226  
     }
 227  
     
 228  
     @Override
 229  
     public Point getPoint() {
 230  0
         return getPosition();
 231  
     }
 232  
 
 233  
     /**
 234  
      * Get the anchor position.  The represents the point along the path that
 235  
      * is used as the starting point for all other calculations.
 236  
      * 
 237  
      * @return the anchor position represented by the current percentage and
 238  
      *         path offset parameters
 239  
      */
 240  
     public Point getAnchorPosition() {
 241  0
         int pathDistance = getPathDistance();
 242  0
         Point anchor = new Point();
 243  0
         _pathFigure.stuffPointAlongPerimeter(pathDistance, anchor);
 244  0
         return anchor;
 245  
     }
 246  
 
 247  
 
 248  
     /**
 249  
      * Compute distance along the path based on percentage and offset, clamped
 250  
      * to the length of the path.
 251  
      * 
 252  
      * @return the distance
 253  
      */
 254  
     private int getPathDistance() {
 255  0
         int length = _pathFigure.getPerimeterLength();
 256  0
         int distance = Math.max(0, (length * percent) / 100 + pathOffset);
 257  
         // Boundary condition in GEF, make sure this is LESS THAN, not equal
 258  0
         if (distance >= length) {
 259  0
             distance = length - 1;
 260  
         }
 261  0
         return distance;  
 262  
     }
 263  
     
 264  
 
 265  
     /**
 266  
      * Get the computed position based on the current set of parameters.
 267  
      * 
 268  
      * @param result Point in which to return result.  Not read as input, but it
 269  
      * <em>is</em> modified.
 270  
      * @return the updated point
 271  
      */
 272  
     private Point getPosition(Point result) {
 273  
 
 274  0
         Point anchor = getAnchorPosition();
 275  0
         result.setLocation(anchor);
 276  
 
 277  
         // If we're using a fixed offset, just add it and return
 278  
         // No collision detection is done in this case
 279  0
         if (!useAngle) {
 280  0
             result.translate(offset.x, offset.y);
 281  0
             return result;
 282  
         }
 283  
         
 284  0
         double slope = getSlope();
 285  0
         result.setLocation(applyOffset(slope, vectorOffset, anchor));
 286  
 
 287  
         // Check for a collision between our computed position and the edge
 288  0
         if (useCollisionCheck) {
 289  0
             int increment = 2; // increase offset by 2px at a time
 290  
 
 291  
             // TODO: The size of text figs, which is what we care about most,
 292  
             // isn't computed correctly by GEF. If we got ambitious, we could
 293  
             // recompute a proper size ourselves.
 294  0
             Dimension size = new Dimension(itemFig.getWidth(), itemFig
 295  
                     .getHeight());
 296  
 
 297  
             // Get the points representing the poly line for our edge
 298  0
             FigEdge fp = (FigEdge) _pathFigure;
 299  0
             Point[] points = fp.getPoints();
 300  0
             if (intersects(points, result, size)) {
 301  
 
 302  
                 // increase offset by increments until we're clear
 303  0
                 int scaledOffset = vectorOffset + increment;
 304  
 
 305  0
                 int limit = 20;
 306  0
                 int count = 0;
 307  
                 // limit our retries in case its too hard to get free
 308  0
                 while (intersects(points, result, size) && count++ < limit) {
 309  0
                     result.setLocation(
 310  
                             applyOffset(slope, scaledOffset, anchor));
 311  0
                     scaledOffset += increment;
 312  
                 }
 313  
                 // If we timed out, give it one more try on the other side
 314  
                 if (false /* count >= limit */) {
 315  
                     LOG.debug("Retry limit exceeded.  Trying other side");
 316  
                     result.setLocation(anchor);
 317  
                     // TODO: This works for 90 degree angles, but is suboptimal
 318  
                     // for other angles. It should reflect the angle, rather
 319  
                     // than just using a negative offset along the same vector
 320  
                     result.setLocation(
 321  
                             applyOffset(slope, -vectorOffset, anchor));
 322  
                     count = 0;
 323  
                     scaledOffset = -scaledOffset;
 324  
                     while (intersects(points, result, size) 
 325  
                             && count++ < limit) {
 326  
                         result.setLocation(
 327  
                                 applyOffset(slope, scaledOffset, anchor));
 328  
                         scaledOffset += increment;
 329  
                     }
 330  
                 }
 331  
 //                LOG.debug("Final point #" + count + " " + result
 332  
 //                        + " offset of " + scaledOffset);
 333  
             }
 334  
         }
 335  0
         return result;
 336  
     }
 337  
 
 338  
     /**
 339  
      * Check for intersection between the segments of a poly line and a
 340  
      * rectangle.  Unlike FigEdge.intersects(), this only checks the main
 341  
      * path, not any associated path items (like ourselves).
 342  
      * 
 343  
      * @param points set of points representing line segments
 344  
      * @param center position of center
 345  
      * @param size size of bounding box
 346  
      * @return true if they intersect
 347  
      */
 348  
     private boolean intersects(Point[] points, Point center, Dimension size) {
 349  
         // Convert to bounding box
 350  
         // Very screwy!  GEF sometimes uses center and sometimes upper left
 351  
         // TODO: GEF also positions text at the nominal baseline which is
 352  
         // well inside the bounding box and gives the overall size incorrectly
 353  0
         Rectangle r = new Rectangle(center.x - (size.width / 2), 
 354  
                 center.y - (size.height / 2), 
 355  
                 size.width, size.height);
 356  0
         Line2D line = new Line2D.Double();
 357  0
         for (int i = 0; i < points.length - 1; i++) {
 358  0
             line.setLine(points[i], points[i + 1]);
 359  0
             if (r.intersectsLine(line)) {
 360  0
                 return true;
 361  
             }
 362  
         }
 363  0
         return false;
 364  
     }
 365  
 
 366  
     /**
 367  
      * Convenience method to set anchor percentage distance and offset.
 368  
      * 
 369  
      * @param newPercent distance as a percent of total path 0<=percent<=100
 370  
      * @param newOffset offset in drawing coordinate system
 371  
      */
 372  
     public void setAnchor(int newPercent, int newOffset) {
 373  0
         setAnchorPercent(newPercent);
 374  0
         setAnchorOffset(newOffset);
 375  0
     }
 376  
     
 377  
     /**
 378  
      * Set distance along path of anchor in integer percentages.
 379  
      * @param newPercent distance as a percent of total path 0<=percent<=100
 380  
      */
 381  
     public void setAnchorPercent(int newPercent) {
 382  0
         percent = newPercent;
 383  0
     }
 384  
     
 385  
     /**
 386  
      * Set offset along path to be applied to anchor after percentage based
 387  
      * location is calculated. Specified in units of the drawing coordinate
 388  
      * system.
 389  
      * 
 390  
      * @param newOffset offset in drawing coordinate system
 391  
      */
 392  
     public void setAnchorOffset(int newOffset) {
 393  0
         pathOffset = newOffset;
 394  0
     }
 395  
     
 396  
     /**
 397  
      * Set a fixed offset from the anchor point.
 398  
      * @param newOffset a Point who's x & y coordinates will be used as a 
 399  
      * displacement from anchor point
 400  
      */
 401  
     public void setAbsoluteOffset(Point newOffset) {
 402  0
         offset = newOffset;
 403  0
         useAngle = false;
 404  0
     }
 405  
     
 406  
     /**
 407  
      * Attempts to set a new location for the fig being controlled
 408  
      * by this path item.  Takes the given Point which represents an x,y 
 409  
      * position, and calculates the most appropriate angle and displacement
 410  
      * to achieve this new position.  Used when the user drags a label
 411  
      * on the diagram.  
 412  
      * @override
 413  
      * @param newPoint The new target location for the PathItem fig.
 414  
      * @see org.tigris.gef.base.PathConv#setPoint(java.awt.Point)
 415  
      */
 416  
     public void setPoint(Point newPoint) {
 417  0
         int vect[] = computeVector(newPoint);
 418  0
         setDisplacementAngle(vect[0]);
 419  0
         setDisplacementDistance(vect[1]);
 420  0
     }
 421  
 
 422  
 
 423  
     /**
 424  
      * Compute an angle and distance which is equivalent to the given point.
 425  
      * This is a convenience method to help callers get coordinates in a form
 426  
      * that can be passed back in using {@link #setDisplacementVector(int, int)}
 427  
      * 
 428  
      * @param point the desired target point
 429  
      * @return an array of two integers containing the angle and distance
 430  
      */
 431  
     public int[] computeVector(Point point) {
 432  0
         Point anchor = getAnchorPosition();
 433  0
         int distance = (int) anchor.distance(point);
 434  0
         int angl = 0;
 435  0
         double pathSlope = getSlope();
 436  0
         double offsetSlope = getSlope(anchor, point);
 437  
 
 438  0
         if (swap && pathSlope > Math.PI / 2 && pathSlope < Math.PI * 3 / 2) {
 439  0
             angl = -(int) ((offsetSlope - pathSlope) / Math.PI * 180);
 440  
         }
 441  
         else {
 442  0
             angl = (int) ((offsetSlope - pathSlope) / Math.PI * 180);
 443  
         }
 444  
 
 445  0
         int[] result = new int[] {angl, distance};
 446  0
         return result;
 447  
     }
 448  
     
 449  
     /**
 450  
      * Set the displacement vector to the given angle and distance.
 451  
      * 
 452  
      * @param vectorAngle angle in degrees relative to the edge at the anchor
 453  
      *            point.
 454  
      * @param vectorDistance distance along vector in drawing coordinate units
 455  
      */
 456  
     public void setDisplacementVector(int vectorAngle, int vectorDistance) {
 457  0
         setDisplacementAngle(vectorAngle);
 458  0
         setDisplacementDistance(vectorDistance);
 459  0
     }
 460  
     
 461  
     /**
 462  
      * Set the displacement vector to the given angle and distance.
 463  
      * 
 464  
      * @param vectorAngle angle in degrees relative to the edge at the anchor
 465  
      *            point.
 466  
      * @param vectorDistance distance along vector in drawing coordinate units
 467  
      */
 468  
     public void setDisplacementVector(double vectorAngle, 
 469  
             int vectorDistance) {
 470  0
         setDisplacementAngle(vectorAngle);
 471  0
         setDisplacementDistance(vectorDistance);
 472  0
     }
 473  
     
 474  
     /**
 475  
      * @param offsetAngle the new angle for the displacement vector, 
 476  
      * specified in degrees relative to the edge at the anchor.
 477  
      */
 478  
     public void setDisplacementAngle(int offsetAngle) {
 479  0
         angle = offsetAngle * Math.PI / 180.0;
 480  0
         useAngle = true;
 481  0
     }
 482  
 
 483  
     /**
 484  
      * @param offsetAngle the new angle for the displacement vector, 
 485  
      * specified in degrees relative to the edge at the anchor.
 486  
      */
 487  
     public void setDisplacementAngle(double offsetAngle) {
 488  0
         angle = offsetAngle * Math.PI / 180.0;
 489  0
         useAngle = true;
 490  0
     }
 491  
 
 492  
     /**
 493  
      * Set distance along displacement vector to place the figure.
 494  
      * @param newDistance distance in units of the drawing coordinate system
 495  
      */
 496  
     public void setDisplacementDistance(int newDistance) {
 497  0
         vectorOffset = newDistance;
 498  0
         useAngle = true;
 499  0
     }
 500  
     
 501  
 
 502  
     /**
 503  
      * Don't know what this is supposed to do since GEF has no API spec for it,
 504  
      * but we don't implement it and it'll throw an
 505  
      * UnsupportedOperationException if you try to use it.
 506  
      * 
 507  
      * @param newPoint ignored
 508  
      * @see org.tigris.gef.base.PathConv#setClosestPoint(java.awt.Point)
 509  
      */
 510  
     public void setClosestPoint(Point newPoint) {
 511  0
         throw new UnsupportedOperationException();
 512  
     }
 513  
 
 514  
 
 515  
     /**
 516  
      * Compute slope of path at the anchor point.  Slope is computed using a
 517  
      * short segment instead of using the instantaneous slope, so it will give
 518  
      * unusual results near discontinuities in the path (ie bends).
 519  
      * @return the slope radians in the range 0 < slope < 2PI
 520  
      */
 521  
     private double getSlope() {
 522  
 
 523  0
         final int slopeSegLen = 40; // segment size for computing slope
 524  
 
 525  0
         int pathLength = _pathFigure.getPerimeterLength();
 526  0
         int pathDistance = getPathDistance();
 527  
         
 528  
         // Two points for line segment used to compute slope of path here
 529  
         // NOTE that this is the average slope, not instantaneous, so it will
 530  
         // give screwy results near bends in the path
 531  0
         int d1 = Math.max(0, pathDistance - slopeSegLen / 2);
 532  
         // If our position was clamped, try to make it up on the other end
 533  0
         int d2 = Math.min(pathLength - 1, d1 + slopeSegLen);
 534  
         // Can't get the slope of a point.  Just return an arbitrary point.
 535  0
         if (d1 == d2) {
 536  0
             return 0;
 537  
         }
 538  0
         Point p1 = _pathFigure.pointAlongPerimeter(d1);
 539  0
         Point p2 = _pathFigure.pointAlongPerimeter(d2);
 540  
         
 541  0
         double theta = getSlope(p1, p2);
 542  0
         return theta;
 543  
     }
 544  
 
 545  
 
 546  
     /**
 547  
      * Compute the slope in radians of the line between two points.
 548  
      * @param p1 first point 
 549  
      * @param p2 second point
 550  
      * @return slope in radians in the range 0<=slope<=2PI
 551  
      */
 552  
     private static double getSlope(Point p1, Point p2) {
 553  
         // Our angle theta is arctan(opposite/adjacent)
 554  
         // Because y increases going down the screen, positive angles are
 555  
         // clockwise rather than counterclockwise
 556  0
         int opposite = p2.y - p1.y;
 557  0
         int adjacent = p2.x - p1.x;
 558  
         double theta;
 559  0
         if (adjacent == 0) {
 560  
             // This shouldn't happen, because of our line segment size check
 561  0
             if (opposite == 0) {
 562  0
                 return 0;
 563  
             }
 564  
             // "We're going vertical!" - Goose in "Top Gun"
 565  0
             if (opposite < 0) {
 566  0
                 theta = Math.PI * 3 / 2;
 567  
             } else {
 568  0
                 theta = Math.PI / 2;
 569  
             }
 570  
         } else {
 571  
             // Arctan only returns -PI/2 to PI/2
 572  
             // Handle the other two quadrants and normalize to 0 - 2PI
 573  0
             theta = Math.atan((double) opposite / (double) adjacent);
 574  
             // Quadrant II & III
 575  0
             if (adjacent < 0) {
 576  0
                 theta += Math.PI;
 577  
             }
 578  
             // Quadrant IV
 579  0
             if (theta < 0) {
 580  0
                 theta += Math.PI * 2;
 581  
             } 
 582  
         }
 583  0
         return theta;
 584  
     }
 585  
 
 586  
     /**
 587  
      * Apply an offset for a given distance along the normal vector computed
 588  
      * to the line specified by the two points.
 589  
      * 
 590  
      * @param p1 point one of line to use in computing normal vector
 591  
      * @param p2 point two of line to use in computing normal vector
 592  
      * @param theOffset distance to displace fig along normal vector
 593  
      * @param anchor The start point to apply the offset from.  Not modified.
 594  
      * @return A new computed point describing the location after the offset 
 595  
      * has been applied to the anchor.
 596  
      */
 597  
     private Point applyOffset(double theta, int theOffset, 
 598  
             Point anchor) {
 599  
      
 600  0
         Point result = new Point(anchor);
 601  
         
 602  
         // Set the following for some backward compatibility with old algorithm
 603  0
         final boolean aboveAndRight = false;
 604  
 
 605  
 //        LOG.debug("Slope = " + theta / Math.PI + "PI " 
 606  
 //                + theta / Math.PI * 180.0);
 607  
         
 608  
         // Add displacement angle to slope
 609  0
         if (swap && theta > Math.PI / 2 && theta < Math.PI * 3 / 2) {
 610  0
             theta = theta - angle;
 611  
         } else {
 612  0
             theta = theta + angle;
 613  
         }
 614  
 
 615  
         // Transform to 0 - 2PI range if we've gone all the way around circle
 616  0
         if (theta > Math.PI * 2) {
 617  0
             theta -= Math.PI * 2;
 618  
         }
 619  0
         if (theta < 0) {
 620  0
             theta += Math.PI * 2;
 621  
         }
 622  
 
 623  
         // Compute our deltas
 624  0
         int dx = (int) (theOffset * Math.cos(theta));
 625  0
         int dy = (int) (theOffset * Math.sin(theta));
 626  
         
 627  
         // For backward compatibility everything is above and right
 628  
         // TODO: Do in polar domain?
 629  
         if (aboveAndRight) {
 630  
             dx = Math.abs(dx);
 631  
             dy = -Math.abs(dy);
 632  
         }
 633  
 
 634  0
         result.x += dx;
 635  0
         result.y += dy;
 636  
         
 637  
 //        LOG.debug(result.x + ", " + result.y 
 638  
 //                + " theta = " + theta * 180 / Math.PI
 639  
 //                + " dx = " + dx + " dy = " + dy);
 640  
         
 641  0
         return result;
 642  
     }
 643  
     
 644  
     /**
 645  
      * Paint the virtual connection from the edge to where the path item
 646  
      * is placed according to this path item placement algorithm.
 647  
      * 
 648  
      * @param g the Graphics object
 649  
      * @see org.tigris.gef.base.PathConv#paint(java.awt.Graphics)
 650  
      */
 651  
     public void paint(Graphics g) {
 652  0
         final Point p1 = getAnchorPosition();
 653  0
         Point p2 = getPoint();
 654  0
         Rectangle r = itemFig.getBounds();
 655  
         // Load the standard colour, just add an alpha channel.
 656  0
         Color c = Globals.getPrefs().handleColorFor(itemFig);
 657  0
         c = new Color(c.getRed(), c.getGreen(), c.getBlue(), 100);
 658  0
         g.setColor(c);
 659  0
         r.grow(2, 2);
 660  0
         g.fillRoundRect(r.x, r.y, r.width, r.height, 8, 8);
 661  0
         if (r.contains(p2)) {
 662  0
             p2 = getRectLineIntersection(r, p1, p2);     
 663  
         }
 664  0
         g.drawLine(p1.x, p1.y, p2.x, p2.y);
 665  0
     }
 666  
     
 667  
     /**
 668  
      * Finds the point where a rectangle and line intersect.
 669  
      * Finds the intersection point between the border of a Rectangle r and 
 670  
      * a line drawn between two Points pOut (outside the rectangle) and pIn 
 671  
      * (inside the rectangle).
 672  
      * If the pIn is not inside the rectangle, or if any other problem occurs,
 673  
      * pIn is returned. 
 674  
      * @param r Rectangle to find the intersection of.
 675  
      * @param pOut Point outside the rectangle.
 676  
      * @param pIn Point inside the rectangle.
 677  
      * @return The intersection between Line(pOut, pIn) and Rectangle r.
 678  
      */
 679  
     private Point getRectLineIntersection(Rectangle r, Point pOut, Point pIn) {
 680  
         Line2D.Double m, n;
 681  0
         m = new Line2D.Double(pOut, pIn);
 682  0
         n = new Line2D.Double(r.x, r.y, r.x + r.width, r.y);
 683  0
         if (m.intersectsLine(n)) {
 684  0
             return intersection(m, n);
 685  
         }
 686  0
         n = new Line2D.Double(r.x + r.width, r.y, r.x + r.width, 
 687  
                 r.y + r.height);
 688  0
         if (m.intersectsLine(n)) {
 689  0
             return intersection(m, n);
 690  
         }
 691  0
         n = new Line2D.Double(r.x, r.y + r.height, r.x + r.width, 
 692  
                 r.y + r.height);
 693  0
         if (m.intersectsLine(n)) {
 694  0
             return intersection(m, n);
 695  
         }
 696  0
         n = new Line2D.Double(r.x, r.y, r.x, r.y + r.width);
 697  0
         if (m.intersectsLine(n)) {
 698  0
             return intersection(m, n);
 699  
         }
 700  
         // Should never get here.  If we do, return the inner point.
 701  0
         LOG.warn("Could not find rectangle intersection, using inner point.");
 702  0
         return pIn;
 703  
     }
 704  
     
 705  
     /**
 706  
      * Finds the intersection point of two lines.
 707  
      * It is surprising that this method isn't already available in the base 
 708  
      * Line2D class of Java.  If a stock method exists or is implemented in
 709  
      * future, feel free replace this code with it.
 710  
      * @param m First line.
 711  
      * @param n Second line.
 712  
      * @return Intersection point of first and second line.
 713  
      */
 714  
     private Point intersection(Line2D m, Line2D n) {
 715  0
         double d = (n.getY2() - n.getY1()) * (m.getX2() - m.getX1()) 
 716  
                 - (n.getX2() - n.getX1()) * (m.getY2() - m.getY1());
 717  0
         double a = (n.getX2() - n.getX1()) * (m.getY1() - n.getY1()) 
 718  
                 - (n.getY2() - n.getY1()) * (m.getX1() - n.getX1());
 719  
         
 720  0
         double as = a / d;
 721  
         
 722  0
         double x = m.getX1() + as * (m.getX2() - m.getX1());
 723  0
         double y = m.getY1() + as * (m.getY2() - m.getY1());
 724  0
         return new Point((int) x, (int) y);
 725  
     }
 726  
     
 727  
     /**
 728  
      * Returns the value of the percent field - the position of the anchor
 729  
      * point as a percentage of the edge.
 730  
      * @important Used by PGML.tee.
 731  
      * @return The value of the percent field. 
 732  
      */
 733  
     public int getPercent() {
 734  0
         return percent;
 735  
     }
 736  
     
 737  
     /**
 738  
      * Returns the value of the angle field converted to degrees.
 739  
      * The angle of the path item relative to the edge.
 740  
      * @important Used by PGML.tee.
 741  
      * @return The value of the angle field in degrees.
 742  
      */
 743  
     public double getAngle() {
 744  0
         return angle * 180 / Math.PI;
 745  
     }
 746  
     
 747  
     /**
 748  
      * Returns the value of the vectorOffset field.
 749  
      * The vectorOffset field is the distance away from the edge, along the 
 750  
      * path vector that the item Fig is placed.
 751  
      * @important Used by PGML.tee.
 752  
      * @return The value of the vectorOffset field.
 753  
      */
 754  
     public int getVectorOffset() {
 755  0
         return vectorOffset;
 756  
     }
 757  
     /** End of methods used by PGML.tee */
 758  
     
 759  
 }