001/* The graph controller for the ptolemy schematic editor ports 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.actor; 029 030import java.awt.Color; 031import java.awt.Font; 032import java.awt.Shape; 033import java.awt.geom.Ellipse2D; 034import java.awt.geom.Line2D; 035import java.awt.geom.Rectangle2D; 036import java.util.HashMap; 037import java.util.List; 038 039import javax.swing.Action; 040import javax.swing.SwingConstants; 041 042import diva.canvas.CanvasUtilities; 043import diva.canvas.CompositeFigure; 044import diva.canvas.Figure; 045import diva.canvas.Site; 046import diva.canvas.connector.FixedNormalSite; 047import diva.canvas.connector.PerimeterSite; 048import diva.canvas.connector.TerminalFigure; 049import diva.canvas.toolbox.BasicFigure; 050import diva.canvas.toolbox.LabelFigure; 051import diva.graph.GraphController; 052import diva.graph.GraphPane; 053import diva.util.java2d.Polygon2D; 054import ptolemy.actor.IOPort; 055import ptolemy.actor.PubSubPort; 056import ptolemy.actor.PublisherPort; 057import ptolemy.actor.SubscriberPort; 058import ptolemy.actor.gui.Configuration; 059import ptolemy.actor.parameters.ParameterPort; 060import ptolemy.data.type.Typeable; 061import ptolemy.kernel.InstantiableNamedObj; 062import ptolemy.kernel.Port; 063import ptolemy.kernel.util.Attribute; 064import ptolemy.kernel.util.IllegalActionException; 065import ptolemy.kernel.util.InternalErrorException; 066import ptolemy.kernel.util.KernelException; 067import ptolemy.kernel.util.Locatable; 068import ptolemy.kernel.util.Location; 069import ptolemy.kernel.util.NamedObj; 070import ptolemy.vergil.basic.BasicGraphController; 071import ptolemy.vergil.basic.BasicGraphFrame; 072import ptolemy.vergil.basic.WithIconGraphController; 073import ptolemy.vergil.icon.EditorIcon; 074import ptolemy.vergil.kernel.AttributeController; 075import ptolemy.vergil.toolbox.EditIconAction; 076import ptolemy.vergil.toolbox.RemoveIconAction; 077import ptolemy.vergil.toolbox.SnapConstraint; 078 079/////////////////////////////////////////////////////////////////// 080//// ExternalIOPortController 081 082/** 083 This class provides interaction with nodes that represent Ptolemy II 084 ports inside a composite. It provides a double click binding and context 085 menu entry to edit the parameters of the port ("Configure") and a 086 command to get documentation. 087 It can have one of two access levels, FULL or PARTIAL. 088 If the access level is FULL, the the context menu also 089 contains a command to rename the node. 090 Note that whether the port is an input or output or multiport cannot 091 be controlled via this interface. The "Configure Ports" command of 092 the container should be invoked instead. 093 094 @author Steve Neuendorffer and Edward A. Lee, Elaine Cheong 095 @version $Id$ 096 @since Ptolemy II 2.0 097 @Pt.ProposedRating Red (eal) 098 @Pt.AcceptedRating Red (johnr) 099 */ 100public class ExternalIOPortController extends AttributeController { 101 /** Create a port controller associated with the specified graph 102 * controller. The controller is given full access. 103 * @param controller The associated graph controller. 104 */ 105 public ExternalIOPortController(GraphController controller) { 106 this(controller, FULL); 107 } 108 109 /** Create a port controller associated with the specified graph 110 * controller. 111 * @param controller The associated graph controller. 112 * @param access The access level. 113 */ 114 public ExternalIOPortController(GraphController controller, Access access) { 115 super(controller, access); 116 setNodeRenderer(new PortRenderer()); 117 } 118 119 /////////////////////////////////////////////////////////////////// 120 //// public members //// 121 122 /** Prototype input port. */ 123 public static final IOPort _GENERIC_INPUT = new IOPort(); 124 125 /** Prototype output port. */ 126 public static final IOPort _GENERIC_OUTPUT = new IOPort(); 127 128 /** Prototype inout port. */ 129 public static final IOPort _GENERIC_INOUT = new IOPort(); 130 131 /** Prototype input multiport. */ 132 public static final IOPort _GENERIC_INPUT_MULTIPORT = new IOPort(); 133 134 /** Prototype output multiport. */ 135 public static final IOPort _GENERIC_OUTPUT_MULTIPORT = new IOPort(); 136 137 /** Prototype inout multiport. */ 138 public static final IOPort _GENERIC_INOUT_MULTIPORT = new IOPort(); 139 140 /** Polygon coordinates for input output port. */ 141 public static Integer[] IOPORT_COORDINATES = new Integer[] { 0, 4, 0, 9, 6, 142 4, 12, 4, 12, -4, 6, -4, 0, -9, 0, -4, -8, -4 }; 143 144 /** Polygon coordinates for input port. */ 145 public static Integer[] IPORT_COORDINATES = new Integer[] { 0, 4, 0, 9, 12, 146 0, 0, -9, 0, -4, -8, -4 }; 147 148 /** Polygon coordinates for output port. */ 149 public static Integer[] OPORT_COORDINATES = new Integer[] { -8, 9, -2, 4, 150 12, 4, 12, -4, -2, -4, -8, -9 }; 151 152 /** Polygon coordinates for input output multiport. */ 153 public static Integer[] MULTI_IPORT_COORDINATES = new Integer[] { -5, 4, -5, 154 9, 1, 4, 1, 9, 7, 4, 12, 0, 7, -4, 1, -9, 1, -4, -5, -9, -5, -4, -8, 155 -4 }; 156 157 /** Polygon coordinates for output multiport. */ 158 public static Integer[] MULTI_OPORT_COORDINATES = new Integer[] { -8, 4, -8, 159 9, -2, 4, -2, 9, 4, 4, 12, 4, 12, -4, 4, -4, -2, -9, -2, -4, -8, 160 -9 }; 161 162 /** Polygon coordinates for input multiport. */ 163 public static Integer[] MULTI_IOPORT_COORDINATES = new Integer[] { -4, 4, 164 -4, 9, 2, 4, 2, 9, 8, 4, 12, 4, 12, -4, 8, -4, 2, -9, 2, -4, -4, -9, 165 -4, -4, -8, -4 }; 166 167 // Static initializer. 168 static { 169 try { 170 _GENERIC_INPUT.setInput(true); 171 _GENERIC_OUTPUT.setOutput(true); 172 _GENERIC_INOUT.setInput(true); 173 _GENERIC_INOUT.setOutput(true); 174 _GENERIC_INPUT_MULTIPORT.setInput(true); 175 _GENERIC_OUTPUT_MULTIPORT.setOutput(true); 176 _GENERIC_INOUT_MULTIPORT.setInput(true); 177 _GENERIC_INOUT_MULTIPORT.setOutput(true); 178 _GENERIC_INPUT_MULTIPORT.setMultiport(true); 179 _GENERIC_OUTPUT_MULTIPORT.setMultiport(true); 180 _GENERIC_INOUT_MULTIPORT.setMultiport(true); 181 182 // Need location attributes for these ports in order to 183 // be able to render them. 184 new Location(_GENERIC_INPUT, "_location"); 185 new Location(_GENERIC_OUTPUT, "_location"); 186 new Location(_GENERIC_INOUT, "_location"); 187 new Location(_GENERIC_INPUT_MULTIPORT, "_location"); 188 new Location(_GENERIC_OUTPUT_MULTIPORT, "_location"); 189 new Location(_GENERIC_INOUT_MULTIPORT, "_location"); 190 } catch (KernelException ex) { 191 // Should not occur. 192 throw new InternalErrorException(null, ex, null); 193 } 194 } 195 196 /** Move the node's figure to the location specified in the node's 197 * semantic object, if that object is an instance of Locatable. 198 * If the semantic object is not a location, then do nothing. 199 * If the figure associated with the semantic object is an instance 200 * of TerminalFigure, then modify the location to ensure that the 201 * connect site snaps to grid. 202 * @param node The object to locate. 203 */ 204 @Override 205 public void locateFigure(Object node) { 206 Figure nf = getController().getFigure(node); 207 208 try { 209 if (hasLocation(node)) { 210 double[] location = getLocation(node); 211 if (node instanceof Location) { 212 NamedObj port = ((Location) node).getContainer(); 213 214 // In case the location is (0,0) we try to come up with a 215 // better one. 216 if (port instanceof IOPort && location[0] == 0.0 217 && location[1] == 0.0) { 218 BasicGraphController controller = (BasicGraphController) getController(); 219 BasicGraphFrame frame = controller.getFrame(); 220 221 // We have a bootstrapping problem. In case the window 222 // is just being opened, the rendering happens before the 223 // creation of the the JGraph, and we don't know the actual 224 // size of the window (hence the magic numbers below in case 225 // frame.getJGraph() == null. 226 if (frame.getJGraph() != null) { 227 GraphPane pane = controller.getGraphPane(); 228 location = WithIconGraphController 229 .getNewPortLocation(pane, frame, 230 (IOPort) port); 231 } else { 232 IOPort ioPort = (IOPort) port; 233 if (ioPort.isInput() && ioPort.isOutput()) { 234 double[] newLocation = _inoutputPortLocations 235 .get(ioPort); 236 if (newLocation != null) { 237 location = newLocation; 238 } else { 239 // Put at the bottom 240 location[0] = 300.0 241 + _inoutputPortLocations.size() 242 * 40; 243 location[1] = 380.0; 244 _inoutputPortLocations.put(ioPort, 245 location); 246 } 247 } else if (ioPort.isInput()) { 248 double[] newLocation = _inputPortLocations 249 .get(ioPort); 250 if (newLocation != null) { 251 location = newLocation; 252 } else { 253 // Put at the left side 254 location[0] = 20.0; 255 location[1] = 200.0 256 + _inputPortLocations.size() * 40; 257 _inputPortLocations.put(ioPort, location); 258 } 259 } else if (ioPort.isOutput()) { 260 double[] newLocation = _outputPortLocations 261 .get(ioPort); 262 if (newLocation != null) { 263 location = newLocation; 264 } else { 265 // Put at the right side 266 location[0] = 580.0; 267 location[1] = 200.0 268 + _outputPortLocations.size() * 40; 269 _outputPortLocations.put(ioPort, location); 270 } 271 } else { 272 double[] newLocation = _otherPortLocations 273 .get(ioPort); 274 if (newLocation != null) { 275 location = newLocation; 276 } else { 277 // Put in the middle 278 location[0] = 300.0; 279 location[1] = 200.0 280 + _otherPortLocations.size() * 40; 281 _otherPortLocations.put(ioPort, location); 282 } 283 } 284 285 } 286 location = SnapConstraint.constrainPoint(location[0], 287 location[1]); 288 } 289 } 290 CanvasUtilities.translateTo(nf, location[0], location[1]); 291 } 292 } catch (Throwable throwable) { 293 // FIXME: Ignore if there is no valid location. This 294 // happens occasionally due to a race condition in the 295 // Bouncer demo. Occasionally, the repaint thread will 296 // attempt to locate the bouncing icon before the location 297 // parameter has been evaluated, causing an exception to 298 // be thrown. Basically the lazy parameter evaluation 299 // mechanism causes rerendering in Diva to be rentrant, 300 // which it shouldn't be. Unfortunately, I have no idea 301 // how to fix it... SN 5/5/2003 302 } 303 } 304 305 /** Set the configuration. This is used in derived classes to 306 * to open files (such as documentation). The configuration is 307 * is important because it keeps track of which files are already 308 * open and ensures that there is only one editor operating on the 309 * file at any one time. 310 * @param configuration The configuration. 311 */ 312 @Override 313 public void setConfiguration(Configuration configuration) { 314 super.setConfiguration(configuration); 315 316 if (_configuration != null) { 317 // Create an Appearance submenu. 318 _createAppearanceSubmenu(); 319 } 320 } 321 322 /////////////////////////////////////////////////////////////////// 323 //// protected methods //// 324 325 /** Override the base class to return true if the specified node 326 * contains an attribute named "_hideInside". This ensures that 327 * ports can be hidden on the outside while still being visible 328 * on the outside. 329 */ 330 @Override 331 protected boolean _hide(java.lang.Object node) { 332 if (node instanceof Locatable) { 333 if (((Locatable) node).getContainer() 334 .getAttribute("_hideInside") != null) { 335 return true; 336 } 337 } 338 339 if (node instanceof NamedObj) { 340 if (((NamedObj) node).getAttribute("_hideInside") != null) { 341 return true; 342 } 343 } 344 345 return false; 346 } 347 348 /** Given a port, return a reasonable tooltip message for that port. 349 * @param port The port. 350 * @return The name, type, and whether it's a multiport. 351 */ 352 protected String _portTooltip(final Port port) { 353 String tipText = port.getName(); 354 355 if (port instanceof IOPort) { 356 IOPort ioport = (IOPort) port; 357 358 if (ioport.isInput()) { 359 tipText += ", Input"; 360 } 361 362 if (ioport.isOutput()) { 363 tipText += ", Output"; 364 } 365 366 if (ioport.isMultiport()) { 367 tipText += ", Multiport"; 368 } 369 370 try { 371 tipText = tipText + ", type:" + ((Typeable) port).getType(); 372 } catch (ClassCastException ex) { 373 // Do nothing. 374 } catch (IllegalActionException ex) { 375 // Do nothing. 376 } 377 } 378 379 return tipText; 380 } 381 382 /////////////////////////////////////////////////////////////////// 383 //// protected members //// 384 385 /** The action that handles edit custom icon. */ 386 protected EditIconAction _editIconAction = new EditIconAction(); 387 388 /** The font used to label a port. */ 389 protected static Font _labelFont = new Font("SansSerif", Font.PLAIN, 12); 390 391 /** The action that handles removing a custom icon. */ 392 protected RemoveIconAction _removeIconAction = new RemoveIconAction(); 393 394 /////////////////////////////////////////////////////////////////// 395 //// private methods //// 396 397 /** 398 * Create an Appearance submenu. 399 */ 400 private void _createAppearanceSubmenu() { 401 _editIconAction.setConfiguration(_configuration); 402 _removeIconAction.setConfiguration(_configuration); 403 Action[] actions = { _editIconAction, _removeIconAction }; 404 _appearanceMenuActionFactory.addActions(actions, "Appearance"); 405 } 406 407 /////////////////////////////////////////////////////////////////// 408 //// inner classes //// 409 410 /** Render the external ports of a graph as a 5-sided tab thingy. 411 * Multiports are rendered hollow, 412 * while single ports are rendered filled. 413 * Publisher and subscriber ports are rendered specially. 414 */ 415 public class PortRenderer extends IconRenderer { 416 417 /** Render a port. If the argument implements Locatable, 418 * then render the port that is the container of that locatable. 419 * If the argument is an instance of _GENERIC_INPUT, 420 * _GENERIC_OUTPUT, or _GENERIC_INOUT, then render an input, 421 * output, or inout port with no name. If the argument is null, 422 * then render a port that is neither an input nor an output. 423 * @param n An instance of Locatable or one of the objects 424 * _GENERIC_INPUT, _GENERIC_OUTPUT, or _GENERIC_INOUT. 425 * @return The figure that is rendered. 426 */ 427 @Override 428 public Figure render(Object n) { 429 Figure figure = null; 430 Locatable location = (Locatable) n; 431 if (location == null) { 432 Polygon2D.Double polygon = new Polygon2D.Double(); 433 polygon.moveTo(0, 0); 434 polygon.lineTo(0, 10); 435 polygon.lineTo(12, 0); 436 polygon.lineTo(0, -10); 437 polygon.closePath(); 438 439 figure = new BasicFigure(polygon, Color.black); 440 figure.setToolTipText("Unknown port"); 441 return figure; 442 } 443 444 final Port port = (Port) location.getContainer(); 445 List iconList = port.attributeList(EditorIcon.class); 446 447 // Check to see whether there is an icon that has been created, 448 // but not inserted. 449 if (iconList.size() > 0) { 450 EditorIcon icon = (EditorIcon) iconList 451 .get(iconList.size() - 1); 452 figure = icon.createFigure(); 453 } else { 454 Attribute iconDescription = port 455 .getAttribute("_iconDescription"); 456 if (iconDescription != null) { 457 figure = super.render(n); 458 } 459 } 460 461 // Wrap the figure in a TerminalFigure to set the direction that 462 // connectors exit the port. Note that this direction is the 463 // OPPOSITE direction that is used to layout the port in the 464 // Entity Controller. 465 int direction; 466 Shape shape; 467 Polygon2D.Double polygon = new Polygon2D.Double(); 468 Color fill = Color.black; 469 if (figure == null) { 470 if (!(port instanceof IOPort)) { 471 polygon.moveTo(-6, 6); 472 polygon.lineTo(0, 6); 473 polygon.lineTo(8, 0); 474 polygon.lineTo(0, -6); 475 polygon.lineTo(-6, -6); 476 } else { 477 IOPort ioport = (IOPort) port; 478 polygon.moveTo(-8, 4); 479 if (ioport.isMultiport()) { 480 if (ioport instanceof ParameterPort) { 481 // It would be better to couple these to the 482 // parameters by position, but this is impossible 483 // in diva, so we assign a special color. 484 // FIXME: are Multiport ParameterPorts possible? 485 // FIXME: Should this be lightGrey? 486 fill = Color.darkGray; 487 } else if (ioport instanceof PublisherPort 488 || ioport instanceof SubscriberPort) { 489 fill = Color.CYAN; 490 } else { 491 fill = Color.white; 492 } 493 if (ioport.isOutput() && ioport.isInput()) { 494 polygon = _createPolygon(MULTI_IOPORT_COORDINATES, 495 polygon); 496 } else if (ioport.isOutput()) { 497 polygon = _createPolygon(MULTI_OPORT_COORDINATES, 498 polygon); 499 } else if (ioport.isInput()) { 500 polygon = _createPolygon(MULTI_IPORT_COORDINATES, 501 polygon); 502 } else { 503 polygon = null; 504 } 505 } else { 506 if (ioport instanceof ParameterPort) { 507 // It would be better to couple these to the 508 // parameters by position, but this is impossible 509 // in diva, so we assign a special color. 510 // FIXME: what about multiports para 511 fill = Color.lightGray; 512 } else if (ioport instanceof PublisherPort 513 || ioport instanceof SubscriberPort) { 514 fill = Color.CYAN; 515 } else { 516 fill = Color.black; 517 } 518 if (ioport.isOutput() && ioport.isInput()) { 519 polygon = _createPolygon(IOPORT_COORDINATES, 520 polygon); 521 } else if (ioport.isOutput()) { 522 polygon = _createPolygon(OPORT_COORDINATES, 523 polygon); 524 } else if (ioport.isInput()) { 525 polygon = _createPolygon(IPORT_COORDINATES, 526 polygon); 527 } else { 528 polygon = null; 529 } 530 } 531 } 532 533 if (polygon == null) { 534 Ellipse2D.Double ellipse = new Ellipse2D.Double(0.0, 0.0, 535 16.0, 16.0); 536 shape = ellipse; 537 } else { 538 polygon.closePath(); 539 shape = polygon; 540 } 541 if (port instanceof ParameterPort) { 542 figure = new BasicFigure(shape, new Color(0, 0, 0, 0), 543 (float) 0.0); 544 } else { 545 figure = new BasicFigure(shape, fill, (float) 1.5); 546 } 547 } 548 549 if (!(port instanceof IOPort)) { 550 direction = SwingConstants.NORTH; 551 } else { 552 IOPort ioport = (IOPort) port; 553 554 if (ioport.isInput() && ioport.isOutput()) { 555 direction = SwingConstants.NORTH; 556 } else if (ioport.isInput()) { 557 direction = SwingConstants.EAST; 558 } else if (ioport.isOutput()) { 559 direction = SwingConstants.WEST; 560 } else { 561 // should never happen 562 direction = SwingConstants.NORTH; 563 } 564 } 565 566 double normal = CanvasUtilities.getNormal(direction); 567 String name = port.getDisplayName(); 568 Rectangle2D backBounds = figure.getBounds(); 569 figure = new CompositeFigure(figure) { 570 // Override this because we want to show the type. 571 // It doesn't work to set it once because the type 572 // has not been resolved, and anyway, it may 573 // change. NOTE: This is copied from above. 574 @Override 575 public String getToolTipText() { 576 return _portTooltip(port); 577 } 578 }; 579 580 if (name != null && !name.equals("") 581 && !(port instanceof ParameterPort)) { 582 // Do not create a label if there is a custom icon. 583 List<EditorIcon> icons = port.attributeList(EditorIcon.class); 584 if (icons.size() == 0) { 585 LabelFigure label = new LabelFigure(name, _labelFont, 1.0, 586 SwingConstants.SOUTH_WEST); 587 588 // Shift the label slightly right so it doesn't 589 // collide with ports. 590 label.translateTo(backBounds.getX(), backBounds.getY()); 591 ((CompositeFigure) figure).add(label); 592 } 593 } 594 595 if (port instanceof IOPort) { 596 // Create a diagonal connector for multiports, if necessary. 597 IOPort ioPort = (IOPort) port; 598 599 if (ioPort.isMultiport()) { 600 int numberOfLinks = ioPort.insideRelationList().size(); 601 602 if (numberOfLinks > 1) { 603 // The diagonal is necessary. 604 // Line depends on the orientation. 605 double startX, startY, endX, endY; 606 Rectangle2D bounds = figure.getShape().getBounds2D(); 607 double x = bounds.getX(); 608 double y = bounds.getY(); 609 double width = bounds.getWidth(); 610 double height = bounds.getHeight(); 611 int extent = numberOfLinks - 1; 612 613 if (direction == SwingConstants.EAST) { 614 startX = x + width; 615 startY = y + height / 2; 616 endX = startX + extent 617 * IOPortController.MULTIPORT_CONNECTION_SPACING; 618 endY = startY + extent 619 * IOPortController.MULTIPORT_CONNECTION_SPACING; 620 } else if (direction == SwingConstants.WEST) { 621 startX = x; 622 startY = y + height / 2; 623 endX = startX - extent 624 * IOPortController.MULTIPORT_CONNECTION_SPACING; 625 endY = startY - extent 626 * IOPortController.MULTIPORT_CONNECTION_SPACING; 627 } else if (direction == SwingConstants.NORTH) { 628 startX = x + width / 2; 629 startY = y; 630 endX = startX - extent 631 * IOPortController.MULTIPORT_CONNECTION_SPACING; 632 endY = startY - extent 633 * IOPortController.MULTIPORT_CONNECTION_SPACING; 634 } else { 635 // Coverity: Logically dead code: 636 // direction can only be EAST, WEST or 637 // NORTH. 638 639 // However, if we don't have this else 640 // clause, then the compiler indicates 641 // that startX, startY, endX and endY are 642 // not initialized. One fix would be to 643 // set them when we declare them, but then 644 // set them again in the clauses above. 645 646 startX = x + width / 2; 647 startY = y + height; 648 endX = startX + extent 649 * IOPortController.MULTIPORT_CONNECTION_SPACING; 650 endY = startY + extent 651 * IOPortController.MULTIPORT_CONNECTION_SPACING; 652 } 653 654 Line2D line = new Line2D.Double(startX, startY, endX, 655 endY); 656 Figure lineFigure = new BasicFigure(line, fill, 657 (float) 2.0); 658 ((CompositeFigure) figure).add(lineFigure); 659 } 660 } 661 662 _createPubSubLabels(ioPort, (CompositeFigure) figure); 663 664 figure = new PortTerminal(ioPort, figure, normal, true); 665 } else { 666 Site tsite = new PerimeterSite(figure, 0); 667 tsite.setNormal(normal); 668 tsite = new FixedNormalSite(tsite); 669 figure = new TerminalFigure(figure, tsite) { 670 // Override this because the tooltip may 671 // change over time. I.e., the port may 672 // change from being an input or output, etc. 673 @Override 674 public String getToolTipText() { 675 return _portTooltip(port); 676 } 677 }; 678 } 679 680 // Have to do this as well or awt will not render a tooltip. 681 figure.setToolTipText(port.getName()); 682 AttributeController.renderHighlight(port, figure); 683 684 return figure; 685 } 686 687 } 688 689 /////////////////////////////////////////////////////////////////// 690 //// private methods //// 691 692 private Polygon2D.Double _createPolygon(Integer[] coordinates, 693 Polygon2D.Double polygon) { 694 for (int i = 0; i < coordinates.length; i = i + 2) { 695 polygon.lineTo(coordinates[i], coordinates[i + 1]); 696 } 697 return polygon; 698 } 699 700 /** Create a label showing the channel and initial values 701 * for PubSubPort. If the port argument is not an instance 702 * of PubSubPort, then do nothing. 703 * @param port The port. 704 * @param figure The composite figure to add the label to. 705 */ 706 private void _createPubSubLabels(IOPort port, CompositeFigure figure) { 707 if (!(port instanceof PubSubPort)) { 708 return; 709 } 710 try { 711 String channel = null; 712 if (((InstantiableNamedObj) port.getContainer()) 713 .isWithinClassDefinition()) { 714 // If the port is in a class definition, do not expand it, it might contain $foo.$bar. 715 channel = "Channel: " 716 + ((PubSubPort) port).channel.getExpression(); 717 } else { 718 channel = "Channel: " 719 + ((PubSubPort) port).channel.stringValue(); 720 } 721 // The anchor argument below is (sadly) ignored. 722 Figure label = new LabelFigure(channel, _labelFont, 0.0, 723 SwingConstants.SOUTH_EAST, _pubSubLabelColor); 724 double labelHeight = label.getBounds().getHeight(); 725 Rectangle2D bounds = figure.getShape().getBounds2D(); 726 label.translate(-8.0, bounds.getMaxY() + labelHeight + 4); 727 figure.add(label); 728 729 String initialTokens = ((PubSubPort) port).initialTokens 730 .getExpression(); 731 if (!initialTokens.trim().equals("")) { 732 initialTokens = "Initial tokens: " + initialTokens; 733 label = new LabelFigure(initialTokens, _labelFont, 0.0, 734 SwingConstants.SOUTH_EAST, _pubSubLabelColor); 735 label.translate(-8.0, bounds.getMaxY() + 2 * labelHeight + 8); 736 figure.add(label); 737 } 738 } catch (Exception e) { 739 // Ignore and display question marks. 740 e.printStackTrace(); 741 } 742 } 743 744 /////////////////////////////////////////////////////////////////// 745 //// private members //// 746 747 /** Color for publish and subscribe labels. */ 748 private static Color _pubSubLabelColor = new Color(0.0f, 0.4f, 0.4f, 1.0f); 749 750 // The following maps are to keep track of the ports that already needed 751 // to be located (since they had location 0,0). 752 private HashMap<Object, double[]> _inputPortLocations = new HashMap<Object, double[]>(); 753 private HashMap<Object, double[]> _outputPortLocations = new HashMap<Object, double[]>(); 754 private HashMap<Object, double[]> _inoutputPortLocations = new HashMap<Object, double[]>(); 755 private HashMap<Object, double[]> _otherPortLocations = new HashMap<Object, double[]>(); 756}