001/* The node controller for ports contained in entities. 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 */ 027package ptolemy.vergil.actor; 028 029import java.awt.Color; 030import java.awt.Font; 031import java.awt.Shape; 032import java.awt.geom.AffineTransform; 033import java.awt.geom.Ellipse2D; 034import java.awt.geom.Line2D; 035import java.awt.geom.Path2D; 036import java.awt.geom.Rectangle2D; 037import java.util.List; 038 039import javax.swing.SwingConstants; 040 041import diva.canvas.CanvasUtilities; 042import diva.canvas.CompositeFigure; 043import diva.canvas.Figure; 044import diva.canvas.Site; 045import diva.canvas.connector.PerimeterSite; 046import diva.canvas.connector.TerminalFigure; 047import diva.canvas.interactor.CompositeInteractor; 048import diva.canvas.toolbox.BasicFigure; 049import diva.canvas.toolbox.LabelFigure; 050import diva.graph.GraphController; 051import diva.graph.NodeRenderer; 052import diva.util.java2d.Polygon2D; 053import diva.util.java2d.ShapeUtilities; 054import ptolemy.actor.CommunicationAspect; 055import ptolemy.actor.IOPort; 056import ptolemy.actor.PubSubPort; 057import ptolemy.actor.PublisherPort; 058import ptolemy.actor.SubscriberPort; 059import ptolemy.actor.gui.ColorAttribute; 060import ptolemy.actor.gui.PtolemyPreferences; 061import ptolemy.actor.parameters.ParameterPort; 062import ptolemy.data.ArrayToken; 063import ptolemy.data.BooleanToken; 064import ptolemy.data.DoubleToken; 065import ptolemy.data.IntToken; 066import ptolemy.data.Token; 067import ptolemy.data.expr.Parameter; 068import ptolemy.data.type.Typeable; 069import ptolemy.kernel.InstantiableNamedObj; 070import ptolemy.kernel.Port; 071import ptolemy.kernel.util.Attribute; 072import ptolemy.kernel.util.IllegalActionException; 073import ptolemy.kernel.util.NameDuplicationException; 074import ptolemy.kernel.util.NamedObj; 075import ptolemy.kernel.util.StringAttribute; 076import ptolemy.vergil.kernel.AttributeController; 077 078/////////////////////////////////////////////////////////////////// 079//// IOPortController 080 081/** 082 This class provides interaction with nodes that represent Ptolemy II 083 ports on an actor. It provides a double click binding and context 084 menu entry to edit the parameters of the port ("Configure") and a 085 command to get documentation. 086 It can have one of two access levels, FULL or PARTIAL. 087 If the access level is FULL, the the context menu also 088 contains a command to rename the node. 089 Note that whether the port is an input or output or multiport cannot 090 be controlled via this interface. The "Configure Ports" command of 091 the container should be invoked instead. 092 093 @author Steve Neuendorffer and Edward A. Lee 094 @version $Id$ 095 @since Ptolemy II 2.0 096 @Pt.ProposedRating Red (eal) 097 @Pt.AcceptedRating Red (johnr) 098 */ 099public class IOPortController extends AttributeController { 100 /** Create a port controller associated with the specified graph 101 * controller. The controller is given full access. 102 * @param controller The associated graph controller. 103 */ 104 public IOPortController(GraphController controller) { 105 this(controller, FULL); 106 } 107 108 /** Create a port controller associated with the 109 * specified graph controller. 110 * @param controller The associated graph controller. 111 * @param access The access level. 112 */ 113 public IOPortController(GraphController controller, Access access) { 114 super(controller, access); 115 116 // Override this default value to ensure that ports are 117 // not decorated to indicate that they are derived. 118 // Instead, the entire actor will be decorated. 119 _decoratable = false; 120 121 setNodeRenderer(new EntityPortRenderer()); 122 123 // // "Listen to Actor" 124 // _menuFactory.addMenuItemFactory(new MenuActionFactory( 125 // new ListenToPortAction())); 126 127 // Ports of entities do not use a selection interactor with 128 // the same selection model as the rest of the first level figures. 129 // If this were allowed, then the port would be able to be deleted. 130 CompositeInteractor interactor = new CompositeInteractor(); 131 setNodeInteractor(interactor); 132 interactor.addInteractor(_menuCreator); 133 } 134 135 /////////////////////////////////////////////////////////////////// 136 //// public variables //// 137 138 /** The spacing between individual connections to a multiport. */ 139 public static final double MULTIPORT_CONNECTION_SPACING = 5.0; 140 141 /////////////////////////////////////////////////////////////////// 142 //// public methods //// 143 144 /** Return one of {-270, -180, -90, 0, 90, 180, 270} specifying 145 * the orientation of a port. This depends on whether the port 146 * is an input, output, or both, whether the port has a parameter 147 * named "_cardinal" that specifies a cardinality, and whether the 148 * containing actor has a parameter named "_rotatePorts" that 149 * specifies a rotation of the ports. In addition, if the 150 * containing actor has a parameter named "_flipPortsHorizontal" 151 * or "_flipPortsVertical" with value true, then any ports that end up on the left 152 * or right (top or bottom) will be reversed. 153 * @param port The port. 154 * @return One of {-270, -180, -90, 0, 90, 180, 270}. 155 */ 156 public static int getCardinality(Port port) { 157 // Determine whether the port has an attribute that specifies 158 // which side of the icon it should be on, and whether the 159 // actor has an attribute that rotates the ports. If both 160 // are present, the port attribute takes precedence. 161 162 boolean isInput = false; 163 boolean isOutput = false; 164 boolean isInputOutput = false; 165 166 // Figure out what type of port we're dealing with. 167 // If ports are not IOPorts, then draw then as ports with 168 // no direction. 169 if (port instanceof IOPort) { 170 isInput = ((IOPort) port).isInput(); 171 isOutput = ((IOPort) port).isOutput(); 172 isInputOutput = isInput && isOutput; 173 } 174 175 StringAttribute cardinal = null; 176 int portRotation = 0; 177 try { 178 cardinal = (StringAttribute) port.getAttribute("_cardinal", 179 StringAttribute.class); 180 NamedObj container = port.getContainer(); 181 if (container != null) { 182 Parameter rotationParameter = (Parameter) container 183 .getAttribute("_rotatePorts", Parameter.class); 184 if (rotationParameter != null) { 185 Token rotationValue = rotationParameter.getToken(); 186 if (rotationValue instanceof IntToken) { 187 portRotation = ((IntToken) rotationValue).intValue(); 188 } 189 } 190 } 191 } catch (IllegalActionException ex) { 192 // Ignore and use defaults. 193 } 194 if (cardinal == null) { 195 // Port cardinality is not specified in the port. 196 if (isInputOutput) { 197 portRotation += -90; 198 } else if (isOutput) { 199 portRotation += 180; 200 } 201 } else if (cardinal.getExpression().equalsIgnoreCase("NORTH")) { 202 portRotation = 90; 203 } else if (cardinal.getExpression().equalsIgnoreCase("SOUTH")) { 204 portRotation = -90; 205 } else if (cardinal.getExpression().equalsIgnoreCase("EAST")) { 206 portRotation = 180; 207 } else if (cardinal.getExpression().equalsIgnoreCase("WEST")) { 208 portRotation = 0; 209 } else { // this shouldn't happen either 210 portRotation += -90; 211 } 212 213 // Ensure that the port rotation is one of 214 // {-270, -180, -90, 0, 90, 180, 270}. 215 portRotation = 90 * (portRotation / 90 % 4); 216 217 // Finally, check for horizontal or vertical flipping. 218 try { 219 NamedObj container = port.getContainer(); 220 if (container != null) { 221 Parameter flipHorizontalParameter = (Parameter) container 222 .getAttribute("_flipPortsHorizontal", Parameter.class); 223 if (flipHorizontalParameter != null) { 224 Token rotationValue = flipHorizontalParameter.getToken(); 225 if (rotationValue instanceof BooleanToken 226 && ((BooleanToken) rotationValue).booleanValue()) { 227 if (portRotation == 0 || portRotation == -180) { 228 portRotation += 180; 229 } else if (portRotation == 180) { 230 portRotation = 0; 231 } 232 } 233 } 234 Parameter flipVerticalParameter = (Parameter) container 235 .getAttribute("_flipPortsVertical", Parameter.class); 236 if (flipVerticalParameter != null) { 237 Token rotationValue = flipVerticalParameter.getToken(); 238 if (rotationValue instanceof BooleanToken 239 && ((BooleanToken) rotationValue).booleanValue()) { 240 if (portRotation == -270 || portRotation == -90) { 241 portRotation += 180; 242 } else if (portRotation == 90 || portRotation == 270) { 243 portRotation -= 180; 244 } 245 } 246 } 247 } 248 } catch (IllegalActionException ex) { 249 // Ignore and use defaults. 250 } 251 252 return portRotation; 253 } 254 255 /** Return the direction associated with the specified angle, 256 * which is assumed to be one of {-270, -180, -90, 0, 90, 180, 270}. 257 * @param portRotation The angle 258 * @return One of SwingUtilities.NORTH, SwingUtilities.EAST, 259 * SwingUtilities.SOUTH, or SwingUtilities.WEST. 260 */ 261 public static int getDirection(int portRotation) { 262 int direction; 263 if (portRotation == 90 || portRotation == -270) { 264 direction = SwingConstants.NORTH; 265 } else if (portRotation == 180 || portRotation == -180) { 266 direction = SwingConstants.EAST; 267 } else if (portRotation == 270 || portRotation == -90) { 268 direction = SwingConstants.SOUTH; 269 } else { 270 direction = SwingConstants.WEST; 271 } 272 return direction; 273 } 274 275 /** Return one of {-270, -180, -90, 0, 90, 180, 270} specifying 276 * the orientation of a port. This depends on whether the port 277 * is an input, output, or both, whether the port has a parameter 278 * named "_cardinal" that specifies a cardinality, and whether the 279 * containing actor has a parameter named "_rotatePorts" that 280 * specifies a rotation of the ports. In addition, if the 281 * containing actor has a parameter named "_flipPortsHorizontal" 282 * or "_flipPortsVertical" with value true, then any ports that end up on the left 283 * or right (top or bottom) will be reversed. 284 * @deprecated Use public getCardinality() method instead 285 * @param port The port. 286 * @return One of {-270, -180, -90, 0, 90, 180, 270}. 287 * @see ptolemy.vergil.actor.IOPortController#getCardinality(Port) 288 */ 289 @Deprecated 290 protected static int _getCardinality(Port port) { 291 return getCardinality(port); 292 } 293 294 /** 295 * Get the class label of the component. 296 * @return the class label of the component. 297 */ 298 @Override 299 protected String _getComponentType() { 300 return "Port"; 301 } 302 303 /** Return the direction associated with the specified angle, 304 * which is assumed to be one of {-270, -180, -90, 0, 90, 180, 270}. 305 * @deprecated Use public getDirection() method instead 306 * @param portRotation The angle 307 * @return One of SwingUtilities.NORTH, SwingUtilities.EAST, 308 * SwingUtilities.SOUTH, or SwingUtilities.WEST. 309 * @see ptolemy.vergil.actor.IOPortController#getDirection(int) 310 */ 311 @Deprecated 312 protected static int _getDirection(int portRotation) { 313 return getDirection(portRotation); 314 } 315 316 /** The text used in the MoveAction.TO_FIRST action menu choice. 317 */ 318 @Override 319 protected String _moveToFirstDescription() { 320 return "Move to First"; 321 } 322 323 /** The text used in the MoveAction.TO_LAST action menu choice. 324 */ 325 @Override 326 protected String _moveToLastDescription() { 327 return "Move to Last"; 328 } 329 330 /////////////////////////////////////////////////////////////////// 331 //// private variables //// 332 333 /** Color for publish and subscribe labels. */ 334 private static Color _pubSubLabelColor = new Color(0.0f, 0.4f, 0.4f, 1.0f); 335 336 /** Font for port labels. */ 337 private static Font _pubSubLabelFont = new Font("SansSerif", Font.PLAIN, 338 10); 339 340 /////////////////////////////////////////////////////////////////// 341 //// inner classes //// 342 343 /** 344 * Render the ports of components as triangles. Multiports are rendered 345 * hollow, while single ports are rendered filled in black. ParameterPort 346 * is filled in grey. PublisherPort and SubscriberPort are filled with 347 * cyan. 348 */ 349 public class EntityPortRenderer implements NodeRenderer { 350 351 /** Render a visual representation of the given node. If the 352 * StringAttribute _color of the node is set then use that color to 353 * render the node. If the StringAttribute _explanation of the node is 354 * set then use it to set the tooltip. 355 * @see diva.graph.NodeRenderer#render(java.lang.Object) 356 */ 357 @Override 358 public Figure render(Object n) { 359 final Port port = (Port) n; 360 361 // If the port has an attribute called "_hide", then 362 // do not render it. 363 if (_isPropertySet(port, "_hide")) { 364 return null; 365 } 366 367 boolean isInput = false; 368 boolean isOutput = false; 369 boolean isInputOutput = false; 370 371 // Figure out what type of port we're dealing with. 372 // If ports are not IOPorts, then draw then as ports with 373 // no direction. 374 if (port instanceof IOPort) { 375 isInput = ((IOPort) port).isInput(); 376 isOutput = ((IOPort) port).isOutput(); 377 isInputOutput = isInput && isOutput; 378 } 379 380 // The shape that the port will have. These are all 381 // created as oriented to the West, i.e. the left side of 382 // the actor. 383 Shape shape; 384 385 // NOTE: The preferences mechanism may set this. 386 Token portSize = PtolemyPreferences.preferenceValue(port, 387 "_portSize"); 388 389 // Default size is 4.0. 390 double size = 4.0; 391 if (portSize instanceof DoubleToken) { 392 size = ((DoubleToken) portSize).doubleValue(); 393 } 394 double halfSize = size / 2.0; 395 double doubleSize = size * 2.0; 396 397 if (size == 0.0) { 398 // Use an empty ellipse as the shape. 399 shape = new Ellipse2D.Double(); 400 } else { 401 if (isInputOutput) { 402 Polygon2D.Double polygon = new Polygon2D.Double(); 403 polygon.moveTo(0, -size); 404 polygon.lineTo(-size, -size); 405 polygon.lineTo(-halfSize, 0); 406 polygon.lineTo(-size, size); 407 polygon.lineTo(size, size); 408 polygon.lineTo(halfSize, 0); 409 polygon.lineTo(size, -size); 410 polygon.lineTo(0, -size); 411 polygon.closePath(); 412 shape = polygon; 413 } else if (isInput) { 414 Polygon2D.Double polygon = new Polygon2D.Double(); 415 polygon.moveTo(-size, 0); 416 polygon.lineTo(-size, size); 417 polygon.lineTo(size, 0); 418 polygon.lineTo(-size, -size); 419 polygon.lineTo(-size, 0); 420 polygon.closePath(); 421 shape = polygon; 422 } else if (isOutput) { 423 Polygon2D.Double polygon = new Polygon2D.Double(); 424 polygon.moveTo(size, 0); 425 polygon.lineTo(size, -size); 426 polygon.lineTo(-size, 0); 427 polygon.lineTo(size, size); 428 polygon.lineTo(size, 0); 429 polygon.closePath(); 430 shape = polygon; 431 } else { 432 shape = new Ellipse2D.Double(-size, -size, doubleSize, 433 doubleSize); 434 } 435 } 436 boolean isPubSubPort = port instanceof PubSubPort; 437 Color fill; 438 if (port instanceof ParameterPort) { 439 fill = Color.lightGray; 440 } else if (isPubSubPort) { 441 fill = Color.CYAN; 442 } else if (port instanceof IOPort 443 && ((IOPort) port).isMultiport()) { 444 fill = Color.white; 445 } else { 446 fill = Color.black; 447 } 448 449 // Handle communication aspects. 450 try { 451 if (port instanceof IOPort) { 452 List<CommunicationAspect> communicationAspects = ((IOPort) port) 453 .getCommunicationAspects(); 454 if (communicationAspects != null 455 && communicationAspects.size() > 0) { 456 Object object = null; 457 if (((IOPort) port).isOutput()) { 458 object = communicationAspects.get(0); 459 } else { 460 object = communicationAspects 461 .get(communicationAspects.size() - 1); 462 } 463 ColorAttribute color = null; 464 if (object != null) { 465 color = (ColorAttribute) ((NamedObj) object) 466 .getAttribute( 467 CommunicationAspect.decoratorHighlightColorName); 468 } 469 if (color != null) { 470 fill = color.asColor(); 471 } else { 472 fill = Color.BLACK; 473 } 474 475 StringAttribute info = (StringAttribute) port 476 .getAttribute("_showInfo"); 477 if (info == null) { 478 info = new StringAttribute(port, "_showInfo"); 479 } 480 481 StringBuffer aspectsStringBuffer = new StringBuffer(); 482 for (int j = 0; j < communicationAspects.size(); j++) { 483 NamedObj namedObj = (NamedObj) communicationAspects 484 .get(j); 485 if (namedObj != null) { 486 if (aspectsStringBuffer.length() > 0) { 487 aspectsStringBuffer.append(", "); 488 } 489 aspectsStringBuffer.append(namedObj.getName()); 490 } 491 } 492 info.setExpression( 493 "Aspects: " + aspectsStringBuffer.toString()); 494 } else { 495 // No CommunicationAspect in use anymore, clean up _showInfo 496 // string. 497 498 // Use Attribute here instead of StringAttribute since 499 // the attribute could be e.g. a StringParameter, which 500 // is an Attribute but not a StringAttribute 501 Attribute info = port.getAttribute("_showInfo"); 502 if (info != null && info instanceof StringAttribute) { 503 String infoString = ((StringAttribute) info) 504 .getValueAsString(); 505 if (infoString.contains("Aspects:")) { 506 int start = infoString.indexOf("Aspects: "); 507 int end = infoString.length(); 508 String aspectInfo = infoString.substring(start, 509 end); 510 infoString = infoString.replace(aspectInfo, ""); 511 infoString = infoString.trim(); 512 ((StringAttribute) info) 513 .setExpression(infoString); 514 } 515 } 516 } 517 } 518 } catch (IllegalActionException e1) { 519 // TODO Auto-generated catch block 520 e1.printStackTrace(); 521 } catch (NameDuplicationException e) { 522 // Ignore. This exception should be thrown because before adding the 523 // new ColorAttribute any previous attribute with the same name is 524 // removed. 525 e.printStackTrace(); 526 } 527 528 ColorAttribute colorAttribute; 529 try { 530 colorAttribute = (ColorAttribute) port.getAttribute("_color", 531 ColorAttribute.class); 532 if (colorAttribute != null) { 533 Color color = colorAttribute.asColor(); 534 fill = color; 535 } 536 } catch (IllegalActionException e1) { 537 // TODO Auto-generated catch block 538 e1.printStackTrace(); 539 } 540 541 // StringAttribute _colorAttr = (StringAttribute) (port 542 // .getAttribute("_color")); 543 // 544 // if (_colorAttr != null) { 545 // String _color = _colorAttr.getExpression(); 546 // fill = SVGUtilities.getColor(_color); 547 // } 548 549 //ActorGraphModel model = (ActorGraphModel) getController() 550 // .getGraphModel(); 551 552 int portRotation = getCardinality(port); 553 int direction = getDirection(portRotation); 554 555 // Transform the port shape so it is facing the right way. 556 double rotation = portRotation; 557 AffineTransform transform = AffineTransform 558 .getRotateInstance(Math.toRadians(rotation)); 559 shape = ShapeUtilities.transformModify(shape, transform); 560 561 // Create a figure with a tooltip. 562 Figure figure = new BasicFigure(shape, fill, (float) 1.5) { 563 // Override this because we want to show the type. 564 // It doesn't work to set it once because the type 565 // has not been resolved, and anyway, it may 566 // change. NOTE: This is copied below. 567 @Override 568 public String getToolTipText() { 569 String tipText = port.getName(); 570 String displayName = port.getDisplayName(); 571 if (!tipText.equals(displayName)) { 572 tipText = displayName + " (" + tipText + ")"; 573 } 574 StringAttribute _explAttr = (StringAttribute) port 575 .getAttribute("_explanation"); 576 577 if (_explAttr != null) { 578 tipText = _explAttr.getExpression(); 579 } else if (port instanceof Typeable) { 580 try { 581 tipText = tipText + ", type:" 582 + ((Typeable) port).getType(); 583 } catch (IllegalActionException ex) { 584 // System.out.println("Tooltip failed: " + ex); 585 } 586 } 587 588 return tipText; 589 } 590 }; 591 // Have to do this also, or the AWT doesn't display any 592 // tooltip at all. 593 String tipText = port.getName(); 594 String displayName = port.getDisplayName(); 595 if (!tipText.equals(displayName)) { 596 tipText = displayName + " (" + tipText + ")"; 597 } 598 figure.setToolTipText(tipText); 599 600 double normal = CanvasUtilities.getNormal(direction); 601 602 if (port instanceof IOPort) { 603 // Create a diagonal connector for multiports, if necessary. 604 IOPort ioPort = (IOPort) port; 605 606 if (ioPort.isMultiport()) { 607 int numberOfLinks = ioPort.linkedRelationList().size(); 608 609 if (numberOfLinks > 1) { 610 // The diagonal is necessary. 611 CompositeFigure compositeFigure = new CompositeFigure( 612 figure) { 613 // Override this because we want to show the type. 614 // It doesn't work to set it once because the type 615 // has not been resolved, and anyway, it may 616 // change. NOTE: This is copied from above. 617 @Override 618 public String getToolTipText() { 619 String tipText = port.getName(); 620 String displayName = port.getDisplayName(); 621 if (!tipText.equals(displayName)) { 622 tipText = displayName + " (" + tipText 623 + ")"; 624 } 625 StringAttribute _explAttr = (StringAttribute) port 626 .getAttribute("_explanation"); 627 628 if (_explAttr != null) { 629 tipText = _explAttr.getExpression(); 630 } else if (port instanceof Typeable) { 631 try { 632 tipText = tipText + ", type:" 633 + ((Typeable) port).getType(); 634 } catch (IllegalActionException ex) { 635 // System.out.println("Tooltip failed: " + ex); 636 } 637 } 638 return tipText; 639 } 640 }; 641 642 // Line depends on the orientation. 643 double startX; 644 645 // Line depends on the orientation. 646 double startY; 647 648 // Line depends on the orientation. 649 double endX; 650 651 // Line depends on the orientation. 652 double endY; 653 Rectangle2D bounds = figure.getShape().getBounds2D(); 654 double x = bounds.getX(); 655 double y = bounds.getY(); 656 double width = bounds.getWidth(); 657 double height = bounds.getHeight(); 658 int extent = numberOfLinks - 1; 659 660 if (direction == SwingConstants.EAST) { 661 startX = x + width; 662 startY = y + height / 2; 663 endX = startX 664 + extent * MULTIPORT_CONNECTION_SPACING; 665 endY = startY 666 + extent * MULTIPORT_CONNECTION_SPACING; 667 } else if (direction == SwingConstants.WEST) { 668 startX = x; 669 startY = y + height / 2; 670 endX = startX 671 - extent * MULTIPORT_CONNECTION_SPACING; 672 endY = startY 673 - extent * MULTIPORT_CONNECTION_SPACING; 674 } else if (direction == SwingConstants.NORTH) { 675 startX = x + width / 2; 676 startY = y; 677 endX = startX 678 - extent * MULTIPORT_CONNECTION_SPACING; 679 endY = startY 680 - extent * MULTIPORT_CONNECTION_SPACING; 681 } else { 682 startX = x + width / 2; 683 startY = y + height; 684 endX = startX 685 + extent * MULTIPORT_CONNECTION_SPACING; 686 endY = startY 687 + extent * MULTIPORT_CONNECTION_SPACING; 688 } 689 690 Line2D line = new Line2D.Double(startX, startY, endX, 691 endY); 692 Figure lineFigure = new BasicFigure(line, fill, 693 (float) 2.0); 694 compositeFigure.add(lineFigure); 695 figure = compositeFigure; 696 } 697 } 698 if (port instanceof PublisherPort) { 699 CompositeFigure compositeFigure = new CompositeFigure( 700 figure); 701 Path2D path = new Path2D.Double(); 702 path.moveTo(0.5 * size, -size); 703 path.lineTo(2.5 * size, 0); 704 path.lineTo(0.5 * size, size); 705 706 path.moveTo(2 * size, -size); 707 path.lineTo(4 * size, 0); 708 path.lineTo(2 * size, size); 709 710 Figure pathFigure = new BasicFigure(path); 711 compositeFigure.add(pathFigure); 712 713 String channel = "???"; 714 try { 715 if (((InstantiableNamedObj) port.getContainer()) 716 .isWithinClassDefinition()) { 717 // If the port is in a class definition, do not expand it, it might contain $foo.$bar. 718 channel = ((PublisherPort) port).channel 719 .getExpression(); 720 } else { 721 channel = ((PublisherPort) port).channel 722 .stringValue(); 723 } 724 // Also display the number of initial tokens in parens. 725 Token initialTokens = ((PublisherPort) port).initialTokens 726 .getToken(); 727 if (initialTokens instanceof ArrayToken) { 728 int number = ((ArrayToken) initialTokens).length(); 729 channel = channel + " (" + number + ")"; 730 } 731 } catch (IllegalActionException e) { 732 // Ignore and display question marks. 733 e.printStackTrace(); 734 } 735 // The anchor argument below is (sadly) ignored. 736 Figure label = new LabelFigure(channel, _pubSubLabelFont, 737 0.0, SwingConstants.SOUTH_EAST, _pubSubLabelColor); 738 double labelHeight = label.getBounds().getHeight(); 739 label.translate(5 * size, 0.5 * labelHeight); 740 compositeFigure.add(label); 741 742 figure = compositeFigure; 743 } else if (port instanceof SubscriberPort) { 744 CompositeFigure compositeFigure = new CompositeFigure( 745 figure); 746 Path2D path = new Path2D.Double(); 747 path.moveTo(-2 * size, -size); 748 path.lineTo(-2 * size, size); 749 750 path.moveTo(-3 * size, -size); 751 path.lineTo(-3 * size, size); 752 753 path.moveTo(-4 * size, -size); 754 path.lineTo(-4 * size, size); 755 756 Figure pathFigure = new BasicFigure(path); 757 compositeFigure.add(pathFigure); 758 759 String channel = "???"; 760 try { 761 if (((InstantiableNamedObj) port.getContainer()) 762 .isWithinClassDefinition()) { 763 // If the port is in a class definition, do not expand it, it might contain $foo.$bar. 764 channel = ((SubscriberPort) port).channel 765 .getExpression(); 766 } else { 767 channel = ((SubscriberPort) port).channel 768 .stringValue(); 769 } 770 // Also display the number of initial tokens in parens. 771 Token initialTokens = ((SubscriberPort) port).initialTokens 772 .getToken(); 773 if (initialTokens instanceof ArrayToken) { 774 int number = ((ArrayToken) initialTokens).length(); 775 channel = channel + " (" + number + ")"; 776 } 777 } catch (IllegalActionException e) { 778 // Ignore and display question marks. 779 e.printStackTrace(); 780 } 781 // The anchor argument below is (sadly) ignored. 782 Figure label = new LabelFigure(channel, _pubSubLabelFont, 783 0.0, SwingConstants.SOUTH_EAST, _pubSubLabelColor); 784 double labelHeight = label.getBounds().getHeight(); 785 double labelWidth = label.getBounds().getWidth(); 786 label.translate(-labelWidth - 5 * size, 0.5 * labelHeight); 787 compositeFigure.add(label); 788 789 figure = compositeFigure; 790 } 791 figure = _decoratePortFigure(n, figure); 792 // Wrap the figure in a TerminalFigure to set the direction that 793 // connectors exit the port. Note that this direction is the 794 // same direction that is used to layout the port in the 795 // Entity Controller. 796 figure = new PortTerminal(ioPort, figure, normal, false); 797 } else { 798 figure = _decoratePortFigure(n, figure); 799 Site tsite = new PerimeterSite(figure, 0); 800 tsite.setNormal(normal); 801 figure = new TerminalFigure(figure, tsite); 802 } 803 804 // New way to specify a highlight color. 805 AttributeController.renderHighlight(port, figure); 806 807 return figure; 808 } 809 810 /** Decorate the figure according to the properties of the node. This 811 * method does nothing, but subclasses may override this method to 812 * decorate the port's figure (e.g., add highlighting color). 813 * 814 * @param node The node. 815 * @param figure The port's figure before decoration. 816 * @return The port's figure after decoration (which may or may not be 817 * the figure given in the parameter). 818 */ 819 protected Figure _decoratePortFigure(Object node, Figure figure) { 820 return figure; 821 } 822 } 823}