001/* The node controller for locatable nodes 002 003 Copyright (c) 1998-2016 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 above 008 copyright notice and the following two paragraphs appear in all copies 009 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 027 */ 028package ptolemy.vergil.basic; 029 030import java.awt.BasicStroke; 031import java.awt.Color; 032import java.awt.Stroke; 033import java.awt.geom.Rectangle2D; 034 035import diva.canvas.CanvasUtilities; 036import diva.canvas.CompositeFigure; 037import diva.canvas.Figure; 038import diva.canvas.connector.TerminalFigure; 039import diva.canvas.toolbox.BasicFigure; 040import diva.graph.BasicNodeController; 041import diva.graph.GraphController; 042import diva.graph.GraphModel; 043import diva.graph.NodeInteractor; 044import ptolemy.data.BooleanToken; 045import ptolemy.data.Token; 046import ptolemy.data.expr.Parameter; 047import ptolemy.kernel.util.Attribute; 048import ptolemy.kernel.util.IllegalActionException; 049import ptolemy.kernel.util.Locatable; 050import ptolemy.kernel.util.NamedObj; 051 052/////////////////////////////////////////////////////////////////// 053//// LocatableNodeController 054 055/** 056 This node controller provides interaction techniques for nodes that are 057 locations. This is common when the node has some concept of its 058 graphical location, but does not know about the figure that it 059 is associated with. This class provides the connection between the 060 figure's notion of location and the node's concept of location. 061 <p> 062 When nodes are drawn, they are automatically placed at the 063 coordinate given by the location. A LocatableNodeDragInteractor 064 is used to update the location of the node as the figure moves. 065 066 @author Steve Neuendorffer 067 @version $Id$ 068 @since Ptolemy II 2.0 069 @Pt.ProposedRating Red (eal) 070 @Pt.AcceptedRating Red (johnr) 071 */ 072public class LocatableNodeController extends BasicNodeController { 073 /** Create an instance associated with the specified graph controller. 074 * @param controller The graph controller. 075 */ 076 public LocatableNodeController(GraphController controller) { 077 super(controller); 078 079 NodeInteractor nodeInteractor = (NodeInteractor) getNodeInteractor(); 080 _dragInteractor = new LocatableNodeDragInteractor(this); 081 nodeInteractor.setDragInteractor(_dragInteractor); 082 } 083 084 /////////////////////////////////////////////////////////////////// 085 //// public methods //// 086 087 /** Add a node to this graph editor and render it 088 * at the given location. 089 */ 090 @Override 091 public void addNode(Object node, double x, double y) { 092 throw new UnsupportedOperationException("Cannot add node."); 093 } 094 095 /** Draw the node at its location. This overrides the base class 096 * to assign a location to the object. 097 */ 098 @Override 099 public Figure drawNode(Object node) { 100 Figure nf = super.drawNode(node); 101 locateFigure(node); 102 return nf; 103 } 104 105 /** Return the desired location of this node. Throw a runtime 106 * exception if the node does not have a desired location. 107 * @param node The node. 108 * @return The desired location of the node. 109 * @see #setLocation(Object, double[]) 110 */ 111 public double[] getLocation(Object node) { 112 if (hasLocation(node)) { 113 return ((Locatable) node).getLocation(); 114 } else { 115 throw new RuntimeException( 116 "The node " + node + "does not have a desired location"); 117 } 118 } 119 120 /** Return true if the node is associated with a desired location. 121 * In this base class, return true if the the node's semantic object is 122 * an instance of Locatable. 123 * @param node The node. 124 * @return True if the node is associated with a desired location. 125 */ 126 public boolean hasLocation(Object node) { 127 if (node instanceof Locatable) { 128 Locatable object = (Locatable) node; 129 double[] location = object.getLocation(); 130 131 if (location != null && location.length == 2) { 132 return true; 133 } 134 } 135 136 return false; 137 } 138 139 /** Move the node's figure to the location specified in the node's 140 * semantic object, if that object is an instance of Locatable. 141 * If the semantic object is not a location, then do nothing. 142 * If the figure associated with the semantic object is an instance 143 * of TerminalFigure, then modify the location to ensure that the 144 * connect site snaps to grid. 145 * @param node The object to locate. 146 */ 147 public void locateFigure(Object node) { 148 Figure nf = getController().getFigure(node); 149 150 try { 151 if (hasLocation(node)) { 152 double[] location = getLocation(node); 153 CanvasUtilities.translateTo(nf, location[0], location[1]); 154 } 155 } catch (Exception ex) { 156 // FIXME: Ignore if there is no valid location. This 157 // happens occasionally due to a race condition in the 158 // Bouncer demo. Occasionally, the repaint thread will 159 // attempt to locate the bouncing icon before the location 160 // parameter has been evaluated, causing an exception to 161 // be thrown. Basically the lazy parameter evaluation 162 // mechanism causes rerendering in Diva to be rentrant, 163 // which it shouldn't be. Unfortunately, I have no idea 164 // how to fix it... SN 5/5/2003 165 } 166 } 167 168 /** Set the desired location of this node. Throw an exception if the 169 * node can not be given a desired location. 170 * @param node The node 171 * @param location The location 172 * @exception IllegalActionException Not thrown in this base class. 173 * @see #getLocation(Object) 174 */ 175 public void setLocation(Object node, double[] location) 176 throws IllegalActionException { 177 if (location == null) { 178 throw new RuntimeException( 179 "The location is not valid, it should " + "not be null."); 180 } else if (location.length != 2) { 181 throw new RuntimeException("The location is not valid, the length " 182 + "should be 2, but it is " + location.length); 183 } 184 185 if (node instanceof Locatable) { 186 ((Locatable) node).setLocation(location); 187 } else { 188 throw new RuntimeException( 189 "The node " + node + "cannot have a desired location"); 190 } 191 } 192 193 /** Specify the snap resolution. The default snap resolution is 5.0. 194 * @param resolution The snap resolution. 195 */ 196 public void setSnapResolution(double resolution) { 197 _dragInteractor.setSnapResolution(resolution); 198 } 199 200 /////////////////////////////////////////////////////////////////// 201 //// public variables //// 202 203 /** A fourth argument would this highlight translucent, which would enable 204 * combination with other highlights. However, this causes printing to 205 * PDF to rasterize, which significantly degrades the quality of the 206 * graphic output. Used to have value 200. 207 */ 208 public static final Color CLASS_ELEMENT_HIGHLIGHT_COLOR = new Color(255, 209 128, 128); 210 211 /////////////////////////////////////////////////////////////////// 212 //// protected methods //// 213 214 /** Render the children of the specified node. 215 * This overrides the base class to do nothing if the node 216 * contains a parameter named "_hide" with value true. 217 * @param node The node with children to render. 218 */ 219 @Override 220 protected void _drawChildren(java.lang.Object node) { 221 if (!_hide(node)) { 222 super._drawChildren(node); 223 } 224 } 225 226 /** Get the CompositeFigure from the given Figure. 227 * 228 * @param nf The figure that should be a CompositeFigure itself, or be a 229 * TerminalFigure whose getFigure() method returns a CompositeFigure. 230 * @return The CompositeFigure, or null of it cannot be found. 231 */ 232 protected static CompositeFigure _getCompositeFigure(Figure nf) { 233 CompositeFigure cf; 234 235 // Try to get a composite figure that we can add the 236 // annotation to. This is complicated by the fact that 237 // ExternalIOPortController wraps its figure in a 238 // TerminalFigure, and that there is nothing in the API 239 // that enforces that a CompositeFigure is returned. 240 // *sigh* 241 if (nf instanceof CompositeFigure) { 242 cf = (CompositeFigure) nf; 243 } else if (nf instanceof TerminalFigure) { 244 Figure f = ((TerminalFigure) nf).getFigure(); 245 246 if (f instanceof CompositeFigure) { 247 cf = (CompositeFigure) f; 248 } else { 249 cf = null; 250 } 251 } else { 252 cf = null; 253 } 254 255 return cf; 256 } 257 258 /** In this base class, return true if the specified node contains a 259 * parameter named "_hide" with value true or an attribute that is not 260 * a parameter named "_hide". Derived classes can override this method 261 * to provide more sophisticated methods of choosing which nodes to 262 * display. 263 * @param node The node 264 * @return true if the specified node should be hidden. 265 */ 266 protected boolean _hide(java.lang.Object node) { 267 if (node instanceof Locatable) { 268 if (_isPropertySet(((Locatable) node).getContainer(), "_hide")) { 269 return true; 270 } 271 } 272 273 if (node instanceof NamedObj) { 274 if (_isPropertySet((NamedObj) node, "_hide")) { 275 return true; 276 } 277 } 278 279 return false; 280 } 281 282 /** Return true if the property of the specified name is set for 283 * the specified object. A property is specified if the specified 284 * object contains an attribute with the specified name and that 285 * attribute is either not a boolean-valued parameter, or it is 286 * a boolean-valued parameter with value true. 287 * @param object The object. 288 * @param name The property name. 289 * @return True if the property is set. 290 */ 291 protected boolean _isPropertySet(NamedObj object, String name) { 292 Attribute attribute = object.getAttribute(name); 293 294 if (attribute == null) { 295 return false; 296 } 297 298 if (attribute instanceof Parameter) { 299 try { 300 Token token = ((Parameter) attribute).getToken(); 301 302 if (token instanceof BooleanToken) { 303 if (!((BooleanToken) token).booleanValue()) { 304 return false; 305 } 306 } 307 } catch (IllegalActionException e) { 308 // Ignore, using default of true. 309 } 310 } 311 312 return true; 313 } 314 315 /** Render the specified node. This overrides the base class to 316 * return an invisible figure if the node contains a parameter 317 * named "_hide" with value true. This overrides the base class 318 * to assign a location and to highlight the node if it is an 319 * inherited object, and hence cannot be deleted. 320 * @param node The node to render. 321 * @return the newly created figure. 322 */ 323 @Override 324 protected Figure _renderNode(java.lang.Object node) { 325 if (node == null || _hide(node)) { 326 // Return an empty figure. 327 Figure newFigure = new CompositeFigure(); 328 newFigure.setVisible(false); 329 newFigure.setInteractor(getNodeInteractor()); 330 newFigure.setUserObject(node); 331 getController().setFigure(node, newFigure); 332 return newFigure; 333 } else { 334 Figure nf = super._renderNode(node); 335 GraphModel model = getController().getGraphModel(); 336 Object object = model.getSemanticObject(node); 337 CompositeFigure cf = _getCompositeFigure(nf); 338 339 if (_decoratable && object instanceof NamedObj 340 && ((NamedObj) object).getDerivedLevel() < Integer.MAX_VALUE 341 && cf != null) { 342 // float[] dash = { 2.0f, 5.0f }; 343 Stroke stroke = new BasicStroke(2f, /* width */ 344 BasicStroke.CAP_SQUARE, /* cap */ 345 BasicStroke.JOIN_MITER, /* join */ 346 10.0f); /* mitre limit */ 347 // To get a dashed line, add the following two arguments above: 348 // dash, /* dash */ 349 // 0.0f); /* dash_phase */ 350 // Pad the figure so that this highlight composes properly 351 // with other highlights. 352 Rectangle2D bounds = cf.getBackgroundFigure().getBounds(); 353 double padding = 3.0; 354 bounds = new Rectangle2D.Double(bounds.getX() - padding, 355 bounds.getY() - padding, 356 bounds.getWidth() + padding * 2.0, 357 bounds.getHeight() + padding * 2.0); 358 BasicFigure bf = new BasicFigure(bounds); 359 bf.setStroke(stroke); 360 bf.setStrokePaint(CLASS_ELEMENT_HIGHLIGHT_COLOR); 361 // Put the highlighting in the background, 362 // behind the actor label. 363 int index = cf.getFigureCount(); 364 if (index < 0) { 365 index = 0; 366 } 367 cf.add(index, bf); 368 } 369 370 return nf; 371 } 372 } 373 374 /////////////////////////////////////////////////////////////////// 375 //// protected variables //// 376 377 /** A flag indicating that the figures associated with this 378 * controller can be decorated to indicate that they are derived. 379 * Some derived classes (like IOPortController) override this 380 * to suppress such decoration. This is true by default. 381 */ 382 protected boolean _decoratable = true; 383 384 /////////////////////////////////////////////////////////////////// 385 //// private variables //// 386 387 /** The drag interactor, which is remembered so we can change the 388 * snap resolution. 389 */ 390 private LocatableNodeDragInteractor _dragInteractor; 391}