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}