001/* Utility methods for KIELER connector implementations.
002
003 Copyright (c) 2015-2018 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the
008 above copyright notice and the following two paragraphs appear in all
009 copies of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026 */
027package ptolemy.vergil.actor;
028
029import java.awt.Rectangle;
030import java.awt.geom.Point2D;
031import java.util.List;
032
033import diva.canvas.Figure;
034import diva.canvas.Site;
035import diva.canvas.TransformContext;
036import diva.canvas.connector.BasicManhattanRouter;
037import diva.canvas.connector.Connector;
038import diva.canvas.connector.PerimeterSite;
039import ptolemy.kernel.Relation;
040import ptolemy.kernel.util.Attribute;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.Locatable;
043import ptolemy.kernel.util.Location;
044import ptolemy.kernel.util.NamedObj;
045
046/**
047 * Static helper class for the KIELER classes
048 * implementing connector behavior, i.e. special edge
049 * routing mechanisms.
050 *
051 * @author Ulf Rueegg
052@version $Id$
053@since Ptolemy II 11.0
054 *
055 */
056public final class KielerLayoutUtil {
057
058    ///////////////////////////////////////////////////////////////////
059    ////                         public methods                    ////
060
061    /**
062     * Get the center point of a Perimeter Site. Copied the idea from
063     * {@link PerimeterSite#getPoint(double)}.
064     *
065     * @param site the site
066     * @return the center point of the shape that corresponds to the site
067     */
068    public static Point2D getCenterPoint(Site site) {
069        Figure figure = site.getFigure();
070        if (figure == null) {
071            return site.getPoint();
072        }
073        // Port figures return bounds that are relative to the containing node.
074        if (site instanceof PortConnectSite
075                && figure.getParent() instanceof Figure) {
076            figure = (Figure) figure.getParent();
077        }
078        Rectangle bounds = figure.getShape().getBounds();
079        return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
080    }
081
082    /**
083     * Get the starting and ending points of a connector. Copied some code from
084     * {@link BasicManhattanRouter#routeManhattan(diva.canvas.connector.ManhattanConnector)}.
085     * @param connector The connector.
086     * @param bendPoints a list of bendpoints to determine the anchor point on the site
087     * @return the anchor points at the start and end of the
088     * connection, i.e. a Point2D array of size 2
089     */
090    public static Point2D[] getHeadTailPoints(Connector connector,
091            List<Point2D> bendPoints) {
092        TransformContext currentContext = connector.getTransformContext();
093        Point2D headPt, tailPt;
094        Site headSite = connector.getHeadSite();
095        Site tailSite = connector.getTailSite();
096        if (currentContext != null) {
097            headPt = getCenterPoint(headSite);
098            tailPt = getCenterPoint(tailSite);
099            // get neighbor point to head and tail to determine the output sides
100            Point2D headBend, tailBend;
101            if (!bendPoints.isEmpty()) {
102                headBend = bendPoints.get(0);
103                tailBend = bendPoints.get(bendPoints.size() - 1);
104            } else {
105                headBend = tailPt;
106                tailBend = headPt;
107            }
108            // now change the "Normal" side of the site
109            headSite.setNormal(getNormal(headPt, headBend));
110            tailSite.setNormal(getNormal(tailPt, tailBend));
111            // and get the points again
112            headPt = headSite.getPoint(currentContext);
113            tailPt = tailSite.getPoint(currentContext);
114        } else {
115            // fallback if called too early, i.e. no context available
116            tailPt = tailSite.getPoint();
117            headPt = headSite.getPoint();
118        }
119        Point2D[] result = { headPt, tailPt };
120        return result;
121    }
122
123    /**
124     * Get the angle in radians from the origin to the other point.
125     *
126     * @param origin the original point
127     * @param other the other point
128     * @return angle in radians
129     */
130    public static double getNormal(Point2D origin, Point2D other) {
131        double normalX = other.getX() - origin.getX();
132        double normalY = other.getY() - origin.getY();
133        double theta = Math.atan2(normalY, normalX);
134        return theta;
135    }
136
137    /**
138     * Find a location for the given object.
139     *
140     * @param namedObj a model object
141     * @return the object's location, or {@code null} if there is no location
142     */
143    public static Locatable getLocation(NamedObj namedObj) {
144        if (namedObj instanceof Locatable) {
145            return (Location) namedObj;
146        } else {
147            NamedObj object = namedObj;
148
149            // Search for the next entity in the hierarchy that has
150            // a location attribute.
151            while (object != null) {
152                Attribute attribute = object.getAttribute("_location");
153                if (attribute instanceof Locatable) {
154                    return (Locatable) attribute;
155                }
156                List<Locatable> locatables = object
157                        .attributeList(Locatable.class);
158                if (!locatables.isEmpty()) {
159                    return locatables.get(0);
160                }
161                // Relations are directly contained in a composite entity, so
162                // don't take any parent location.
163                if (object instanceof Relation) {
164                    object = null;
165                } else {
166                    object = object.getContainer();
167                }
168            }
169        }
170        return null;
171    }
172
173    /**
174     * Get the location given by the location attribute of the given input
175     * object. If the Ptolemy object has no location attribute, return double
176     * zero.
177     *
178     * @param namedObj The Ptolemy object for which the location should be
179     *            retrieved.
180     * @return A vector corresponding to the location (x and y) of the object.
181     *          Will return a zero vector if no location attribute is set for the object.
182     */
183    public static Point2D getLocationPoint(NamedObj namedObj) {
184        Point2D point = getLocationPoint(getLocation(namedObj));
185        if (point == null) {
186            point = new Point2D.Double();
187        }
188        return point;
189    }
190
191    /**
192     * Retrieve the actual position from a locatable instance.
193     *
194     * @param locatable a locatable
195     * @return the actual position, or null if none is found
196     */
197    public static Point2D getLocationPoint(Locatable locatable) {
198        if (locatable != null) {
199            double[] coords = locatable.getLocation();
200            try {
201                /* Workaround for a strange behavior: If loading a model
202                 * from MoML, a Location might have set a valid expression with
203                 * non trivial values, but it hasn't been validated and therefore
204                 * the value is still {0,0}
205                 */
206                if (coords[0] == 0 && coords[1] == 0) {
207                    locatable.validate();
208                    coords = locatable.getLocation();
209                }
210                Point2D.Double location = new Point2D.Double();
211                location.x = coords[0];
212                location.y = coords[1];
213                return location;
214            } catch (IllegalActionException e) {
215                // nothing, use default value
216            }
217        }
218        return null;
219    }
220
221}