001/** An attribute used to store a relative visual location. */
002/*
003Below is the copyright agreement for the Ptolemy II system.
004
005Copyright (c) 1995-2018 The Regents of the University of California.
006All rights reserved.
007
008Permission is hereby granted, without written agreement and without
009license or royalty fees, to use, copy, modify, and distribute this
010software and its documentation for any purpose, provided that the above
011copyright notice and the following two paragraphs appear in all copies
012of this software.
013
014IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
015FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
016ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
017THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
018SUCH DAMAGE.
019
020THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
021INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
022MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
023PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
024CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
025ENHANCEMENTS, OR MODIFICATIONS.
026
027Ptolemy II includes the work of others, to see those copyrights, follow
028the copyright link on the splash page or see copyright.htm.
029 */
030
031package ptolemy.vergil.basic;
032
033import java.util.List;
034
035import ptolemy.kernel.CompositeEntity;
036import ptolemy.kernel.util.IllegalActionException;
037import ptolemy.kernel.util.InternalErrorException;
038import ptolemy.kernel.util.Locatable;
039import ptolemy.kernel.util.Location;
040import ptolemy.kernel.util.NameDuplicationException;
041import ptolemy.kernel.util.NamedObj;
042import ptolemy.kernel.util.Settable;
043import ptolemy.kernel.util.StringAttribute;
044
045/** An attribute used to store a relative visual location.
046 *  The location is relative to an object specified by
047 *  the <i>relativeTo</i> attribute, which gives the name
048 *  of an object that is expected to be contained by the
049 *  container of the container of this attribute.
050 *  In addition, the <i>relativeToElementName</i> specifies
051 *  what kind of object this is relative to (an "entity",
052 *  "property" (attribute), "port", or "relation").
053 *
054 @author Edward A. Lee, Christian Motika, Miro Spoenemann
055 @version $Id$
056 @since Ptolemy II 11.0
057 @Pt.ProposedRating Yellow (cxh)
058 @Pt.AcceptedRating Red (cxh)
059 */
060public class RelativeLocation extends Location {
061
062    /** Construct an instance.
063     *  @param container The container (the object to have a relative location).
064     *  @param name The name of this instance.
065     *  @exception IllegalActionException If the superclass throws it.
066     *  @exception NameDuplicationException If the superclass throws it.
067     */
068    public RelativeLocation(NamedObj container, String name)
069            throws IllegalActionException, NameDuplicationException {
070        super(container, name);
071
072        relativeTo = new StringAttribute(this, "relativeTo");
073        relativeTo.setVisibility(Settable.EXPERT);
074        relativeToElementName = new StringAttribute(this,
075                "relativeToElementName");
076        relativeToElementName.setExpression("entity");
077    }
078
079    ///////////////////////////////////////////////////////////////////
080    ////                         parameters                        ////
081
082    /** The name of the object this location is relative to. */
083    public StringAttribute relativeTo;
084
085    /** The element name of the object this location is relative to.
086     *  This defaults to "entity".
087     */
088    public StringAttribute relativeToElementName;
089
090    /** The initial offset for new relative locatable objects. */
091    public static final double INITIAL_OFFSET = 40.0;
092
093    /** The maximal distance of the relative location. If this is exceeded
094     *  after moving the relative locatable, the link is broken (see
095     *  {@link LocatableNodeDragInteractor#mouseReleased(diva.canvas.event.LayerEvent)}).
096     */
097    public static final double BREAK_THRESHOLD = 300.0;
098
099    ///////////////////////////////////////////////////////////////////
100    ////                         public methods                    ////
101
102    /** Get the location in some cartesian coordinate system.
103     *  This method returns the absolute location of the object.
104     *  If the relative location was previously attached to an object
105     *  referenced in the {@link #relativeTo} property and that object
106     *  is gone, then the internally stored location is updated so it
107     *  contains the correct absolute location.
108     *  @return The location.
109     *  @see #setLocation(double[])
110     */
111    @Override
112    public double[] getLocation() {
113        double[] offset = super.getLocation();
114        NamedObj relativeToObject = getRelativeToNamedObj();
115        if (relativeToObject == null || offset == null) {
116            return offset;
117        }
118        double[] relativeToLocation = _getRelativeToLocation(relativeToObject);
119        if (relativeToLocation != null) {
120            double[] result = new double[offset.length];
121            for (int i = 0; i < offset.length; i++) {
122                result[i] = offset[i] + relativeToLocation[i];
123            }
124            return result;
125        }
126        // If we get to here, then the relativeTo object is gone, so
127        // update the relative location to an absolute one if possible.
128        if (_cachedReltoLoc != null) {
129            for (int i = 0; i < offset.length; i++) {
130                // The offset array is also referenced by the superclass, so
131                // changes to its content affect the actual location.
132                offset[i] += _cachedReltoLoc[i];
133            }
134            _cachedReltoLoc = null;
135        }
136        return offset;
137    }
138
139    /** Get the relative location, relative to the <i>relativeTo</i>
140     *  object, if there is one, and otherwise return the absolute
141     *  location.
142     *  @return The relative location.
143     *  @see #setLocation(double[])
144     */
145    public double[] getRelativeLocation() {
146        return super.getLocation();
147    }
148
149    /** If the <i>relativeTo</i> object exists, return it.
150     *  Otherwise, return null and clear the <i>relativeTo</i>
151     *  parameter value.
152     *  @return The relativeTo object, or null if it
153     *   does not exist.
154     */
155    public NamedObj getRelativeToNamedObj() {
156        String relativeToName = relativeTo.getExpression();
157        if (relativeToName.trim().equals("")) {
158            return null;
159        }
160        NamedObj result = null;
161        NamedObj container = getContainer();
162        if (container != null) {
163            NamedObj containersContainer = container.getContainer();
164            if (containersContainer instanceof CompositeEntity) {
165                CompositeEntity composite = (CompositeEntity) containersContainer;
166                String elementName = relativeToElementName.getExpression();
167                // The relativeTo object is not necessarily an Entity.
168                if (elementName.equals("property")) {
169                    result = composite.getAttribute(relativeToName);
170                } else if (elementName.equals("port")) {
171                    result = composite.getPort(relativeToName);
172                } else if (elementName.equals("relation")) {
173                    result = composite.getRelation(relativeToName);
174                } else {
175                    result = composite.getEntity(relativeToName);
176                }
177            }
178        }
179        if (result == null) {
180            // The relativeTo object could not be found, so the attributes holding
181            // the reference are no longer valid. Erase their content.
182            try {
183                relativeTo.setExpression("");
184                relativeToElementName.setExpression("");
185            } catch (IllegalActionException exception) {
186                throw new InternalErrorException(exception);
187            }
188        }
189        return result;
190    }
191
192    /** Set the location in some cartesian coordinate system, and notify
193     *  the container and any value listeners of the new location. Setting
194     *  the location involves maintaining a local copy of the passed
195     *  parameter. No notification is done if the location is the same
196     *  as before. This method propagates the value to any derived objects.
197     *  If the relative location is attached to an object referenced in the
198     *  {@link #relativeTo} property, then only the relative location is
199     *  stored internally.
200     *  @param location The location.
201     *  @exception IllegalActionException Thrown when attributeChanged() is called.
202     *  @see #getLocation()
203     */
204    @Override
205    public void setLocation(double[] location) throws IllegalActionException {
206        NamedObj relativeToObject = getRelativeToNamedObj();
207        if (relativeToObject == null) {
208            super.setLocation(location);
209            return;
210        }
211        double[] relativeToLocation = _getRelativeToLocation(relativeToObject);
212        if (relativeToLocation != null) {
213            double[] result = new double[location.length];
214            for (int i = 0; i < location.length; i++) {
215                result[i] = location[i] - relativeToLocation[i];
216            }
217            super.setLocation(result);
218            return;
219        }
220        // If we get to here, then the relativeTo object is gone, so delete
221        // the cached value.
222        _cachedReltoLoc = null;
223        super.setLocation(location);
224    }
225
226    ///////////////////////////////////////////////////////////////////
227    ////                         private methods                   ////
228
229    /** If the <i>relativeTo</i> object exists, return its location.
230     *  Otherwise, return null.
231     *  @param relativeToObject The relativeTo object.
232     *  @return The location of the relativeTo object, or null if it
233     *   does not exist.
234     */
235    private double[] _getRelativeToLocation(NamedObj relativeToObject) {
236        List<Locatable> locatables = relativeToObject
237                .attributeList(Locatable.class);
238        if (locatables.size() > 0) {
239            _cachedReltoLoc = locatables.get(0).getLocation();
240            return _cachedReltoLoc;
241        }
242        return null;
243    }
244
245    ///////////////////////////////////////////////////////////////////
246    ////                         private variables                 ////
247
248    /** The cached relativeTo location. This is used to restore the absolute position
249     *  after the relativeTo object has been deleted.
250     */
251    private double[] _cachedReltoLoc;
252
253}