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}