001/* A simple graph view for Ptolemy 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.event.ActionEvent; 031import java.awt.event.ActionListener; 032import java.awt.event.KeyEvent; 033import java.io.IOException; 034import java.io.Writer; 035import java.util.HashSet; 036import java.util.LinkedList; 037import java.util.List; 038import java.util.Set; 039 040import javax.swing.JMenu; 041import javax.swing.JMenuItem; 042 043import diva.graph.GraphPane; 044import ptolemy.actor.IOPort; 045import ptolemy.actor.gui.DebugListenerTableau; 046import ptolemy.actor.gui.Effigy; 047import ptolemy.actor.gui.Tableau; 048import ptolemy.actor.gui.TextEffigy; 049import ptolemy.data.BooleanToken; 050import ptolemy.domains.modal.kernel.FSMActor; 051import ptolemy.domains.modal.kernel.State; 052import ptolemy.domains.modal.modal.ModalPort; 053import ptolemy.domains.modal.modal.RefinementPort; 054import ptolemy.gui.ComponentDialog; 055import ptolemy.gui.Query; 056import ptolemy.kernel.CompositeEntity; 057import ptolemy.kernel.util.IllegalActionException; 058import ptolemy.kernel.util.InternalErrorException; 059import ptolemy.kernel.util.KernelException; 060import ptolemy.kernel.util.NamedObj; 061import ptolemy.moml.LibraryAttribute; 062import ptolemy.util.CancelException; 063import ptolemy.util.MessageHandler; 064import ptolemy.vergil.basic.BasicGraphPane; 065import ptolemy.vergil.basic.ExtendedGraphFrame; 066 067/////////////////////////////////////////////////////////////////// 068//// FSMGraphFrame 069 070/** 071 This is a graph editor frame for ptolemy FSM models. Given a composite 072 entity and a tableau, it creates an editor and populates the menus 073 and toolbar. This overrides the base class to associate with the 074 editor an instance of FSMGraphController. 075 076 @author Steve Neuendorffer, Contributor: Edward A. Lee 077 @version $Id$ 078 @since Ptolemy II 8.0 079 @Pt.ProposedRating Red (neuendor) 080 @Pt.AcceptedRating Red (johnr) 081 */ 082@SuppressWarnings("serial") 083public class FSMGraphFrame extends ExtendedGraphFrame 084 implements ActionListener { 085 086 /** Construct a frame associated with the specified FSM model. 087 * After constructing this, it is necessary 088 * to call setVisible(true) to make the frame appear. 089 * This is typically done by calling show() on the controlling tableau. 090 * This constructor results in a graph frame that obtains its library 091 * either from the model (if it has one) or the default library defined 092 * in the configuration. 093 * @see Tableau#show() 094 * @param entity The model to put in this frame. 095 * @param tableau The tableau responsible for this frame. 096 */ 097 public FSMGraphFrame(CompositeEntity entity, Tableau tableau) { 098 this(entity, tableau, null); 099 } 100 101 /** Construct a frame associated with the specified FSM model. 102 * After constructing this, it is necessary 103 * to call setVisible(true) to make the frame appear. 104 * This is typically done by calling show() on the controlling tableau. 105 * This constructor results in a graph frame that obtains its library 106 * either from the model (if it has one), or the <i>defaultLibrary</i> 107 * argument (if it is non-null), or the default library defined 108 * in the configuration. 109 * @see Tableau#show() 110 * @param entity The model to put in this frame. 111 * @param tableau The tableau responsible for this frame. 112 * @param defaultLibrary An attribute specifying the default library 113 * to use if the model does not have a library. 114 */ 115 public FSMGraphFrame(CompositeEntity entity, Tableau tableau, 116 LibraryAttribute defaultLibrary) { 117 super(entity, tableau, defaultLibrary); 118 119 // Override the default help file. 120 helpFile = "ptolemy/configs/doc/vergilFsmEditorHelp.htm"; 121 } 122 123 /** React to the actions specific to this FSM graph frame. 124 * 125 * @param e The action event. 126 */ 127 @Override 128 public void actionPerformed(ActionEvent e) { 129 JMenuItem target = (JMenuItem) e.getSource(); 130 String actionCommand = target.getActionCommand(); 131 if (actionCommand.equals("Import Design Pattern")) { 132 importDesignPattern(); 133 } else if (actionCommand.equals("Export Design Pattern")) { 134 exportDesignPattern(); 135 } 136 } 137 138 /** Get the currently selected objects from this document, if any, 139 * and place them on the clipboard in MoML format. 140 */ 141 @Override 142 public void copy() { 143 HashSet<NamedObj> namedObjSet = _getSelectionSet(); 144 try { 145 for (NamedObj namedObj : namedObjSet) { 146 if (namedObj instanceof State) { 147 ((State) namedObj).saveRefinementsInConfigurer 148 .setToken(BooleanToken.TRUE); 149 } 150 } 151 super.copy(); 152 } catch (IllegalActionException e) { 153 MessageHandler.error( 154 "Unable to set attributes of the selected " + "states."); 155 } finally { 156 for (NamedObj namedObj : namedObjSet) { 157 if (namedObj instanceof State) { 158 try { 159 ((State) namedObj).saveRefinementsInConfigurer 160 .setToken(BooleanToken.FALSE); 161 } catch (IllegalActionException e) { 162 // Ignore. 163 } 164 } 165 } 166 } 167 } 168 169 /////////////////////////////////////////////////////////////////// 170 //// protected methods //// 171 172 /** Create the menus that are used by this frame. 173 * It is essential that _createGraphPane() be called before this. 174 */ 175 @Override 176 protected void _addMenus() { 177 super._addMenus(); 178 179 // _graphMenu is instantiated in BasicGraphFrame. 180 _addLayoutMenu(_graphMenu); 181 182 // Add any commands to graph menu and toolbar that the controller 183 // wants in the graph menu and toolbar. 184 _controller.addToMenuAndToolbar(_graphMenu, _toolbar); 185 186 JMenuItem[] debugMenuItems = _debugMenuItems(); 187 188 // NOTE: This has to be initialized here rather than 189 // statically because this method is called by the constructor 190 // of the base class, and static initializers have not yet 191 // been run. 192 _debugMenu = new JMenu("Debug"); 193 _debugMenu.setMnemonic(KeyEvent.VK_D); 194 195 ActionListener debugMenuListener = _getDebugMenuListener(); 196 197 // Set the action command and listener for each menu item. 198 for (JMenuItem debugMenuItem : debugMenuItems) { 199 debugMenuItem.setActionCommand(debugMenuItem.getText()); 200 debugMenuItem.addActionListener(debugMenuListener); 201 _debugMenu.add(debugMenuItem); 202 } 203 204 _menubar.add(_debugMenu); 205 } 206 207 /** Return a new DebugMenuListener. 208 * @return the new DebugMenuListener. 209 */ 210 protected ActionListener _getDebugMenuListener() { 211 return new DebugMenuListener(); 212 } 213 214 /** Return an array of debug menu items. 215 * @return an array of debug menu items. 216 */ 217 protected JMenuItem[] _debugMenuItems() { 218 // Add debug menu. 219 JMenuItem[] debugMenuItems = { 220 new JMenuItem("Listen to Director", KeyEvent.VK_D), 221 new JMenuItem("Listen to State Machine", KeyEvent.VK_L), 222 new JMenuItem(_getAnimationMenuText(), KeyEvent.VK_A), 223 new JMenuItem("Stop Animating", KeyEvent.VK_S), }; 224 return debugMenuItems; 225 } 226 227 /** Close the window. Override the base class to remove the debug 228 * listener, if there is one. 229 * @return False if the user cancels on a save query. 230 */ 231 @Override 232 protected boolean _close() { 233 // Running with a headless display (Xvfb) could result in the model being null. 234 if (getModel() != null) { 235 getModel().removeDebugListener(_controller); 236 } 237 return super._close(); 238 } 239 240 /** Create the items in the File menu. A null element in the array 241 * represents a separator in the menu. 242 * 243 * @return The items in the File menu. 244 */ 245 @Override 246 protected JMenuItem[] _createFileMenuItems() { 247 JMenuItem[] fileMenuItems = super._createFileMenuItems(); 248 int i = 0; 249 for (JMenuItem item : fileMenuItems) { 250 i++; 251 if (item.getActionCommand().equals("Save As")) { 252 // Add a SaveAsDesignPattern here. 253 JMenuItem importItem = new JMenuItem("Import Design Pattern", 254 KeyEvent.VK_D); 255 JMenuItem exportItem = new JMenuItem("Export Design Pattern", 256 KeyEvent.VK_D); 257 JMenuItem[] newItems = new JMenuItem[fileMenuItems.length + 4]; 258 System.arraycopy(fileMenuItems, 0, newItems, 0, i); 259 newItems[i + 1] = importItem; 260 importItem.addActionListener(this); 261 newItems[i + 2] = exportItem; 262 exportItem.addActionListener(this); 263 System.arraycopy(fileMenuItems, i, newItems, i + 4, 264 fileMenuItems.length - i); 265 return newItems; 266 } 267 } 268 return fileMenuItems; 269 } 270 271 /** Create a new graph pane. Note that this method is called in 272 * constructor of the base class, so it must be careful to not reference 273 * local variables that may not have yet been created. 274 * @param entity The object to be displayed in the pane (which must be 275 * an instance of CompositeEntity). 276 * @return The pane that is created. 277 */ 278 @Override 279 protected GraphPane _createGraphPane(NamedObj entity) { 280 _controller = new FSMGraphController(); 281 _controller.setConfiguration(getConfiguration()); 282 _controller.setFrame(this); 283 284 // NOTE: The cast is safe because the constructor accepts 285 // only CompositeEntity. 286 final FSMGraphModel graphModel = new FSMGraphModel( 287 (CompositeEntity) entity); 288 return new BasicGraphPane(_controller, graphModel, entity); 289 } 290 291 /** Export the model into the writer with the given name. 292 * 293 * @param writer The writer. 294 * @param model The model to export. 295 * @param name The name of the exported model. 296 * @exception IOException If an I/O error occurs. 297 */ 298 @Override 299 protected void _exportDesignPattern(Writer writer, NamedObj model, 300 String name) throws IOException { 301 if (_query != null && _query.hasEntry("selected") 302 && _query.getBooleanValue("selected")) { 303 List<State> modifiedStates = new LinkedList<State>(); 304 try { 305 Set<?> set = _getSelectionSet(); 306 for (Object object : set) { 307 if (object instanceof State) { 308 State state = (State) object; 309 modifiedStates.add(state); 310 state.saveRefinementsInConfigurer 311 .setToken(BooleanToken.TRUE); 312 } 313 } 314 super._exportDesignPattern(writer, model, name); 315 } catch (IllegalActionException e) { 316 throw new InternalErrorException(null, e, 317 "Unable to set " + "attributes for the states."); 318 } finally { 319 for (State state : modifiedStates) { 320 try { 321 state.saveRefinementsInConfigurer 322 .setToken(BooleanToken.FALSE); 323 } catch (IllegalActionException e) { 324 // Ignore. 325 } 326 } 327 } 328 } else { 329 ((FSMActor) model).exportSubmodel(writer, 0, name); 330 } 331 } 332 333 /** Finish exporting a design pattern. 334 */ 335 @Override 336 protected void _finishExportDesignPattern() { 337 super._finishExportDesignPattern(); 338 339 for (IOPort port : _modifiedPorts) { 340 try { 341 port.setInput(true); 342 } catch (IllegalActionException e) { 343 // Ignore. 344 } 345 } 346 } 347 348 /** Return the text to be used in the animation menu item. In this base 349 * class, always return "Animate States". 350 * 351 * @return The text for the menu item. 352 */ 353 protected String _getAnimationMenuText() { 354 return "Animate States"; 355 } 356 357 /** Prepare to export a design pattern. 358 * 359 * @exception InternalErrorException Thrown if attributes of the ports to 360 * be exported cannot be set. 361 */ 362 @Override 363 protected void _prepareExportDesignPattern() { 364 super._prepareExportDesignPattern(); 365 366 try { 367 FSMActor actor = (FSMActor) getModel(); 368 List<IOPort> ports = actor.portList(); 369 _modifiedPorts.clear(); 370 for (IOPort port : ports) { 371 if (port instanceof RefinementPort && port.isInput() 372 && port.isOutput()) { 373 List<IOPort> connectedPorts = port.connectedPortList(); 374 for (IOPort connectedPort : connectedPorts) { 375 if (connectedPort instanceof ModalPort 376 && !connectedPort.isInput()) { 377 _modifiedPorts.add(port); 378 port.setInput(false); 379 break; 380 } 381 } 382 } 383 } 384 } catch (Exception e) { 385 throw new InternalErrorException(null, e, 386 "Fail to prepare for " + "exporting a design pattern."); 387 } 388 } 389 390 /////////////////////////////////////////////////////////////////// 391 //// protected variables //// 392 393 /** The controller. 394 * The controller is protected so that the subclass 395 * (InterfaceAutomatonGraphFrame) can set it to a more specific 396 * controller. 397 */ 398 protected FSMGraphController _controller; 399 400 /** Debug menu for this frame. */ 401 protected JMenu _debugMenu; 402 403 /////////////////////////////////////////////////////////////////// 404 //// private variables //// 405 406 // The delay time specified that last time animation was set. 407 private long _lastDelayTime = 0; 408 409 // The list of ports modified by the previous invocation of 410 // _prepareExportDesignPattern(). 411 private List<IOPort> _modifiedPorts = new LinkedList<IOPort>(); 412 413 /////////////////////////////////////////////////////////////////// 414 //// inner classes //// 415 416 /** Listener for debug menu commands. */ 417 public class DebugMenuListener implements ActionListener { 418 /** React to a menu command. */ 419 @Override 420 public void actionPerformed(ActionEvent e) { 421 JMenuItem target = (JMenuItem) e.getSource(); 422 String actionCommand = target.getActionCommand(); 423 424 try { 425 if (actionCommand.equals("Listen to Director")) { 426 Effigy effigy = (Effigy) getTableau().getContainer(); 427 428 // Create a new text effigy inside this one. 429 Effigy textEffigy = new TextEffigy(effigy, 430 effigy.uniqueName("debug listener")); 431 DebugListenerTableau tableau = new DebugListenerTableau( 432 textEffigy, textEffigy.uniqueName("debugListener")); 433 tableau.setDebuggable( 434 ((FSMActor) getModel()).getDirector()); 435 } else if (actionCommand.equals("Listen to State Machine")) { 436 Effigy effigy = (Effigy) getTableau().getContainer(); 437 438 // Create a new text effigy inside this one. 439 Effigy textEffigy = new TextEffigy(effigy, 440 effigy.uniqueName("debug listener")); 441 DebugListenerTableau tableau = new DebugListenerTableau( 442 textEffigy, textEffigy.uniqueName("debugListener")); 443 tableau.setDebuggable(getModel()); 444 } else if (actionCommand.equals(_getAnimationMenuText())) { 445 // Dialog to ask for a delay time. 446 Query query = new Query(); 447 query.addLine("delay", "Time (in ms) to hold highlight", 448 Long.toString(_lastDelayTime)); 449 450 ComponentDialog dialog = new ComponentDialog( 451 FSMGraphFrame.this, "Delay for Animation", query); 452 453 if (dialog.buttonPressed().equals("OK")) { 454 try { 455 _lastDelayTime = Long 456 .parseLong(query.getStringValue("delay")); 457 _controller.setAnimationDelay(_lastDelayTime); 458 459 NamedObj model = getModel(); 460 461 if (model != null && _listeningTo != model) { 462 if (_listeningTo != null) { 463 _listeningTo 464 .removeDebugListener(_controller); 465 } 466 467 _listeningTo = model; 468 _listeningTo.addDebugListener(_controller); 469 } 470 } catch (NumberFormatException ex) { 471 MessageHandler 472 .error("Invalid time, which is required " 473 + "to be an integer: ", ex); 474 } 475 } 476 } else if (actionCommand.equals("Stop Animating") 477 && _listeningTo != null) { 478 _listeningTo.removeDebugListener(_controller); 479 _controller.clearAnimation(); 480 _listeningTo = null; 481 } 482 } catch (KernelException ex) { 483 try { 484 MessageHandler 485 .warning("Failed to create debug listener: " + ex); 486 } catch (CancelException exception) { 487 } 488 } 489 } 490 491 private NamedObj _listeningTo; 492 } 493 494}