001/* The graph controller for vergil. 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.Toolkit; 031import java.awt.event.ActionEvent; 032import java.awt.event.InputEvent; 033import java.awt.event.KeyEvent; 034import java.awt.geom.AffineTransform; 035import java.awt.geom.NoninvertibleTransformException; 036import java.awt.geom.Point2D; 037import java.awt.geom.Rectangle2D; 038import java.lang.reflect.Constructor; 039import java.util.Iterator; 040 041import javax.swing.Action; 042import javax.swing.JMenu; 043import javax.swing.JMenuItem; 044import javax.swing.JToolBar; 045 046import diva.canvas.CanvasComponent; 047import diva.canvas.CanvasUtilities; 048import diva.canvas.Figure; 049import diva.canvas.FigureLayer; 050import diva.canvas.Site; 051import diva.canvas.connector.AutonomousSite; 052import diva.canvas.connector.Connector; 053import diva.canvas.connector.ConnectorManipulator; 054import diva.canvas.event.LayerEvent; 055import diva.canvas.event.MouseFilter; 056import diva.canvas.interactor.AbstractInteractor; 057import diva.canvas.interactor.ActionInteractor; 058import diva.canvas.interactor.CompositeInteractor; 059import diva.canvas.interactor.GrabHandle; 060import diva.canvas.interactor.Interactor; 061import diva.graph.GraphModel; 062import diva.graph.GraphPane; 063import diva.graph.JGraph; 064import diva.graph.NodeRenderer; 065import diva.graph.layout.GlobalLayout; 066import diva.graph.layout.IncrLayoutAdapter; 067import diva.graph.layout.IncrementalLayoutListener; 068import diva.gui.GUIUtilities; 069import diva.gui.toolbox.FigureIcon; 070import diva.gui.toolbox.JContextMenu; 071import diva.util.Filter; 072import diva.util.UserObjectContainer; 073import ptolemy.actor.PublisherPort; 074import ptolemy.actor.SubscriberPort; 075import ptolemy.actor.gui.Configuration; 076import ptolemy.data.expr.Parameter; 077import ptolemy.data.expr.StringParameter; 078import ptolemy.kernel.CompositeEntity; 079import ptolemy.kernel.Entity; 080import ptolemy.kernel.util.InternalErrorException; 081import ptolemy.kernel.util.Locatable; 082import ptolemy.kernel.util.NamedObj; 083import ptolemy.kernel.util.StringAttribute; 084import ptolemy.moml.MoMLChangeRequest; 085import ptolemy.util.MessageHandler; 086import ptolemy.vergil.basic.BasicGraphFrame; 087import ptolemy.vergil.basic.NamedObjController; 088import ptolemy.vergil.kernel.AttributeController; 089import ptolemy.vergil.kernel.Link; 090import ptolemy.vergil.kernel.ListenToAction; 091import ptolemy.vergil.kernel.PortDialogAction; 092import ptolemy.vergil.kernel.RelationController; 093import ptolemy.vergil.toolbox.FigureAction; 094import ptolemy.vergil.toolbox.MenuItemFactory; 095import ptolemy.vergil.toolbox.SnapConstraint; 096import ptolemy.vergil.unit.ConfigureUnitsAction; 097 098/////////////////////////////////////////////////////////////////// 099//// ActorEditorGraphController 100 101/** 102 A Graph Controller for the Ptolemy II schematic editor. In addition to the 103 interaction allowed in the viewer, this controller allows nodes to be 104 dragged and dropped onto its graph. Relations can be created by 105 control-clicking on the background. Links can be created by control-clicking 106 and dragging on a port or a relation. In addition links can be created by 107 clicking and dragging on the ports that are inside an entity. 108 Anything can be deleted by selecting it and pressing 109 the delete key on the keyboard. 110 111 @author Steve Neuendorffer, Contributor: Edward A. Lee, Bert Rodiers 112 @version $Id$ 113 @since Ptolemy II 2.0 114 @Pt.ProposedRating Red (eal) 115 @Pt.AcceptedRating Red (johnr) 116 */ 117public class ActorEditorGraphController extends ActorViewerGraphController { 118 /** Create a new basic controller with default 119 * terminal and edge interactors. 120 */ 121 public ActorEditorGraphController() { 122 super(); 123 } 124 125 /////////////////////////////////////////////////////////////////// 126 //// public methods //// 127 128 /** Add commands to the specified menu and toolbar, as appropriate 129 * for this controller. In this class, commands are added to create 130 * ports and relations. 131 * @param menu The menu to add to, or null if none. 132 * @param toolbar The toolbar to add to, or null if none. 133 */ 134 @Override 135 public void addToMenuAndToolbar(JMenu menu, JToolBar toolbar) { 136 super.addToMenuAndToolbar(menu, toolbar); 137 // Only include the port actions if there is an actor library. 138 // The ptinyViewer configuration uses this. 139 if (getConfiguration().getEntity("actor library") != null) { 140 diva.gui.GUIUtilities.addMenuItem(menu, _newInputPortAction); 141 diva.gui.GUIUtilities.addToolBarButton(toolbar, 142 _newInputPortAction); 143 diva.gui.GUIUtilities.addMenuItem(menu, _newOutputPortAction); 144 diva.gui.GUIUtilities.addToolBarButton(toolbar, 145 _newOutputPortAction); 146 diva.gui.GUIUtilities.addMenuItem(menu, _newInoutPortAction); 147 diva.gui.GUIUtilities.addToolBarButton(toolbar, 148 _newInoutPortAction); 149 diva.gui.GUIUtilities.addMenuItem(menu, _newInputMultiportAction); 150 diva.gui.GUIUtilities.addToolBarButton(toolbar, 151 _newInputMultiportAction); 152 diva.gui.GUIUtilities.addMenuItem(menu, _newOutputMultiportAction); 153 diva.gui.GUIUtilities.addToolBarButton(toolbar, 154 _newOutputMultiportAction); 155 diva.gui.GUIUtilities.addMenuItem(menu, _newInoutMultiportAction); 156 diva.gui.GUIUtilities.addToolBarButton(toolbar, 157 _newInoutMultiportAction); 158 159 menu.addSeparator(); 160 161 // Add an item that adds new relations. 162 diva.gui.GUIUtilities.addMenuItem(menu, _newRelationAction); 163 diva.gui.GUIUtilities.addToolBarButton(toolbar, _newRelationAction); 164 } 165 } 166 167 /** Set the configuration. The configuration is used when 168 * opening documentation files. 169 * @param configuration The configuration. 170 */ 171 @Override 172 public void setConfiguration(Configuration configuration) { 173 super.setConfiguration(configuration); 174 175 if (_portDialogAction != null) { 176 _portDialogAction.setConfiguration(configuration); 177 } 178 179 if (_configureUnitsAction != null) { 180 _configureUnitsAction.setConfiguration(configuration); 181 } 182 183 if (_listenToActorFactory != null) { 184 _listenToActorFactory.setConfiguration(configuration); 185 } 186 } 187 188 /////////////////////////////////////////////////////////////////// 189 //// Public Inner Classes //// 190 191 /////////////////////////////////////////////////////////////////// 192 //// NewRelationAction 193 /** An action to create a new relation. */ 194 @SuppressWarnings("serial") 195 public class NewRelationAction extends FigureAction { 196 /** Create an action that creates a new relation. 197 */ 198 public NewRelationAction() { 199 this(null); 200 } 201 202 /** Create an action that creates a new relation. 203 * @param iconRoles A matrix of Strings, where each element 204 * consists of two Strings, the absolute URL of the icon 205 * and the key that represents the role of the icon. The keys 206 * are usually static fields from this class, such as 207 * {@link diva.gui.GUIUtilities#LARGE_ICON}, 208 * {@link diva.gui.GUIUtilities#ROLLOVER_ICON}, 209 * {@link diva.gui.GUIUtilities#ROLLOVER_SELECTED_ICON} or 210 * {@link diva.gui.GUIUtilities#SELECTED_ICON}. 211 * If this parameter is null, then the icon comes from 212 * the calling getNodeRenderer() on the {@link #_portController}. 213 * @see diva.gui.GUIUtilities#addIcons(Action, String[][]) 214 */ 215 public NewRelationAction(String[][] iconRoles) { 216 super("New Relation"); 217 218 if (iconRoles != null) { 219 GUIUtilities.addIcons(this, iconRoles); 220 } else { 221 // Standard toolbar icons are 25x25 pixels. 222 NodeRenderer renderer = _relationController.getNodeRenderer(); 223 Figure figure = renderer.render(null); 224 225 FigureIcon icon = new FigureIcon(figure, 25, 25, 1, true); 226 putValue(diva.gui.GUIUtilities.LARGE_ICON, icon); 227 } 228 putValue("tooltip", "Control-click to create a new relation"); 229 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 230 Integer.valueOf(KeyEvent.VK_R)); 231 } 232 233 @Override 234 public void actionPerformed(ActionEvent e) { 235 super.actionPerformed(e); 236 237 double x; 238 double y; 239 240 // If you add a vertex on top of an existing link the vertex will 241 // be added to the link. If the link exists, link will be different 242 // from null. 243 244 Link link = null; 245 246 if (getSourceType() == TOOLBAR_TYPE 247 || getSourceType() == MENUBAR_TYPE) { 248 // No location in the action, so put it in the middle. 249 BasicGraphFrame frame = ActorEditorGraphController.this 250 .getFrame(); 251 Point2D center; 252 253 if (frame != null) { 254 // Put in the middle of the visible part. 255 center = frame.getCenter(); 256 x = center.getX(); 257 y = center.getY(); 258 } else { 259 // Put in the middle of the pane. 260 GraphPane pane = getGraphPane(); 261 center = pane.getSize(); 262 x = center.getX() / 2; 263 y = center.getY() / 2; 264 } 265 } else { 266 // Transform 267 AffineTransform current = getGraphPane().getTransformContext() 268 .getTransform(); 269 AffineTransform inverse; 270 271 try { 272 inverse = current.createInverse(); 273 } catch (NoninvertibleTransformException ex) { 274 throw new RuntimeException(ex.toString()); 275 } 276 277 Point2D point = new Point2D.Double(getX(), getY()); 278 279 inverse.transform(point, point); 280 x = point.getX(); 281 y = point.getY(); 282 283 // If you add a vertex on top of an existing link the vertex will 284 // be added to the link. In this code fragment we will find 285 // out whether the vertex is on top of a link. 286 { 287 GraphPane pane = getGraphPane(); 288 FigureLayer foregroundLayer = pane.getForegroundLayer(); 289 290 double halo = foregroundLayer.getPickHalo(); 291 double width = halo * 2; 292 293 // The rectangle in which we search for a Figure. 294 Rectangle2D region = new Rectangle2D.Double(x - halo, 295 y - halo, width, width); 296 297 // Iterate through figures within the region. 298 Iterator<?> foregroundFigures = foregroundLayer.getFigures() 299 .getIntersectedFigures(region).figuresFromFront(); 300 Iterator<?> pickFigures = CanvasUtilities 301 .pickIter(foregroundFigures, region); 302 303 while (link == null && pickFigures.hasNext()) { 304 CanvasComponent possibleFigure = (CanvasComponent) pickFigures 305 .next(); 306 if (possibleFigure == null) { 307 // Nothing to see here, move along - there is no Figure. 308 } else if (possibleFigure instanceof UserObjectContainer) { 309 // Work our way up the CanvasComponent parent tree 310 // See EditorDropTarget for similar code. 311 Object userObject = ((UserObjectContainer) possibleFigure) 312 .getUserObject(); 313 if (userObject instanceof Link) { 314 link = (Link) userObject; 315 } 316 } 317 } 318 } 319 } 320 321 ActorGraphModel graphModel = (ActorGraphModel) getGraphModel(); 322 double[] point = _offsetVertex(SnapConstraint.constrainPoint(x, y)); 323 final NamedObj toplevel = graphModel.getPtolemyModel(); 324 325 if (!(toplevel instanceof CompositeEntity)) { 326 throw new InternalErrorException( 327 "Cannot invoke NewRelationAction on an object " 328 + "that is not a CompositeEntity."); 329 } 330 331 final String relationName = toplevel.uniqueName("relation"); 332 333 StringBuffer moml = new StringBuffer(); 334 if (link != null) { 335 // Add the vertex to an existing link. 336 moml.append("<group>\n"); 337 StringBuffer failmoml = new StringBuffer(); 338 graphModel.getLinkModel().addNewVertexToLink(moml, failmoml, 339 (CompositeEntity) toplevel, link, relationName, x, y); 340 moml.append("</group>\n"); 341 } else { 342 final String vertexName = "vertex1"; 343 344 // Create the relation. 345 moml.append("<relation name=\"" + relationName + "\">\n"); 346 moml.append("<vertex name=\"" + vertexName + "\" value=\"{"); 347 moml.append(point[0] + ", " + point[1]); 348 moml.append("}\"/>\n"); 349 moml.append("</relation>"); 350 } 351 MoMLChangeRequest request = new MoMLChangeRequest(this, toplevel, 352 moml.toString()); 353 request.setUndoable(true); 354 toplevel.requestChange(request); 355 } 356 357 /** Offset a figure if another figure is already at that location. 358 * @param point An array of two doubles (x and y) 359 * @return An array of two doubles (x and y) that represents 360 * either the original location or an offset location that 361 * does not obscure an object of class <i>figure</i>. 362 */ 363 protected double[] _offsetVertex(double[] point) { 364 365 GraphPane pane = getGraphPane(); 366 FigureLayer foregroundLayer = pane.getForegroundLayer(); 367 368 Rectangle2D visibleRectangle; 369 BasicGraphFrame frame = ActorEditorGraphController.this.getFrame(); 370 if (frame != null) { 371 visibleRectangle = frame.getVisibleRectangle(); 372 } else { 373 visibleRectangle = pane.getCanvas().getVisibleSize(); 374 } 375 return offsetFigure(point[0], point[1], PASTE_OFFSET, PASTE_OFFSET, 376 ptolemy.moml.Vertex.class, foregroundLayer, 377 visibleRectangle); 378 } 379 } 380 381 /////////////////////////////////////////////////////////////////// 382 //// protected methods //// 383 384 /** Add hot keys to the actions in the given JGraph. 385 * 386 * @param jgraph The JGraph to which hot keys are to be added. 387 */ 388 @Override 389 protected void _addHotKeys(JGraph jgraph) { 390 super._addHotKeys(jgraph); 391 392 _classDefinitionController.addHotKeys(getFrame().getJGraph()); 393 394 try { 395 396 StringParameter actorInteractionAddon; 397 actorInteractionAddon = (StringParameter) this.getConfiguration() 398 .getAttribute("_actorInteractionAddon", Parameter.class); 399 400 if (actorInteractionAddon != null) { 401 _addonActorController.addHotKeys(getFrame().getJGraph()); 402 } 403 } catch (Exception e) { 404 e.printStackTrace(); 405 } 406 407 } 408 409 /** Create the controllers for nodes in this graph. 410 * In this class, controllers with FULL access are created. 411 * This is called by the constructor, so derived classes that 412 * override this must be careful not to reference local variables 413 * defined in the derived classes, because the derived classes 414 * will not have been fully constructed by the time this is called. 415 */ 416 @Override 417 protected void _createControllers() { 418 Configuration _config = Configuration.configurations().iterator() 419 .next(); 420 String _alternateActorInstanceClassName = null; 421 _attributeController = new AttributeController(this, 422 AttributeController.FULL); 423 424 _classDefinitionController = new ClassDefinitionController(this); 425 426 if (_config != null) { 427 428 try { 429 430 StringParameter actorInteractionAddonParameter; 431 actorInteractionAddonParameter = (StringParameter) _config 432 .getAttribute("_actorInteractionAddon", 433 Parameter.class); 434 435 if (actorInteractionAddonParameter != null) { 436 String actorInteractionAddonClassName = actorInteractionAddonParameter 437 .stringValue(); 438 Class actorInteractionAddonClass = Class 439 .forName(actorInteractionAddonClassName); 440 441 ActorInteractionAddon actorInteractionAddon = (ActorInteractionAddon) actorInteractionAddonClass 442 .newInstance(); 443 444 _addonActorController = actorInteractionAddon 445 .getControllerInstance(this); 446 447 } 448 } catch (Exception e) { 449 e.printStackTrace(); 450 } 451 452 } 453 454 if (_config != null) { 455 /* 456 * If _alternateActorInstanceController is set in the config, use that 457 * class as the _entityController instead of the default 458 * ActorInstanceController 459 */ 460 StringAttribute _alternateActorInstanceAttribute = (StringAttribute) _config 461 .getAttribute("_alternateActorInstanceController"); 462 if (_alternateActorInstanceAttribute != null) { 463 _alternateActorInstanceClassName = _alternateActorInstanceAttribute 464 .getExpression(); 465 } 466 } 467 468 if (_alternateActorInstanceClassName == null) { 469 // Default to the normal ActorInstanceController. 470 _entityController = new ActorInstanceController(this); 471 } else { 472 try { 473 // Try to load the alternate class. 474 Class _alternateActorInstanceClass = Class 475 .forName(_alternateActorInstanceClassName); 476 Class[] argsClass = new Class[] { 477 diva.graph.GraphController.class }; 478 Object[] args = new Object[] { this }; 479 Constructor alternateActorInstanceConstructor = _alternateActorInstanceClass 480 .getConstructor(argsClass); 481 _entityController = (ActorController) alternateActorInstanceConstructor 482 .newInstance(args); 483 } catch (Exception e) { 484 System.out.println("The configuration has " 485 + "_alternateActorInstanceController set, but the class " 486 + _alternateActorInstanceClassName 487 + " is not found. Defaulting " 488 + " to ActorInstanceController: " + e.getMessage()); 489 e.printStackTrace(); 490 } 491 } 492 493 // Set up a listener to lay out the ports when graph changes. 494 // NOTE: It is imperative that there be no more than one such 495 // listener! If there is more than one instance, the 496 // ports will be laid out more than once. This manifests itself 497 // as a bug where port names are rendered twice, and for some 498 // inexplicable reason, are rendered in two different places! 499 // The filter for the layout algorithm of the ports within this 500 // entity. This returns true only if the candidate object is 501 // an instance of Locatable and the semantic object associated 502 // with it is an instance of Entity. 503 Filter portFilter = new Filter() { 504 @Override 505 public boolean accept(Object candidate) { 506 GraphModel model = getGraphModel(); 507 Object semanticObject = model.getSemanticObject(candidate); 508 509 // For some strange reason, this used to avoid doing 510 // layout for class definitions, with the following clause: 511 // && !((Entity) semanticObject).isClassDefinition() 512 if (candidate instanceof Locatable 513 && semanticObject instanceof Entity) { 514 return true; 515 } else { 516 return false; 517 } 518 } 519 }; 520 521 // Anytime we add a port to an entity, we want to layout all the 522 // ports within that entity. 523 GlobalLayout layout = new EntityLayout(); 524 addGraphViewListener( 525 new IncrementalLayoutListener(new IncrLayoutAdapter(layout) { 526 @Override 527 public void nodeDrawn(Object node) { 528 layout(node); 529 } 530 }, portFilter)); 531 532 _entityPortController = new IOPortController(this, 533 AttributeController.FULL); 534 _portController = new ExternalIOPortController(this, 535 AttributeController.FULL); 536 _relationController = new RelationController(this); 537 _linkController = new LinkController(this); 538 } 539 540 /** Initialize interactions for the specified controller. This 541 * method is called when a new controller is constructed. In this 542 * class, this method attaches a link creator to the controller 543 * if the controller is an instance of ExternalIOPortController, 544 * IOPortController, or RelationController. 545 * @param controller The controller for which to initialize interaction. 546 */ 547 @Override 548 protected void _initializeInteraction(NamedObjController controller) { 549 super._initializeInteraction(controller); 550 551 if (controller instanceof ExternalIOPortController 552 || controller instanceof IOPortController 553 || controller instanceof RelationController) { 554 Interactor interactor = controller.getNodeInteractor(); 555 556 if (interactor instanceof CompositeInteractor) { 557 ((CompositeInteractor) interactor).addInteractor(_linkCreator); 558 } 559 } 560 } 561 562 /** Initialize all interaction on the graph pane. This method 563 * is called by the setGraphPane() method of the superclass. 564 * This initialization cannot be done in the constructor because 565 * the controller does not yet have a reference to its pane 566 * at that time. 567 */ 568 @Override 569 protected void initializeInteraction() { 570 // FIXME: why is this protected, but does not have a leading underscore 571 // how is this different from _initializeInteraction above? 572 super.initializeInteraction(); 573 574 GraphPane pane = getGraphPane(); 575 576 // Add a menu command to configure the ports. 577 _portDialogAction = new PortDialogAction("Ports"); 578 _portDialogAction.setConfiguration(getConfiguration()); 579 580 _configureMenuFactory.addAction(_portDialogAction, "Customize"); 581 _configureUnitsAction = new ConfigureUnitsAction("Units Constraints"); 582 _configureMenuFactory.addAction(_configureUnitsAction, "Customize"); 583 _configureUnitsAction.setConfiguration(getConfiguration()); 584 585 // Add a menu command to list to the actor. 586 _listenToActorFactory = new ListenToActorFactory(); 587 _menuFactory.addMenuItemFactory(_listenToActorFactory); 588 _listenToActorFactory.setConfiguration(getConfiguration()); 589 590 // Create listeners that creates new relations. 591 _relationCreator = new RelationCreator(); 592 _relationCreator.setMouseFilter(_shortcutFilter); 593 594 pane.getBackgroundEventLayer().addInteractor(_relationCreator); 595 596 // Note that shift-click is already bound to the dragSelection 597 // interactor when adding things to a selection. 598 // Create the interactor that drags new edges. 599 _linkCreator = new LinkCreator(); 600 _linkCreator.setMouseFilter(_shortcutFilter); 601 602 // NOTE: Do not use _initializeInteraction() because we are 603 // still in the constructor, and that method is overloaded in 604 // derived classes. 605 ((CompositeInteractor) _portController.getNodeInteractor()) 606 .addInteractor(_linkCreator); 607 ((CompositeInteractor) _entityPortController.getNodeInteractor()) 608 .addInteractor(_linkCreator); 609 ((CompositeInteractor) _relationController.getNodeInteractor()) 610 .addInteractor(_linkCreator); 611 612 LinkCreator linkCreator2 = new LinkCreator(); 613 linkCreator2 614 .setMouseFilter(new MouseFilter(InputEvent.BUTTON1_MASK, 0)); 615 ((CompositeInteractor) _entityPortController.getNodeInteractor()) 616 .addInteractor(linkCreator2); 617 } 618 619 /** Action for creating a new relation. */ 620 protected Action _newRelationAction = new NewRelationAction(new String[][] { 621 { "/ptolemy/vergil/actor/img/relation.gif", 622 GUIUtilities.LARGE_ICON }, 623 { "/ptolemy/vergil/actor/img/relation_o.gif", 624 GUIUtilities.ROLLOVER_ICON }, 625 { "/ptolemy/vergil/actor/img/relation_ov.gif", 626 GUIUtilities.ROLLOVER_SELECTED_ICON }, 627 { "/ptolemy/vergil/actor/img/relation_on.gif", 628 GUIUtilities.SELECTED_ICON } }); 629 630 // private LinkCreator _linkCreator2; // For shift-click 631 632 /////////////////////////////////////////////////////////////////// 633 //// Protected Inner Classes //// 634 635 /** This class is an interactor that interactively drags edges from 636 * one terminal to another, creating a link to connect them. 637 */ 638 protected class LinkCreator extends AbstractInteractor { 639 /** Create a new edge when the mouse is pressed. */ 640 @Override 641 public void mousePressed(LayerEvent event) { 642 Figure source = event.getFigureSource(); 643 NamedObj sourceObject = (NamedObj) source.getUserObject(); 644 645 if (sourceObject instanceof PublisherPort 646 || sourceObject instanceof SubscriberPort) { 647 // MessageHandler.error("Cannot connect directly to publish and subscribe ports."); 648 return; 649 } 650 651 // Create the new edge. 652 Link link = new Link(); 653 654 // Set the tail, going through the model so the link is added 655 // to the list of links. 656 ActorGraphModel model = (ActorGraphModel) getGraphModel(); 657 model.getLinkModel().setTail(link, sourceObject); 658 659 try { 660 // add it to the foreground layer. 661 FigureLayer layer = getGraphPane().getForegroundLayer(); 662 Site headSite; 663 Site tailSite; 664 665 // Temporary sites. One of these will get blown away later. 666 headSite = new AutonomousSite(layer, event.getLayerX(), 667 event.getLayerY()); 668 tailSite = new AutonomousSite(layer, event.getLayerX(), 669 event.getLayerY()); 670 671 // Render the edge. 672 Connector c = getEdgeController(link).render(link, layer, 673 tailSite, headSite); 674 675 // get the actual attach site. 676 tailSite = getEdgeController(link).getConnectorTarget() 677 .getTailSite(c, source, event.getLayerX(), 678 event.getLayerY()); 679 680 if (tailSite == null) { 681 throw new RuntimeException("Invalid connector target: " 682 + "no valid site found for tail of new connector."); 683 } 684 685 // And reattach the connector. 686 c.setTailSite(tailSite); 687 688 // Add it to the selection so it gets a manipulator, and 689 // make events go to the grab-handle under the mouse 690 getSelectionModel().addSelection(c); 691 692 ConnectorManipulator cm = (ConnectorManipulator) c.getParent(); 693 GrabHandle gh = cm.getHeadHandle(); 694 layer.grabPointer(event, gh); 695 } catch (Exception ex) { 696 MessageHandler.error("Drag connection failed:", ex); 697 } 698 } 699 } 700 701 /** An interactor for creating relations upon control clicking. 702 */ 703 protected class RelationCreator extends ActionInteractor { 704 public RelationCreator() { 705 super(); 706 setAction(_newRelationAction); 707 } 708 } 709 710 /////////////////////////////////////////////////////////////////// 711 //// private variables //// 712 713 private ConfigureUnitsAction _configureUnitsAction; 714 715 /** The interactors that interactively creates edges. */ 716 private LinkCreator _linkCreator; // For control-click 717 718 /** Factory for listen to actor menu item. */ 719 private ListenToActorFactory _listenToActorFactory; 720 721 /** Action for creating a new inout multiport. */ 722 private Action _newInoutMultiportAction = new NewPortAction( 723 ExternalIOPortController._GENERIC_INOUT_MULTIPORT, 724 "New input/output multiport", KeyEvent.VK_T, 725 new String[][] { 726 { "/ptolemy/vergil/actor/img/multi_inout.gif", 727 GUIUtilities.LARGE_ICON }, 728 { "/ptolemy/vergil/actor/img/multi_inout_o.gif", 729 GUIUtilities.ROLLOVER_ICON }, 730 { "/ptolemy/vergil/actor/img/multi_inout_ov.gif", 731 GUIUtilities.ROLLOVER_SELECTED_ICON }, 732 { "/ptolemy/vergil/actor/img/multi_inout_on.gif", 733 GUIUtilities.SELECTED_ICON } }); 734 735 /** Action for creating a new input/output port. */ 736 private Action _newInoutPortAction = new NewPortAction( 737 ExternalIOPortController._GENERIC_INOUT, "New input/output port", 738 KeyEvent.VK_P, 739 new String[][] { 740 { "/ptolemy/vergil/actor/img/single_inout.gif", 741 GUIUtilities.LARGE_ICON }, 742 { "/ptolemy/vergil/actor/img/single_inout_o.gif", 743 GUIUtilities.ROLLOVER_ICON }, 744 { "/ptolemy/vergil/actor/img/single_inout_ov.gif", 745 GUIUtilities.ROLLOVER_SELECTED_ICON }, 746 { "/ptolemy/vergil/actor/img/single_inout_on.gif", 747 GUIUtilities.SELECTED_ICON } }); 748 749 /** Action for creating a new input multiport. */ 750 private Action _newInputMultiportAction = new NewPortAction( 751 ExternalIOPortController._GENERIC_INPUT_MULTIPORT, 752 "New input multiport", KeyEvent.VK_N, 753 new String[][] { 754 { "/ptolemy/vergil/actor/img/multi_in.gif", 755 GUIUtilities.LARGE_ICON }, 756 { "/ptolemy/vergil/actor/img/multi_in_o.gif", 757 GUIUtilities.ROLLOVER_ICON }, 758 { "/ptolemy/vergil/actor/img/multi_in_ov.gif", 759 GUIUtilities.ROLLOVER_SELECTED_ICON }, 760 { "/ptolemy/vergil/actor/img/multi_in_on.gif", 761 GUIUtilities.SELECTED_ICON } }); 762 763 /** Action for creating a new input port. */ 764 private Action _newInputPortAction = new NewPortAction( 765 ExternalIOPortController._GENERIC_INPUT, "New input port", 766 KeyEvent.VK_I, 767 new String[][] { 768 { "/ptolemy/vergil/actor/img/single_in.gif", 769 GUIUtilities.LARGE_ICON }, 770 { "/ptolemy/vergil/actor/img/single_in_o.gif", 771 GUIUtilities.ROLLOVER_ICON }, 772 { "/ptolemy/vergil/actor/img/single_in_ov.gif", 773 GUIUtilities.ROLLOVER_SELECTED_ICON }, 774 { "/ptolemy/vergil/actor/img/single_in_on.gif", 775 GUIUtilities.SELECTED_ICON } }); 776 777 /** Action for creating a new output multiport. */ 778 private Action _newOutputMultiportAction = new NewPortAction( 779 ExternalIOPortController._GENERIC_OUTPUT_MULTIPORT, 780 "New output multiport", KeyEvent.VK_U, 781 new String[][] { 782 { "/ptolemy/vergil/actor/img/multi_out.gif", 783 GUIUtilities.LARGE_ICON }, 784 { "/ptolemy/vergil/actor/img/multi_out_o.gif", 785 GUIUtilities.ROLLOVER_ICON }, 786 { "/ptolemy/vergil/actor/img/multi_out_ov.gif", 787 GUIUtilities.ROLLOVER_SELECTED_ICON }, 788 { "/ptolemy/vergil/actor/img/multi_out_on.gif", 789 GUIUtilities.SELECTED_ICON } }); 790 791 /** Action for creating a new output port. */ 792 private Action _newOutputPortAction = new NewPortAction( 793 ExternalIOPortController._GENERIC_OUTPUT, "New output port", 794 KeyEvent.VK_O, 795 new String[][] { 796 { "/ptolemy/vergil/actor/img/single_out.gif", 797 GUIUtilities.LARGE_ICON }, 798 { "/ptolemy/vergil/actor/img/single_out_o.gif", 799 GUIUtilities.ROLLOVER_ICON }, 800 { "/ptolemy/vergil/actor/img/single_out_ov.gif", 801 GUIUtilities.ROLLOVER_SELECTED_ICON }, 802 { "/ptolemy/vergil/actor/img/single_out_on.gif", 803 GUIUtilities.SELECTED_ICON } }); 804 805 /** The port dialog factory. */ 806 private PortDialogAction _portDialogAction; 807 808 /** The interactor for creating new relations. */ 809 private RelationCreator _relationCreator; 810 811 /** The filter for shortcut operations. This is used for creation 812 * of relations and creation of links from relations. Under PC, 813 * this is a control-1 click. Under Mac OS X, the control key is 814 * used for context menus and this corresponds to the command-1 815 * click. For details, see the Apple java archive 816 * http://lists.apple.com/archives/java-dev User: archives, 817 * passwd: archives 818 */ 819 private MouseFilter _shortcutFilter = new MouseFilter( 820 InputEvent.BUTTON1_MASK, 821 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(), 822 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); 823 824 /////////////////////////////////////////////////////////////////// 825 //// inner classes //// 826 827 /////////////////////////////////////////////////////////////////// 828 //// ListenToActorFactory 829 private class ListenToActorFactory implements MenuItemFactory { 830 /** Add an item to the given context menu that will open a listen 831 * to actor window. 832 * @param menu The context menu. 833 * @param object The object whose ports are being manipulated. 834 */ 835 @Override 836 public JMenuItem create(final JContextMenu menu, NamedObj object) { 837 String name = "Listen to Actor"; 838 final NamedObj target = object; 839 840 _action = new ListenToAction(target, 841 ActorEditorGraphController.this); 842 _action.setConfiguration(_configuration); 843 return menu.add(_action, name); 844 } 845 846 /** Set the configuration for use by the help screen. 847 * @param configuration The configuration. 848 */ 849 public void setConfiguration(Configuration configuration) { 850 _configuration = configuration; 851 852 if (_action != null) { 853 _action.setConfiguration(_configuration); 854 } 855 } 856 857 private ListenToAction _action; 858 859 private Configuration _configuration; 860 } 861}