001/* 002 003Copyright (c) 2011-2016 The Regents of the University of California. 004All rights reserved. 005 006Permission is hereby granted, without written agreement and without 007license or royalty fees, to use, copy, modify, and distribute this 008software and its documentation for any purpose, provided that the above 009copyright notice and the following two paragraphs appear in all copies 010of this software. 011 012IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA LIABLE TO ANY PARTY 013FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016SUCH DAMAGE. 017 018THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023ENHANCEMENTS, OR MODIFICATIONS. 024 025PT_COPYRIGHT_VERSION_2 026COPYRIGHTENDKEY 027 */ 028package ptolemy.vergil.kernel; 029 030import java.awt.BasicStroke; 031import java.awt.Color; 032import java.awt.Graphics2D; 033import java.awt.Shape; 034import java.awt.Stroke; 035import java.awt.geom.AffineTransform; 036import java.awt.geom.Line2D; 037import java.awt.geom.Point2D; 038import java.awt.geom.Rectangle2D; 039 040import diva.canvas.AbstractFigure; 041import diva.canvas.CanvasComponent; 042import diva.canvas.CanvasPane; 043import diva.canvas.CompositeFigure; 044import diva.canvas.Figure; 045import diva.canvas.FigureLayer; 046import diva.graph.GraphController; 047import ptolemy.kernel.util.IllegalActionException; 048import ptolemy.kernel.util.Locatable; 049import ptolemy.kernel.util.NamedObj; 050import ptolemy.vergil.basic.BasicGraphPane; 051import ptolemy.vergil.basic.RelativeLocation; 052 053/** 054 * A figure for drawing a link between a relative locatable and its referenced object. 055 * The link is represented by a straight thin line. 056 * 057 * FIXME: Some artifacts are visible when the relative locatable object is dragged, because 058 * the clipping region seems not to be updated quickly enough. 059 * 060 * @author Miro Spoenemann 061 * @version $Id$ 062 * @since Ptolemy II 10.0 063 * @Pt.ProposedRating Red (msp) 064 * @Pt.AcceptedRating Red (msp) 065 */ 066public class RelativeLinkFigure extends AbstractFigure { 067 068 /** 069 * Construct a figure to draw the link of a relative locatable object. 070 * 071 * @param location The location of the relative locatable object. 072 */ 073 public RelativeLinkFigure(RelativeLocation location) { 074 _relativeLocation = location; 075 _line = new Line2D.Double(); 076 NamedObj relativeTo = _relativeLocation.getRelativeToNamedObj(); 077 if (relativeTo != null) { 078 _updateLine(relativeTo); 079 } 080 } 081 082 /** Get the outline shape of this figure. This implementation returns a line. 083 * 084 * @return A line, which may have length 0 if the related object has no valid reference. 085 */ 086 @Override 087 public Shape getShape() { 088 return _line; 089 } 090 091 /** Paint the figure. This implementation paints a line if the related object has 092 * a valid reference, and it paints nothing if it hasn't. If the length of the 093 * line exceeds a specific threshold, it is drawn with a different color to 094 * highlight that the reference will eventually be broken. 095 * 096 * @param g The graphics context used for painting. 097 */ 098 @Override 099 public void paint(Graphics2D g) { 100 NamedObj relativeTo = _relativeLocation.getRelativeToNamedObj(); 101 if (relativeTo != null) { 102 _updateLine(relativeTo); 103 double distance = Math 104 .sqrt(_line.x2 * _line.x2 + _line.y2 * _line.y2); 105 if (distance <= RelativeLocation.BREAK_THRESHOLD) { 106 g.setColor(NORMAL_COLOR); 107 } else { 108 g.setColor(THRESHOLD_COLOR); 109 } 110 g.setStroke(STROKE); 111 if (_transform != null) { 112 g.draw(_transform.createTransformedShape(_line)); 113 } else { 114 g.draw(_line); 115 } 116 } else { 117 // The relative location does not have a valid reference, so reset 118 // the cached location vector and draw nothing. 119 _line.x2 = 0; 120 _line.y2 = 0; 121 } 122 } 123 124 /** Set the given affine transformation for this figure. 125 * 126 * @param at an affine transformation 127 */ 128 @Override 129 public void transform(AffineTransform at) { 130 _transform = at; 131 } 132 133 /////////////////////////////////////////////////////////////////// 134 //// private methods //// 135 136 /** Update the line to the specified object. 137 * @param relativeTo The destination object. 138 */ 139 private void _updateLine(NamedObj relativeTo) { 140 // The line goes from this object (number 1) to 141 // the remote object (number 2). The positions for 142 // these objects are as follows. 143 boolean success = false; 144 if (getParent() instanceof CompositeFigure) { 145 CompositeFigure parentFigure = (CompositeFigure) getParent(); 146 // NOTE: Calling getBounds() on the figure itself yields an 147 // inaccurate bounds, for some reason. 148 // Weirdly, to get the size right, we need to use the shape. 149 // But to get the location right, we need the other! 150 Rectangle2D bounds1 = parentFigure.getBackgroundFigure() 151 .getBounds(); 152 Point2D origin1 = parentFigure.getBackgroundFigure().getOrigin(); 153 154 double xOffset1 = origin1.getX(); 155 // FIXME: Diva is a complete mystery. Offset doesn't work here, but works below. 156 xOffset1 = 0.0; 157 double left1 = bounds1.getX() - xOffset1; 158 double center1 = bounds1.getX() + bounds1.getWidth() * 0.5 159 - xOffset1; 160 double right1 = bounds1.getX() + bounds1.getWidth() - xOffset1; 161 162 double yOffset1 = origin1.getY(); 163 // FIXME: Diva is a complete mystery. Offset doesn't work here, but works below. 164 yOffset1 = 0.0; 165 double top1 = bounds1.getY() - yOffset1; 166 double middle1 = bounds1.getY() + bounds1.getHeight() * 0.5 167 - yOffset1; 168 double bottom1 = bounds1.getY() + bounds1.getHeight() - yOffset1; 169 170 // Now find the destination. 171 // Unfortunately, this is rather hard to do. 172 Locatable location = null; 173 try { 174 location = (Locatable) relativeTo.getAttribute("_location", 175 Locatable.class); 176 } catch (IllegalActionException e1) { 177 // Ignore and handle as if location is null. 178 } 179 CanvasComponent parent = getParent(); 180 FigureLayer enclosingFigureLayer = null; 181 while (parent != null) { 182 if (parent instanceof FigureLayer) { 183 enclosingFigureLayer = (FigureLayer) parent; 184 break; 185 } 186 parent = parent.getParent(); 187 } 188 if (location != null && enclosingFigureLayer != null) { 189 CanvasPane pane = enclosingFigureLayer.getCanvasPane(); 190 if (pane instanceof BasicGraphPane) { 191 GraphController controller = ((BasicGraphPane) pane) 192 .getGraphController(); 193 Figure figure = controller.getFigure(location); 194 if (figure instanceof CompositeFigure) { 195 figure = ((CompositeFigure) figure) 196 .getBackgroundFigure(); 197 } 198 double[] offset = _relativeLocation.getRelativeLocation(); 199 // NOTE: Calling getBounds() on the figure itself yields an 200 // inaccurate bounds, for some reason. 201 // Weirdly, to get the size right, we need to use the shape. 202 // But to get the location right, we need the other! 203 Rectangle2D bounds2 = figure.getShape().getBounds2D(); 204 205 Point2D origin2 = figure.getOrigin(); 206 double xOffset2 = origin2.getX(); 207 double left2 = -offset[0] + bounds2.getX() - xOffset2; 208 double center2 = -offset[0] + bounds2.getX() 209 + bounds2.getWidth() * 0.5 - xOffset2; 210 double right2 = -offset[0] + bounds2.getX() 211 + bounds2.getWidth() - xOffset2; 212 213 double yOffset2 = origin2.getY(); 214 // FIXME: Diva is a complete mystery. Offset isn't right. Fudge it. 215 yOffset2 += 11; 216 double top2 = -offset[1] + bounds2.getY() - yOffset2; 217 double middle2 = -offset[1] + bounds2.getY() 218 + bounds2.getHeight() * 0.5 - yOffset2; 219 double bottom2 = -offset[1] + bounds2.getY() 220 + bounds2.getHeight() - yOffset2; 221 222 // We have all the information we need for optimal placement. 223 success = true; 224 225 // There are five possible x positions. 226 if (left1 > right2) { 227 _line.x1 = left1; 228 _line.x2 = right2; 229 } else if (center1 > right2) { 230 _line.x1 = center1; 231 _line.x2 = right2; 232 } else if (center1 > left2) { 233 _line.x1 = center1; 234 _line.x2 = center2; 235 } else if (right1 > left2) { 236 _line.x1 = center1; 237 _line.x2 = left2; 238 } else { 239 _line.x1 = right1; 240 _line.x2 = left2; 241 } 242 // There are five possible y positions. 243 if (top1 > bottom2) { 244 _line.y1 = top1; 245 _line.y2 = bottom2; 246 } else if (middle1 > bottom2) { 247 _line.y1 = middle1; 248 _line.y2 = bottom2; 249 } else if (middle1 > top2) { 250 _line.y1 = middle1; 251 _line.y2 = middle2; 252 } else if (bottom1 > top2) { 253 _line.y1 = middle1; 254 _line.y2 = top2; 255 } else { 256 _line.y1 = bottom1; 257 _line.y2 = top2; 258 } 259 } 260 } 261 } 262 if (!success) { 263 // Fallback connection. 264 _line.x1 = 0; 265 _line.y1 = 0; 266 double[] offset = _relativeLocation.getRelativeLocation(); 267 _line.x2 = -offset[0]; 268 _line.y2 = -offset[1]; 269 } 270 } 271 272 /////////////////////////////////////////////////////////////////// 273 //// private variables //// 274 275 /** The relative location represented by this figure. */ 276 private RelativeLocation _relativeLocation; 277 278 /** The line used for drawing. */ 279 private Line2D.Double _line; 280 281 /** The current affine transformation. */ 282 private AffineTransform _transform; 283 284 /** The stroke used for drawing the line. */ 285 private static final Stroke STROKE = new BasicStroke(1.0f, 286 BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, 287 new float[] { 2.0f, 2.0f }, 0.0f); 288 289 /** The normal color of the line. */ 290 private static final Color NORMAL_COLOR = new Color(180, 180, 0); 291 292 /** The color used when the line is longer that a specific threshold. */ 293 private static final Color THRESHOLD_COLOR = new Color(250, 50, 0); 294 295}