001/* The graph controller for the vergil viewer 002 003 Copyright (c) 1999-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.actor; 029 030import java.awt.Color; 031import java.awt.Font; 032import java.awt.geom.AffineTransform; 033import java.awt.geom.Rectangle2D; 034import java.util.Enumeration; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Vector; 038 039import javax.swing.SwingConstants; 040 041import diva.canvas.CompositeFigure; 042import diva.canvas.Figure; 043import diva.canvas.interactor.SelectionDragger; 044import diva.canvas.toolbox.LabelFigure; 045import diva.graph.EdgeController; 046import diva.graph.GraphModel; 047import diva.graph.GraphPane; 048import diva.graph.JGraph; 049import diva.graph.NodeController; 050import diva.graph.basic.BasicLayoutTarget; 051import diva.graph.layout.AbstractGlobalLayout; 052import ptolemy.actor.Actor; 053import ptolemy.actor.FiringEvent; 054import ptolemy.actor.IOPort; 055import ptolemy.actor.gui.Configuration; 056import ptolemy.data.BooleanToken; 057import ptolemy.data.Token; 058import ptolemy.data.expr.Parameter; 059import ptolemy.data.expr.StringParameter; 060import ptolemy.data.expr.Variable; 061import ptolemy.kernel.Entity; 062import ptolemy.kernel.Port; 063import ptolemy.kernel.util.Attribute; 064import ptolemy.kernel.util.DebugEvent; 065import ptolemy.kernel.util.IllegalActionException; 066import ptolemy.kernel.util.InternalErrorException; 067import ptolemy.kernel.util.KernelException; 068import ptolemy.kernel.util.Locatable; 069import ptolemy.kernel.util.NamedObj; 070import ptolemy.kernel.util.Settable; 071import ptolemy.kernel.util.StringAttribute; 072import ptolemy.moml.Vertex; 073import ptolemy.util.MessageHandler; 074import ptolemy.vergil.basic.AbstractBasicGraphModel; 075import ptolemy.vergil.basic.LocatableNodeController; 076import ptolemy.vergil.basic.NamedObjController; 077import ptolemy.vergil.basic.RunnableGraphController; 078import ptolemy.vergil.kernel.AnimationRenderer; 079import ptolemy.vergil.kernel.AttributeController; 080import ptolemy.vergil.kernel.RelationController; 081import ptolemy.vergil.toolbox.PortSite; 082 083/////////////////////////////////////////////////////////////////// 084//// ActorViewerGraphController 085 086/** 087 A graph controller for the Ptolemy II schematic viewer. 088 This controller contains a set of default node controllers for attributes, 089 entities, links, ports, and relations. Those default controllers can 090 be overridden by attributes of type NodeControllerFactory. 091 The getNodeController() method determines which controller to return 092 for each node. 093 <p> 094 In addition, this controller provides graph-wide operations that allow 095 nodes to be moved and context menus to be created. It does 096 not provide interaction for adding or removing nodes; those are provided 097 by a derived class. If does provide toolbar buttons for executing 098 the model (or if this is not the top level, delegating to the top 099 level to execute). Right-clicking on the background will 100 create a context-sensitive menu for the graph. 101 102 @author Steve Neuendorffer, Contributor: Edward A. Lee 103 @version $Id$ 104 @since Ptolemy II 2.0 105 @Pt.ProposedRating Red (eal) 106 @Pt.AcceptedRating Red (johnr) 107 */ 108public class ActorViewerGraphController extends RunnableGraphController { 109 /** Create a new basic controller with default 110 * terminal and edge interactors and default context menus. 111 */ 112 public ActorViewerGraphController() { 113 _createControllers(); 114 115 // The following is a fallback controller used when encountering 116 // instances of Locatable that have no container. Do not create 117 // this in _createControllers() because that is overridden by 118 // derived classes. 119 _locatableController = new LocatableNodeController(this); 120 } 121 122 /////////////////////////////////////////////////////////////////// 123 //// public methods //// 124 125 /** React to an event by highlighting the actor being iterated. 126 * This effectively animates the execution. 127 * @param event The debug event. 128 */ 129 @Override 130 public void event(DebugEvent event) { 131 if (event instanceof FiringEvent) { 132 Actor actor = ((FiringEvent) event).getActor(); 133 134 if (actor instanceof NamedObj) { 135 NamedObj objToHighlight = (NamedObj) actor; 136 137 // If the object is not contained by the associated 138 // composite, then find an object above it in the hierarchy 139 // that is. 140 AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) getGraphModel(); 141 NamedObj toplevel = graphModel.getPtolemyModel(); 142 143 while (objToHighlight != null 144 && objToHighlight.getContainer() != toplevel) { 145 objToHighlight = objToHighlight.getContainer(); 146 } 147 148 if (objToHighlight == null) { 149 return; 150 } 151 152 Object location = objToHighlight.getAttribute("_location"); 153 154 if (location != null) { 155 Figure figure = getFigure(location); 156 157 if (figure != null) { 158 if (_animationRenderer == null) { 159 _animationRenderer = new AnimationRenderer(); 160 } 161 162 FiringEvent.FiringEventType type = ((FiringEvent) event) 163 .getType(); 164 165 if (type == FiringEvent.BEFORE_ITERATE 166 || type == FiringEvent.BEFORE_FIRE) { 167 _animationRenderer.renderSelected(figure); 168 _animated = figure; 169 170 long animationDelay = getAnimationDelay(); 171 172 if (animationDelay > 0) { 173 try { 174 Thread.sleep(animationDelay); 175 } catch (InterruptedException ex) { 176 } 177 } 178 } else if (type == FiringEvent.AFTER_ITERATE 179 || type == FiringEvent.AFTER_POSTFIRE) { 180 if (_animated != null) { 181 _animationRenderer.renderDeselected(_animated); 182 } 183 } 184 } 185 } 186 } 187 } 188 } 189 190 /** Return the edge controller appropriate for the given node, 191 * which in this case is the same link controller returned by 192 * getLinkController(). 193 * @param edge The edge object. 194 * @return the edge controller. 195 */ 196 @Override 197 public EdgeController getEdgeController(Object edge) { 198 return _linkController; 199 } 200 201 /** Return the value of the entity controller. 202 * Callers may add context menus by calling 203 * <pre> 204 * getEntityController.addMenuItemFactory(new XXXDialogFactory); 205 * </pre> 206 * @return the entity controller 207 */ 208 public ActorController getEntityController() { 209 // Used by jni.ThalesGraphFrame to add jni.ArgumentDialogFactory 210 return _entityController; 211 } 212 213 /** Return the node controller appropriate for the given object. 214 * If the object is an instance of Vertex, then return the 215 * local relation controller. If it implements Locatable, 216 * then determine whether it is an Entity, Attribute, or Port, 217 * and return the appropriate default controller. 218 * If the argument is an instance of Port, then return the 219 * local port controller. 220 * @param object A Vertex, Locatable, or Port. 221 * @return the node controller 222 */ 223 @Override 224 public NodeController getNodeController(Object object) { 225 // Defer to the superclass if it can provide a controller. 226 NodeController result = super.getNodeController(object); 227 228 if (result != null) { 229 return result; 230 } 231 232 // Superclass cannot provide a controller. Use defaults. 233 if (object instanceof Vertex) { 234 return _relationController; 235 } else if (object instanceof Locatable) { 236 Object semanticObject = getGraphModel().getSemanticObject(object); 237 238 if (semanticObject instanceof Entity) { 239 240 boolean isActorOfInterest = false; 241 242 try { 243 // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration? 244 Configuration configuration = getConfiguration(); 245 if (configuration != null) { 246 StringParameter actorInteractionAddonParameter; 247 actorInteractionAddonParameter = (StringParameter) configuration 248 .getAttribute("_actorInteractionAddon", 249 Parameter.class); 250 251 if (actorInteractionAddonParameter != null) { 252 String actorInteractionAddonClassName = actorInteractionAddonParameter 253 .stringValue(); 254 255 Class actorInteractionAddonClass = Class 256 .forName(actorInteractionAddonClassName); 257 258 ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass 259 .newInstance(); 260 261 isActorOfInterest = actorInteractionAddon 262 .isActorOfInterestForAddonController( 263 (NamedObj) semanticObject); 264 } 265 } 266 } catch (Exception e) { 267 e.printStackTrace(); 268 } 269 270 // In the viewer, there will not be a class definition 271 // controller that is distinct from the entity controller. 272 // In the edit, there will be. 273 if (_classDefinitionController != null 274 && ((Entity) semanticObject).isClassDefinition()) { 275 return _classDefinitionController; 276 } else if (isActorOfInterest) { 277 return _addonActorController; 278 } else { 279 return _entityController; 280 } 281 } else if (semanticObject instanceof Attribute) { 282 return _attributeController; 283 } else if (semanticObject instanceof Port) { 284 return _portController; 285 } else { 286 return _locatableController; 287 } 288 } else if (object instanceof Port) { 289 return _entityPortController; 290 } 291 292 throw new RuntimeException( 293 "Node with unknown semantic object: " + object); 294 } 295 296 /** Set the configuration. The configuration is used when 297 * opening documentation files. 298 * @param configuration The configuration. 299 */ 300 @Override 301 public void setConfiguration(Configuration configuration) { 302 super.setConfiguration(configuration); 303 _attributeController.setConfiguration(configuration); 304 _classDefinitionController.setConfiguration(configuration); 305 _entityController.setConfiguration(configuration); 306 _entityPortController.setConfiguration(configuration); 307 _relationController.setConfiguration(configuration); 308 _linkController.setConfiguration(configuration); 309 310 try { 311 // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration? 312 if (configuration != null) { 313 StringParameter actorInteractionAddon; 314 actorInteractionAddon = (StringParameter) configuration 315 .getAttribute("_actorInteractionAddon", 316 Parameter.class); 317 318 if (actorInteractionAddon != null) { 319 _addonActorController.setConfiguration(configuration); 320 } 321 } 322 } catch (Exception e) { 323 e.printStackTrace(); 324 } 325 } 326 327 /////////////////////////////////////////////////////////////////// 328 //// protected methods //// 329 330 /** Add hot keys to the actions in the given JGraph. 331 * 332 * @param jgraph The JGraph to which hot keys are to be added. 333 */ 334 @Override 335 protected void _addHotKeys(JGraph jgraph) { 336 super._addHotKeys(jgraph); 337 _entityController.addHotKeys(jgraph); 338 _classDefinitionController.addHotKeys(jgraph); 339 _attributeController.addHotKeys(jgraph); 340 341 try { 342 // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration? 343 Configuration configuration = getConfiguration(); 344 if (configuration != null) { 345 StringParameter actorInteractionAddon; 346 actorInteractionAddon = (StringParameter) configuration 347 .getAttribute("_actorInteractionAddon", 348 Parameter.class); 349 350 if (actorInteractionAddon != null) { 351 _addonActorController.addHotKeys(jgraph); 352 } 353 } 354 } catch (Exception e) { 355 e.printStackTrace(); 356 } 357 } 358 359 /** Create the controllers for nodes in this graph. 360 * In this base class, controllers with PARTIAL access are created. 361 * This is called by the constructor, so derived classes that 362 * override this must be careful not to reference local variables 363 * defined in the derived classes, because the derived classes 364 * will not have been fully constructed by the time this is called. 365 */ 366 @Override 367 protected void _createControllers() { 368 super._createControllers(); 369 _attributeController = new AttributeController(this, 370 AttributeController.PARTIAL); 371 372 // NOTE: Use an ordinary ActorController rather than 373 // ClassDefinitionController because access is only PARTIAL. 374 _classDefinitionController = new ClassDefinitionController(this, 375 AttributeController.PARTIAL); 376 377 try { 378 379 // The Vergil Applet at $PTII/ptolemy/vergil/Vergil.htm might not have a configuration? 380 Configuration configuration = getConfiguration(); 381 if (configuration != null) { 382 StringParameter actorInteractionAddonParameter; 383 actorInteractionAddonParameter = (StringParameter) configuration 384 .getAttribute("_actorInteractionAddon", 385 Parameter.class); 386 387 if (actorInteractionAddonParameter != null) { 388 String actorInteractionAddonClassName = actorInteractionAddonParameter 389 .stringValue(); 390 Class actorInteractionAddonClass = Class 391 .forName(actorInteractionAddonClassName); 392 393 ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass 394 .newInstance(); 395 396 _addonActorController = actorInteractionAddon 397 .getControllerInstance(this, false); 398 } 399 } 400 } catch (Exception e) { 401 e.printStackTrace(); 402 } 403 404 _entityController = new ActorInstanceController(this, 405 AttributeController.PARTIAL); 406 _entityPortController = new IOPortController(this, 407 AttributeController.PARTIAL); 408 _relationController = new RelationController(this); 409 _linkController = new LinkController(this); 410 } 411 412 /** Initialize all interaction on the graph pane. This method 413 * is called by the setGraphPane() method of the superclass. 414 * This initialization cannot be done in the constructor because 415 * the controller does not yet have a reference to its pane 416 * at that time. 417 */ 418 @Override 419 protected void initializeInteraction() { 420 GraphPane pane = getGraphPane(); 421 422 // Create and set up the selection dragger 423 _selectionDragger = new SelectionDragger(pane); 424 _selectionDragger.addSelectionModel(getSelectionModel()); 425 426 // If the selectionDragger is consuming, then popup menus don't 427 // disappear properly. 428 _selectionDragger.setConsuming(false); 429 430 super.initializeInteraction(); 431 } 432 433 /////////////////////////////////////////////////////////////////// 434 //// protected variables //// 435 436 /** The attribute controller. */ 437 protected NamedObjController _attributeController; 438 439 /** The class definition controller. */ 440 protected ActorController _classDefinitionController; 441 442 /** The controller for actors with addon gui behavior. */ 443 protected ActorController _addonActorController; 444 445 /** The entity controller. */ 446 protected ActorController _entityController; 447 448 /** The entity port controller. */ 449 protected NamedObjController _entityPortController; 450 451 /** The link controller. */ 452 protected LinkController _linkController; 453 454 /** The relation controller. */ 455 protected NamedObjController _relationController; 456 457 /////////////////////////////////////////////////////////////////// 458 //// private variables //// 459 460 /** The default controller, used only for instances of Locatable 461 * that have no container. 462 */ 463 private LocatableNodeController _locatableController; 464 465 /** The selection interactor for drag-selecting nodes. */ 466 private SelectionDragger _selectionDragger; 467 468 /** Font for port labels. */ 469 private static Font _portLabelFont = new Font("SansSerif", Font.PLAIN, 8); 470 471 /////////////////////////////////////////////////////////////////// 472 //// inner classes //// 473 474 /////////////////////////////////////////////////////////////////// 475 //// EntityLayout 476 477 /** 478 * This layout algorithm is responsible for laying out the ports within an 479 * entity. 480 */ 481 public class EntityLayout extends AbstractGlobalLayout { 482 /** Create a new layout manager. */ 483 public EntityLayout() { 484 super(new BasicLayoutTarget(ActorViewerGraphController.this)); 485 } 486 487 /////////////////////////////////////////////////////////////// 488 //// public methods //// 489 490 /** 491 * Layout the ports of the specified node. 492 * 493 * @param node 494 * The node, which is assumed to be an entity. 495 */ 496 @Override 497 public void layout(Object node) { 498 GraphModel model = ActorViewerGraphController.this.getGraphModel(); 499 500 // System.out.println("layout = " + node); 501 // new Exception().printStackTrace(); 502 Iterator nodes = model.nodes(node); 503 Vector westPorts = new Vector(); 504 Vector eastPorts = new Vector(); 505 Vector southPorts = new Vector(); 506 Vector northPorts = new Vector(); 507 508 while (nodes.hasNext()) { 509 Port port = (Port) nodes.next(); 510 // Skip the port if it is hidden. 511 if (_isHidden(port)) { 512 continue; 513 } 514 int portRotation = IOPortController.getCardinality(port); 515 int direction = IOPortController.getDirection(portRotation); 516 if (direction == SwingConstants.WEST) { 517 westPorts.add(port); 518 } else if (direction == SwingConstants.NORTH) { 519 northPorts.add(port); 520 } else if (direction == SwingConstants.EAST) { 521 eastPorts.add(port); 522 } else { 523 southPorts.add(port); 524 } 525 } 526 527 CompositeFigure figure = (CompositeFigure) getLayoutTarget() 528 .getVisualObject(node); 529 530 _reOrderPorts(westPorts); 531 _placePortFigures(figure, westPorts, SwingConstants.WEST); 532 _reOrderPorts(eastPorts); 533 _placePortFigures(figure, eastPorts, SwingConstants.EAST); 534 _reOrderPorts(southPorts); 535 _placePortFigures(figure, southPorts, SwingConstants.SOUTH); 536 _reOrderPorts(northPorts); 537 _placePortFigures(figure, northPorts, SwingConstants.NORTH); 538 } 539 540 /////////////////////////////////////////////////////////////// 541 //// private methods //// 542 543 private LabelFigure _createPortLabelFigure(String string, Font font, 544 double x, double y, int direction) { 545 LabelFigure label; 546 547 if (direction == SwingConstants.SOUTH) { 548 // The 1.0 argument is the padding. 549 label = new LabelFigure(string, font, 1.0, 550 SwingConstants.SOUTH_WEST); 551 552 // Shift the label down so it doesn't 553 // collide with ports. 554 label.translateTo(x, y + 5); 555 556 // Rotate the label. 557 AffineTransform rotate = AffineTransform 558 .getRotateInstance(Math.PI / 2.0, x, y + 5); 559 label.transform(rotate); 560 } else if (direction == SwingConstants.EAST) { 561 // The 1.0 argument is the padding. 562 label = new LabelFigure(string, font, 1.0, 563 SwingConstants.SOUTH_WEST); 564 565 // Shift the label right so it doesn't 566 // collide with ports. 567 label.translateTo(x + 5, y); 568 } else if (direction == SwingConstants.WEST) { 569 // The 1.0 argument is the padding. 570 label = new LabelFigure(string, font, 1.0, 571 SwingConstants.SOUTH_EAST); 572 573 // Shift the label left so it doesn't 574 // collide with ports. 575 label.translateTo(x - 5, y); 576 } else { // Must be north. 577 578 // The 1.0 argument is the padding. 579 label = new LabelFigure(string, font, 1.0, 580 SwingConstants.SOUTH_WEST); 581 582 // Shift the label right so it doesn't 583 // collide with ports. It will probably 584 // collide with the actor name. 585 label.translateTo(x, y - 5); 586 587 // Rotate the label. 588 AffineTransform rotate = AffineTransform 589 .getRotateInstance(-Math.PI / 2.0, x, y - 5); 590 label.transform(rotate); 591 } 592 593 return label; 594 } 595 596 /** Return true if a property named "_hide" is set for 597 * the specified object. A property is specified if the specified 598 * object contains an attribute with the specified name and that 599 * attribute is either not a boolean-valued parameter, or it is 600 * a boolean-valued parameter with value true. 601 * @param object The object. 602 * @return True if the property is set. 603 */ 604 private boolean _isHidden(NamedObj object) { 605 Attribute attribute = object.getAttribute("_hide"); 606 if (attribute == null) { 607 return false; 608 } 609 if (attribute instanceof Parameter) { 610 try { 611 Token token = ((Parameter) attribute).getToken(); 612 613 if (token instanceof BooleanToken) { 614 if (!((BooleanToken) token).booleanValue()) { 615 return false; 616 } 617 } 618 } catch (IllegalActionException e) { 619 // Ignore, using default of true. 620 } 621 } 622 return true; 623 } 624 625 // re-order the ports according to _ordinal property 626 private void _reOrderPorts(Vector ports) { 627 int size = ports.size(); 628 Enumeration enumeration = ports.elements(); 629 Port port; 630 StringAttribute ordinal = null; 631 int number = 0; 632 int index = 0; 633 634 while (enumeration.hasMoreElements()) { 635 port = (Port) enumeration.nextElement(); 636 ordinal = (StringAttribute) port.getAttribute("_ordinal"); 637 638 if (ordinal != null) { 639 number = Integer.parseInt(ordinal.getExpression()); 640 641 if (number >= size) { 642 ports.remove(index); 643 644 try { 645 ordinal.setExpression(Integer.toString(size - 1)); 646 } catch (Exception e) { 647 MessageHandler 648 .error("Error setting ordinal property", e); 649 } 650 651 ports.add(port); 652 } else if (number < 0) { 653 ports.remove(index); 654 655 try { 656 ordinal.setExpression(Integer.toString(0)); 657 } catch (Exception e) { 658 MessageHandler 659 .error("Error setting ordinal property", e); 660 } 661 662 ports.add(0, port); 663 } else if (number != index) { 664 ports.remove(index); 665 ports.add(number, port); 666 } 667 } 668 669 index++; 670 } 671 } 672 673 // Place the ports. 674 private void _placePortFigures(CompositeFigure figure, List portList, 675 int direction) { 676 Iterator ports = portList.iterator(); 677 int number = 0; 678 // Don't count ports that are hidden and not connected. (Sven Koehler) 679 // "Make hidden, unconnected ports not be rendered on the 680 // canvas and therefore would not displace other ports on 681 // an actor." 682 // This is used by Kepler. 683 int count = 0; 684 for (Object p : portList) { 685 Port port = (Port) p; 686 Attribute portHide = port.getAttribute("_hide"); 687 try { 688 if (!(portHide != null && portHide instanceof Variable 689 && ((Variable) portHide).getToken() 690 .equals(BooleanToken.TRUE) 691 && port.linkedRelationList().isEmpty())) { 692 count++; 693 } 694 } catch (IllegalActionException ex) { 695 count = portList.size(); 696 } 697 } 698 Figure background = figure.getBackgroundFigure(); 699 700 if (background == null) { 701 // This could occur if the icon has a _hide parameter. 702 background = figure; 703 } 704 705 while (ports.hasNext()) { 706 Port port = (Port) ports.next(); 707 Figure portFigure = ActorViewerGraphController.this 708 .getFigure(port); 709 // If there is no figure, then ignore this port. This may 710 // happen if the port hasn't been rendered yet. 711 if (portFigure == null) { 712 continue; 713 } 714 715 Attribute portHide = port.getAttribute("_hide"); 716 // Skip ports that are hidden and not connected (Sven Koehler) 717 try { 718 if (portHide != null && portHide instanceof Variable 719 && ((Variable) portHide).getToken() 720 .equals(BooleanToken.TRUE) 721 && port.linkedRelationList().isEmpty()) { 722 continue; 723 } 724 } catch (IllegalActionException ex) { 725 throw new InternalErrorException(ex); 726 } 727 728 Rectangle2D portBounds = portFigure.getShape().getBounds2D(); 729 PortSite site = new PortSite(background, port, number, count, 730 direction); 731 number++; 732 733 // NOTE: previous expression for port location was: 734 // 100.0 * number / (count+1) 735 // But this leads to squished ports with uneven spacing. 736 // Note that we don't use CanvasUtilities.translateTo because 737 // we want to only get the bounds of the background of the 738 // port figure. 739 double x = site.getX() - portBounds.getCenterX(); 740 double y = site.getY() - portBounds.getCenterY(); 741 portFigure.translate(x, y); 742 743 // If the actor contains a variable named "_showRate", 744 // with value true, then visualize the rate information. 745 // NOTE: Showing rates only makes sense for IOPorts. 746 Attribute showRateAttribute = port.getAttribute("_showRate"); 747 748 if (port instanceof IOPort 749 && showRateAttribute instanceof Variable) { 750 boolean showRate = false; 751 752 try { 753 showRate = ((Variable) showRateAttribute).getToken() 754 .equals(BooleanToken.TRUE); 755 } catch (Exception ex) { 756 // Ignore. 757 showRate = false; 758 } 759 760 if (showRate) { 761 // Infer the rate. See DFUtilities. 762 String rateString = ""; 763 Variable rateParameter = null; 764 765 if (((IOPort) port).isInput()) { 766 rateParameter = (Variable) port 767 .getAttribute("tokenConsumptionRate"); 768 769 if (rateParameter == null) { 770 String altName = "_tokenConsumptionRate"; 771 rateParameter = (Variable) port 772 .getAttribute(altName); 773 } 774 } else if (((IOPort) port).isOutput()) { 775 rateParameter = (Variable) port 776 .getAttribute("tokenProductionRate"); 777 778 if (rateParameter == null) { 779 String altName = "_tokenProductionRate"; 780 rateParameter = (Variable) port 781 .getAttribute(altName); 782 } 783 } 784 785 if (rateParameter != null) { 786 try { 787 rateString = rateParameter.getToken() 788 .toString(); 789 } catch (KernelException ex) { 790 // Ignore. 791 } 792 } 793 794 LabelFigure labelFigure = _createPortLabelFigure( 795 rateString, _portLabelFont, x, y, direction); 796 labelFigure.setFillPaint(Color.BLUE); 797 figure.add(labelFigure); 798 } 799 } 800 801 // If the port contains an attribute named "_showName", 802 // then render the name of the port as well. If the 803 // attribute is a boolean-valued parameter, then 804 // show the name only if the value is true. 805 Attribute showAttribute = port.getAttribute("_showName"); 806 String toShow = null; 807 if (showAttribute != null) { 808 boolean show = true; 809 810 if (showAttribute instanceof Parameter) { 811 try { 812 Token token = ((Parameter) showAttribute) 813 .getToken(); 814 815 if (token instanceof BooleanToken) { 816 show = ((BooleanToken) token).booleanValue(); 817 } 818 } catch (IllegalActionException e) { 819 // Ignore. Presence of the attribute will prevail. 820 } 821 } 822 823 if (show) { 824 toShow = port.getDisplayName(); 825 } 826 } 827 // In addition, if the port contains an attribute 828 // called "_showInfo", then if that attribute is 829 // a variable, then its value is shown. Otherwise, 830 // if it is a Settable, then its expression is shown. 831 Attribute showInfo = port.getAttribute("_showInfo"); 832 try { 833 if (showInfo instanceof Variable 834 && !((Variable) showInfo).isStringMode()) { 835 String value = ((Variable) showInfo).getToken() 836 .toString(); 837 if (toShow != null && !value.trim().equals("")) { 838 toShow += " (" + value + ")"; 839 } else { 840 toShow = value; 841 } 842 } else if (showInfo instanceof Settable) { 843 String value = ((Settable) showInfo).getExpression(); 844 if (toShow != null && !value.trim().equals("")) { 845 toShow += " (" + value + ")"; 846 } else { 847 toShow = ((Settable) showInfo).getExpression(); 848 } 849 } 850 } catch (IllegalActionException e) { 851 if (toShow == null) { 852 toShow = e.getMessage(); 853 } else { 854 toShow += e.getMessage(); 855 } 856 } 857 858 // Finally, if the port is an IOPort and it has a defaultValue, 859 // show that value. If there is already text to show, the insert 860 // a colon before the value. 861 if (port instanceof IOPort) { 862 try { 863 Token defaultValue = ((IOPort) port).defaultValue 864 .getToken(); 865 if (defaultValue != null) { 866 if (toShow == null) { 867 toShow = defaultValue.toString(); 868 } else { 869 toShow += ": " + defaultValue.toString(); 870 } 871 } 872 } catch (IllegalActionException e) { 873 if (toShow == null) { 874 toShow = e.getMessage(); 875 } else { 876 toShow += e.getMessage(); 877 } 878 } 879 } 880 881 if (toShow != null) { 882 LabelFigure labelFigure = _createPortLabelFigure(toShow, 883 _portLabelFont, x, y, direction); 884 figure.add(labelFigure); 885 } 886 } 887 } 888 } 889}