001/* The graph controller for FSM models. 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.modal; 029 030import java.awt.Toolkit; 031import java.awt.event.ActionEvent; 032import java.awt.event.InputEvent; 033import java.awt.event.KeyEvent; 034import java.awt.geom.Point2D; 035 036import javax.swing.Action; 037import javax.swing.JMenu; 038import javax.swing.JToolBar; 039 040import diva.canvas.Figure; 041import diva.canvas.FigureLayer; 042import diva.canvas.Site; 043import diva.canvas.connector.AutonomousSite; 044import diva.canvas.connector.Connector; 045import diva.canvas.connector.ConnectorManipulator; 046import diva.canvas.event.LayerEvent; 047import diva.canvas.event.MouseFilter; 048import diva.canvas.interactor.AbstractInteractor; 049import diva.canvas.interactor.CompositeInteractor; 050import diva.canvas.interactor.GrabHandle; 051import diva.canvas.interactor.Interactor; 052import diva.graph.GraphException; 053import diva.graph.GraphPane; 054import diva.graph.NodeRenderer; 055import diva.gui.GUIUtilities; 056import diva.gui.toolbox.FigureIcon; 057import ptolemy.actor.gui.Configuration; 058import ptolemy.domains.modal.kernel.State; 059import ptolemy.kernel.CompositeEntity; 060import ptolemy.kernel.Entity; 061import ptolemy.kernel.util.ChangeRequest; 062import ptolemy.kernel.util.InternalErrorException; 063import ptolemy.kernel.util.KernelException; 064import ptolemy.kernel.util.Location; 065import ptolemy.kernel.util.NamedObj; 066import ptolemy.moml.LibraryAttribute; 067import ptolemy.moml.MoMLChangeRequest; 068import ptolemy.util.MessageHandler; 069import ptolemy.vergil.actor.ExternalIOPortController; 070import ptolemy.vergil.basic.BasicGraphFrame; 071import ptolemy.vergil.basic.NamedObjController; 072import ptolemy.vergil.kernel.AttributeController; 073import ptolemy.vergil.kernel.Link; 074import ptolemy.vergil.kernel.PortDialogAction; 075import ptolemy.vergil.modal.modal.ModalTransitionController; 076import ptolemy.vergil.toolbox.FigureAction; 077import ptolemy.vergil.unit.ConfigureUnitsAction; 078 079/////////////////////////////////////////////////////////////////// 080//// FSMGraphController 081 082/** 083 A Graph Controller for FSM models. This controller allows states to be 084 dragged and dropped onto its graph. Links can be created by 085 control-clicking and dragging from one state to another. 086 087 @author Steve Neuendorffer, Contributor: Edward A. Lee 088 @version $Id$ 089 @since Ptolemy II 8.0 090 @Pt.ProposedRating Red (eal) 091 @Pt.AcceptedRating Red (johnr) 092 */ 093public class FSMGraphController extends FSMViewerGraphController { 094 /** Create a new basic controller with default 095 * terminal and edge interactors. 096 */ 097 public FSMGraphController() { 098 super(); 099 } 100 101 /////////////////////////////////////////////////////////////////// 102 //// public methods //// 103 104 /** Add commands to the specified menu and toolbar, as appropriate 105 * for this controller. In this class, commands are added to create 106 * ports and relations. 107 * @param menu The menu to add to, or null if none. 108 * @param toolbar The toolbar to add to, or null if none. 109 */ 110 @Override 111 public void addToMenuAndToolbar(JMenu menu, JToolBar toolbar) { 112 super.addToMenuAndToolbar(menu, toolbar); 113 114 // Only include the port actions if there is an actor library. 115 // The ptinyViewer configuration uses this. 116 if (getConfiguration().getEntity("actor library") != null) { 117 diva.gui.GUIUtilities.addMenuItem(menu, _newInputPortAction); 118 diva.gui.GUIUtilities.addToolBarButton(toolbar, 119 _newInputPortAction); 120 diva.gui.GUIUtilities.addMenuItem(menu, _newOutputPortAction); 121 diva.gui.GUIUtilities.addToolBarButton(toolbar, 122 _newOutputPortAction); 123 diva.gui.GUIUtilities.addMenuItem(menu, _newInOutPortAction); 124 diva.gui.GUIUtilities.addToolBarButton(toolbar, 125 _newInOutPortAction); 126 diva.gui.GUIUtilities.addMenuItem(menu, _newInputMultiportAction); 127 diva.gui.GUIUtilities.addToolBarButton(toolbar, 128 _newInputMultiportAction); 129 diva.gui.GUIUtilities.addMenuItem(menu, _newOutputMultiportAction); 130 diva.gui.GUIUtilities.addToolBarButton(toolbar, 131 _newOutputMultiportAction); 132 diva.gui.GUIUtilities.addMenuItem(menu, _newInOutMultiportAction); 133 diva.gui.GUIUtilities.addToolBarButton(toolbar, 134 _newInOutMultiportAction); 135 136 // Add an item that adds new states. 137 menu.addSeparator(); 138 diva.gui.GUIUtilities.addMenuItem(menu, _newStateAction); 139 diva.gui.GUIUtilities.addToolBarButton(toolbar, _newStateAction); 140 } 141 } 142 143 /** Set the configuration. The configuration is used when 144 * opening documentation files. 145 * @param configuration The configuration. 146 */ 147 @Override 148 public void setConfiguration(Configuration configuration) { 149 super.setConfiguration(configuration); 150 151 if (_portDialogAction != null) { 152 _portDialogAction.setConfiguration(configuration); 153 } 154 155 if (_configureUnitsAction != null) { 156 _configureUnitsAction.setConfiguration(configuration); 157 } 158 159 } 160 161 /////////////////////////////////////////////////////////////////// 162 //// protected methods //// 163 164 /** Create the controllers for nodes in this graph. 165 * In this class, controllers with FULL access are created. 166 * This is called by the constructor, so derived classes that 167 * override this must be careful not to reference local variables 168 * defined in the derived classes, because the derived classes 169 * will not have been fully constructed by the time this is called. 170 */ 171 @Override 172 protected void _createControllers() { 173 _attributeController = new AttributeController(this, 174 AttributeController.FULL); 175 _portController = new ExternalIOPortController(this, 176 AttributeController.FULL); 177 _stateController = new StateController(this, AttributeController.FULL); 178 _modalTransitionController = new ModalTransitionController(this); 179 _transitionController = new TransitionController(this); 180 } 181 182 /** Initialize interaction on the graph pane. This method 183 * is called by the setGraphPane() method of the superclass. 184 * This initialization cannot be done in the constructor because 185 * the controller does not yet have a reference to its pane 186 * at that time. 187 */ 188 @Override 189 protected void initializeInteraction() { 190 // NOTE: This method name does not have a leading underscore 191 // because it is a diva method. 192 super.initializeInteraction(); 193 194 /* GraphPane pane = */getGraphPane(); 195 196 // Add a menu command to configure the ports. 197 _portDialogAction = new PortDialogAction("Ports"); 198 _portDialogAction.setConfiguration(getConfiguration()); 199 200 _configureMenuFactory.addAction(_portDialogAction, "Customize"); 201 _configureUnitsAction = new ConfigureUnitsAction("Units Constraints"); 202 _configureMenuFactory.addAction(_configureUnitsAction, "Customize"); 203 _configureUnitsAction.setConfiguration(getConfiguration()); 204 205 // Create the interactor that drags new edges. 206 _linkCreator = new LinkCreator(); 207 _linkCreator.setMouseFilter(_shortcutFilter); 208 209 // NOTE: Do not use _initializeInteraction() because we are 210 // still in the constructor, and that method is overloaded in 211 // derived classes. 212 ((CompositeInteractor) _stateController.getNodeInteractor()) 213 .addInteractor(_linkCreator); 214 } 215 216 /** Initialize interactions for the specified controller. This 217 * method is called when a new controller is constructed. In this 218 * class, this method attaches a link creator to the controller 219 * if the controller is an instance of StateController. 220 * @param controller The controller for which to initialize interaction. 221 */ 222 @Override 223 protected void _initializeInteraction(NamedObjController controller) { 224 super._initializeInteraction(controller); 225 226 if (controller instanceof StateController) { 227 Interactor interactor = controller.getNodeInteractor(); 228 229 if (interactor instanceof CompositeInteractor) { 230 ((CompositeInteractor) interactor).addInteractor(_linkCreator); 231 } 232 } 233 } 234 235 /////////////////////////////////////////////////////////////////// 236 //// private variables //// 237 238 private ConfigureUnitsAction _configureUnitsAction; 239 240 /** The interactor that interactively creates edges. */ 241 private LinkCreator _linkCreator; // For control-click 242 243 /** The action for creating states. */ 244 private NewStateAction _newStateAction = new NewStateAction(); 245 246 /** The filter for shortcut operations. This is used for creation 247 * of relations and creation of links from relations. Under PC, 248 * this is a control-1 click. Under Mac OS X, the control key is 249 * used for context menus and this corresponds to the command-1 250 * click. For details, see the Apple java archive 251 * http://lists.apple.com/archives/java-dev User: archives, 252 * passwd: archives 253 */ 254 private MouseFilter _shortcutFilter = new MouseFilter( 255 InputEvent.BUTTON1_MASK, 256 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); 257 258 /** Action for creating a new input port. */ 259 private Action _newInputPortAction = new NewPortAction( 260 ExternalIOPortController._GENERIC_INPUT, "New input port", 261 KeyEvent.VK_I, 262 new String[][] { 263 { "/ptolemy/vergil/actor/img/single_in.gif", 264 GUIUtilities.LARGE_ICON }, 265 { "/ptolemy/vergil/actor/img/single_in_o.gif", 266 GUIUtilities.ROLLOVER_ICON }, 267 { "/ptolemy/vergil/actor/img/single_in_ov.gif", 268 GUIUtilities.ROLLOVER_SELECTED_ICON }, 269 { "/ptolemy/vergil/actor/img/single_in_on.gif", 270 GUIUtilities.SELECTED_ICON } }); 271 272 /** Action for creating a new output port. */ 273 private Action _newOutputPortAction = new NewPortAction( 274 ExternalIOPortController._GENERIC_OUTPUT, "New output port", 275 KeyEvent.VK_O, 276 new String[][] { 277 { "/ptolemy/vergil/actor/img/single_out.gif", 278 GUIUtilities.LARGE_ICON }, 279 { "/ptolemy/vergil/actor/img/single_out_o.gif", 280 GUIUtilities.ROLLOVER_ICON }, 281 { "/ptolemy/vergil/actor/img/single_out_ov.gif", 282 GUIUtilities.ROLLOVER_SELECTED_ICON }, 283 { "/ptolemy/vergil/actor/img/single_out_on.gif", 284 GUIUtilities.SELECTED_ICON } }); 285 286 /** Action for creating a new input/output port. */ 287 private Action _newInOutPortAction = new NewPortAction( 288 ExternalIOPortController._GENERIC_INOUT, "New input/output port", 289 KeyEvent.VK_P, 290 new String[][] { 291 { "/ptolemy/vergil/actor/img/single_inout.gif", 292 GUIUtilities.LARGE_ICON }, 293 { "/ptolemy/vergil/actor/img/single_inout_o.gif", 294 GUIUtilities.ROLLOVER_ICON }, 295 { "/ptolemy/vergil/actor/img/single_inout_ov.gif", 296 GUIUtilities.ROLLOVER_SELECTED_ICON }, 297 { "/ptolemy/vergil/actor/img/single_inout_on.gif", 298 GUIUtilities.SELECTED_ICON } }); 299 300 /** Action for creating a new input multiport. */ 301 private Action _newInputMultiportAction = new NewPortAction( 302 ExternalIOPortController._GENERIC_INPUT_MULTIPORT, 303 "New input multiport", KeyEvent.VK_N, 304 new String[][] { 305 { "/ptolemy/vergil/actor/img/multi_in.gif", 306 GUIUtilities.LARGE_ICON }, 307 { "/ptolemy/vergil/actor/img/multi_in_o.gif", 308 GUIUtilities.ROLLOVER_ICON }, 309 { "/ptolemy/vergil/actor/img/multi_in_ov.gif", 310 GUIUtilities.ROLLOVER_SELECTED_ICON }, 311 { "/ptolemy/vergil/actor/img/multi_in_on.gif", 312 GUIUtilities.SELECTED_ICON } }); 313 314 /** Action for creating a new output multiport. */ 315 private Action _newOutputMultiportAction = new NewPortAction( 316 ExternalIOPortController._GENERIC_OUTPUT_MULTIPORT, 317 "New output multiport", KeyEvent.VK_U, 318 new String[][] { 319 { "/ptolemy/vergil/actor/img/multi_out.gif", 320 GUIUtilities.LARGE_ICON }, 321 { "/ptolemy/vergil/actor/img/multi_out_o.gif", 322 GUIUtilities.ROLLOVER_ICON }, 323 { "/ptolemy/vergil/actor/img/multi_out_ov.gif", 324 GUIUtilities.ROLLOVER_SELECTED_ICON }, 325 { "/ptolemy/vergil/actor/img/multi_out_on.gif", 326 GUIUtilities.SELECTED_ICON } }); 327 328 /** Action for creating a new inout multiport. */ 329 private Action _newInOutMultiportAction = new NewPortAction( 330 ExternalIOPortController._GENERIC_INOUT_MULTIPORT, 331 "New input/output multiport", KeyEvent.VK_T, 332 new String[][] { 333 { "/ptolemy/vergil/actor/img/multi_inout.gif", 334 GUIUtilities.LARGE_ICON }, 335 { "/ptolemy/vergil/actor/img/multi_inout_o.gif", 336 GUIUtilities.ROLLOVER_ICON }, 337 { "/ptolemy/vergil/actor/img/multi_inout_ov.gif", 338 GUIUtilities.ROLLOVER_SELECTED_ICON }, 339 { "/ptolemy/vergil/actor/img/multi_inout_on.gif", 340 GUIUtilities.SELECTED_ICON } }); 341 342 /** The port dialog factory. */ 343 private PortDialogAction _portDialogAction; 344 345 /** Prototype state for rendering. */ 346 private static Location _prototypeState; 347 348 static { 349 CompositeEntity container = new CompositeEntity(); 350 351 try { 352 State state = new State(container, "S"); 353 _prototypeState = new Location(state, "_location"); 354 } catch (KernelException ex) { 355 // This should not happen. 356 throw new InternalErrorException(null, ex, null); 357 } 358 } 359 360 /////////////////////////////////////////////////////////////////// 361 //// inner classes //// 362 /////////////////////////////////////////////////////////////////// 363 //// LinkCreator 364 365 /** An interactor that interactively drags edges from one terminal 366 * to another. 367 */ 368 protected class LinkCreator extends AbstractInteractor { 369 /** Initiate creation of an arc. */ 370 @Override 371 public void mousePressed(LayerEvent event) { 372 Figure source = event.getFigureSource(); 373 NamedObj sourceObject = (NamedObj) source.getUserObject(); 374 375 Link link = new Link(); 376 377 // Set the tail, going through the model so the link is added 378 // to the list of links. 379 FSMGraphModel model = (FSMGraphModel) getGraphModel(); 380 model.getArcModel().setTail(link, sourceObject); 381 382 try { 383 // add it to the foreground layer. 384 FigureLayer layer = getGraphPane().getForegroundLayer(); 385 Site headSite; 386 Site tailSite; 387 388 // Temporary sites. One of these will get removed later. 389 headSite = new AutonomousSite(layer, event.getLayerX(), 390 event.getLayerY()); 391 tailSite = new AutonomousSite(layer, event.getLayerX(), 392 event.getLayerY()); 393 394 // Render the edge. 395 Connector c = getEdgeController(link).render(link, layer, 396 tailSite, headSite); 397 398 // get the actual attach site. 399 tailSite = getEdgeController(link).getConnectorTarget() 400 .getTailSite(c, source, event.getLayerX(), 401 event.getLayerY()); 402 403 if (tailSite == null) { 404 throw new RuntimeException("Invalid connector target: " 405 + "no valid site found for tail of new connector."); 406 } 407 408 // And reattach the connector. 409 c.setTailSite(tailSite); 410 411 // Add it to the selection so it gets a manipulator, and 412 // make events go to the grab-handle under the mouse 413 Figure ef = getFigure(link); 414 getSelectionModel().addSelection(ef); 415 416 ConnectorManipulator cm = (ConnectorManipulator) ef.getParent(); 417 GrabHandle gh = cm.getHeadHandle(); 418 layer.grabPointer(event, gh); 419 } catch (Exception ex) { 420 MessageHandler.error("Drag connection failed:", ex); 421 } 422 } 423 } 424 425 /////////////////////////////////////////////////////////////////// 426 //// NewStateAction 427 428 /** An action to create a new state. */ 429 @SuppressWarnings("serial") 430 public class NewStateAction extends FigureAction { 431 /** Construct a new state. */ 432 public NewStateAction() { 433 super("New State"); 434 putValue("tooltip", "New State"); 435 436 NodeRenderer renderer = new StateController.StateRenderer( 437 getGraphModel()); 438 Figure figure = renderer.render(_prototypeState); 439 440 // Standard toolbar icons are 25x25 pixels. 441 FigureIcon icon = new FigureIcon(figure, 25, 25, 1, true); 442 putValue(diva.gui.GUIUtilities.LARGE_ICON, icon); 443 putValue("tooltip", "New State"); 444 putValue(diva.gui.GUIUtilities.MNEMONIC_KEY, 445 Integer.valueOf(KeyEvent.VK_W)); 446 } 447 448 /** Execute the action. */ 449 @Override 450 public void actionPerformed(ActionEvent e) { 451 super.actionPerformed(e); 452 453 double x; 454 double y; 455 456 if (getSourceType() == TOOLBAR_TYPE 457 || getSourceType() == MENUBAR_TYPE) { 458 // No location in the action, so put it in the middle. 459 BasicGraphFrame frame = FSMGraphController.this.getFrame(); 460 Point2D center; 461 462 if (frame != null) { 463 // Put in the middle of the visible part. 464 center = frame.getCenter(); 465 x = center.getX(); 466 y = center.getY(); 467 } else { 468 // Put in the middle of the pane. 469 GraphPane pane = getGraphPane(); 470 center = pane.getSize(); 471 x = center.getX() / 2; 472 y = center.getY() / 2; 473 } 474 } else { 475 x = getX(); 476 y = getY(); 477 } 478 479 FSMGraphModel graphModel = (FSMGraphModel) getGraphModel(); 480 NamedObj toplevel = graphModel.getPtolemyModel(); 481 482 String stateName = toplevel.uniqueName("state"); 483 484 // Create the state. 485 String moml = null; 486 String locationName = "_location"; 487 488 // Try to get the class name for the state from the library, 489 // so that the library and the toolbar are assured of creating 490 // the same object. 491 try { 492 LibraryAttribute attribute = (LibraryAttribute) toplevel 493 .getAttribute("_library", LibraryAttribute.class); 494 495 if (attribute != null) { 496 CompositeEntity library = attribute.getLibrary(); 497 Entity prototype = library.getEntity("state"); 498 499 if (prototype != null) { 500 moml = prototype.exportMoML(stateName); 501 502 // FIXME: Get location name from prototype. 503 } 504 } 505 } catch (Exception ex) { 506 // Ignore and use the default. 507 // Avoid a FindBugs warning about ignored exception. 508 moml = null; 509 } 510 511 if (moml == null) { 512 moml = "<entity name=\"" + stateName 513 + "\" class=\"ptolemy.domains.modal.kernel.State\">\n" 514 + "<property name=\"" + locationName 515 + "\" class=\"ptolemy.kernel.util.Location\"" 516 + " value=\"[" + x + ", " + y + "]\"/>\n" 517 + "</entity>\n"; 518 } 519 520 ChangeRequest request = new MoMLChangeRequest(this, toplevel, moml); 521 toplevel.requestChange(request); 522 523 try { 524 request.waitForCompletion(); 525 } catch (Exception ex) { 526 throw new GraphException(ex); 527 } 528 } 529 } 530}