001/* A graph view for the case construct for Ptolemy models. 002 003 Copyright (c) 2006-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.Component; 031import java.awt.event.ActionEvent; 032import java.awt.event.KeyEvent; 033import java.util.HashSet; 034import java.util.Iterator; 035import java.util.List; 036import java.util.Set; 037 038import javax.swing.JComponent; 039import javax.swing.JMenu; 040import javax.swing.JTabbedPane; 041import javax.swing.event.ChangeEvent; 042import javax.swing.event.ChangeListener; 043 044import diva.canvas.event.LayerAdapter; 045import diva.canvas.event.LayerEvent; 046import diva.graph.GraphModel; 047import diva.graph.GraphPane; 048import diva.graph.JGraph; 049import diva.gui.GUIUtilities; 050import ptolemy.actor.gui.Configuration; 051import ptolemy.actor.gui.Tableau; 052import ptolemy.actor.lib.hoc.Case; 053import ptolemy.actor.lib.hoc.MultiCompositeActor; 054import ptolemy.actor.lib.hoc.Refinement; 055import ptolemy.gui.ComponentDialog; 056import ptolemy.gui.Query; 057import ptolemy.kernel.Port; 058import ptolemy.kernel.util.Nameable; 059import ptolemy.kernel.util.NamedObj; 060import ptolemy.moml.LibraryAttribute; 061import ptolemy.moml.MoMLChangeRequest; 062import ptolemy.util.MessageHandler; 063import ptolemy.util.StringUtilities; 064import ptolemy.vergil.actor.ActorEditorGraphController; 065import ptolemy.vergil.actor.ActorGraphFrame; 066import ptolemy.vergil.actor.ActorGraphModel; 067import ptolemy.vergil.basic.EditorDropTarget; 068import ptolemy.vergil.toolbox.FigureAction; 069 070/////////////////////////////////////////////////////////////////// 071//// CaseGraphFrame 072 073/** 074 This is a graph editor frame for ptolemy case models. 075 076 @author Edward A. Lee 077 @version $Id$ 078 @since Ptolemy II 8.0 079 @Pt.ProposedRating Yellow (eal) 080 @Pt.AcceptedRating Red (johnr) 081 */ 082@SuppressWarnings("serial") 083public class CaseGraphFrame extends ActorGraphFrame implements ChangeListener { 084 /** Construct a frame associated with the specified case actor. 085 * After constructing this, it is necessary 086 * to call setVisible(true) to make the frame appear. 087 * This is typically done by calling show() on the controlling tableau. 088 * This constructor results in a graph frame that obtains its library 089 * either from the model (if it has one) or the default library defined 090 * in the configuration. 091 * @see Tableau#show() 092 * @param entity The model to put in this frame. 093 * @param tableau The tableau responsible for this frame. 094 */ 095 public CaseGraphFrame(Case entity, Tableau tableau) { 096 this(entity, tableau, null); 097 } 098 099 /** Construct a frame associated with the specified case actor. 100 * After constructing this, it is necessary 101 * to call setVisible(true) to make the frame appear. 102 * This is typically done by calling show() on the controlling tableau. 103 * This constructor results in a graph frame that obtains its library 104 * either from the model (if it has one), or the <i>defaultLibrary</i> 105 * argument (if it is non-null), or the default library defined 106 * in the configuration. 107 * @see Tableau#show() 108 * @param entity The model to put in this frame. 109 * @param tableau The tableau responsible for this frame. 110 * @param defaultLibrary An attribute specifying the default library 111 * to use if the model does not have a library. 112 */ 113 public CaseGraphFrame(Case entity, Tableau tableau, 114 LibraryAttribute defaultLibrary) { 115 super(entity, tableau, defaultLibrary); 116 117 _case = entity; 118 _addCaseAction = new AddCaseAction(); 119 _removeCaseAction = new RemoveCaseAction(); 120 121 // Override the default help file. 122 // FIXME 123 // helpFile = "ptolemy/configs/doc/vergilFsmEditorHelp.htm"; 124 } 125 126 /////////////////////////////////////////////////////////////////// 127 //// public methods //// 128 129 /** Open the container, if any, of the entity. 130 * If this entity has no container, then do nothing. 131 */ 132 @Override 133 public void openContainer() { 134 // Method overridden since the parent will go from the refinement to 135 // the case, which is where we were in the first place. 136 if (_case != _case.toplevel()) { 137 try { 138 Configuration configuration = getConfiguration(); 139 // FIXME: do what with the return value? 140 configuration.openInstance(_case.getContainer()); 141 } catch (Throwable throwable) { 142 MessageHandler.error("Failed to open container", throwable); 143 } 144 } 145 } 146 147 /** React to a change in the state of the tabbed pane. 148 * @param event The event. 149 */ 150 @Override 151 public void stateChanged(ChangeEvent event) { 152 Object source = event.getSource(); 153 if (source instanceof JTabbedPane) { 154 Component selected = ((JTabbedPane) source).getSelectedComponent(); 155 if (selected instanceof JGraph) { 156 setJGraph((JGraph) selected); 157 selected.requestFocus(); 158 } 159 if (_graphPanner != null) { 160 _graphPanner.setCanvas((JGraph) selected); 161 _graphPanner.repaint(); 162 } 163 } 164 } 165 166 /////////////////////////////////////////////////////////////////// 167 //// protected methods //// 168 169 /** Create the menus that are used by this frame. 170 * It is essential that _createGraphPane() be called before this. 171 */ 172 @Override 173 protected void _addMenus() { 174 super._addMenus(); 175 _caseMenu = new JMenu("Case"); 176 _caseMenu.setMnemonic(KeyEvent.VK_C); 177 _menubar.add(_caseMenu); 178 GUIUtilities.addHotKey(_getRightComponent(), _addCaseAction); 179 GUIUtilities.addMenuItem(_caseMenu, _addCaseAction); 180 GUIUtilities.addHotKey(_getRightComponent(), _removeCaseAction); 181 GUIUtilities.addMenuItem(_caseMenu, _removeCaseAction); 182 } 183 184 /** Create a new graph pane. Note that this method is called in 185 * constructor of the base class, so it must be careful to not reference 186 * local variables that may not have yet been created. 187 * This overrides the base class to create a specialized 188 * graph controller (an inner class). 189 * @param entity The object to be displayed in the pane. 190 * @return The pane that is created. 191 */ 192 @Override 193 protected GraphPane _createGraphPane(NamedObj entity) { 194 _controller = new CaseGraphController(); 195 _controller.setConfiguration(getConfiguration()); 196 _controller.setFrame(this); 197 198 // The cast is safe because the constructor only accepts 199 // CompositeEntity. 200 final ActorGraphModel graphModel = new ActorGraphModel(entity); 201 return new GraphPane(_controller, graphModel); 202 } 203 204 /** Create the component that goes to the right of the library. 205 * NOTE: This is called in the base class constructor, before 206 * things have been initialized. Hence, it cannot reference 207 * local variables. 208 * @param entity The entity to display in the component. 209 * @return The component that goes to the right of the library. 210 */ 211 @Override 212 protected JComponent _createRightComponent(NamedObj entity) { 213 if (!(entity instanceof Case)) { 214 return super._createRightComponent(entity); 215 } 216 _tabbedPane = new JTabbedPane(); 217 _tabbedPane.addChangeListener(this); 218 Iterator<?> cases = ((Case) entity).entityList(Refinement.class) 219 .iterator(); 220 boolean first = true; 221 while (cases.hasNext()) { 222 Refinement refinement = (Refinement) cases.next(); 223 JGraph jgraph = _addTabbedPane(refinement, false); 224 // The first JGraph is the one with the focus. 225 if (first) { 226 first = false; 227 setJGraph(jgraph); 228 } else { 229 ((CaseGraphController) _controller)._addHotKeys(jgraph); 230 } 231 } 232 return _tabbedPane; 233 } 234 235 /////////////////////////////////////////////////////////////////// 236 //// protected variables //// 237 238 /** The case menu. */ 239 protected JMenu _caseMenu; 240 241 /////////////////////////////////////////////////////////////////// 242 //// private methods //// 243 244 /** Add a tabbed pane for the specified case. 245 * @param refinement The case. 246 * @param newPane True to add the pane prior to the last pane. 247 * @return The pane. 248 */ 249 private JGraph _addTabbedPane(Refinement refinement, boolean newPane) { 250 GraphPane pane = _createGraphPane(refinement); 251 pane.getForegroundLayer().setPickHalo(2); 252 pane.getForegroundEventLayer().setConsuming(false); 253 pane.getForegroundEventLayer().setEnabled(true); 254 pane.getForegroundEventLayer().addLayerListener(new LayerAdapter() { 255 /** Invoked when the mouse is pressed on a layer 256 * or figure. 257 */ 258 @Override 259 public void mousePressed(LayerEvent event) { 260 Component component = event.getComponent(); 261 262 if (!component.hasFocus()) { 263 component.requestFocus(); 264 } 265 } 266 }); 267 JGraph jgraph = new JGraph(pane); 268 String name = refinement.getName(); 269 jgraph.setName(name); 270 int index = _tabbedPane.getComponentCount(); 271 // Put before the default pane, unless this is the default. 272 if (newPane) { 273 index--; 274 } 275 _tabbedPane.add(jgraph, index); 276 jgraph.setBackground(BACKGROUND_COLOR); 277 // Create a drop target for the jgraph. 278 // FIXME: Should override _setDropIntoEnabled to modify all the drop targets created. 279 new EditorDropTarget(jgraph); 280 return jgraph; 281 } 282 283 /////////////////////////////////////////////////////////////////// 284 //// private variables //// 285 286 /** The action to add a case. */ 287 private AddCaseAction _addCaseAction; 288 289 /** The Case actor displayed by this frame. */ 290 private Case _case; 291 292 /** The action to remove a case. */ 293 private RemoveCaseAction _removeCaseAction; 294 295 /** The tabbed pane for cases. */ 296 private JTabbedPane _tabbedPane; 297 298 /////////////////////////////////////////////////////////////////// 299 //// public inner classes //// 300 301 /** Class implementing the Add Case menu command. */ 302 public class AddCaseAction extends FigureAction { 303 304 /** Create a case action with label "Add Case". */ 305 public AddCaseAction() { 306 super("Add Case"); 307 putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_A)); 308 } 309 310 /////////////////////////////////////////////////////////////////////////////// 311 //// public methods //// 312 313 /** Perform the action. */ 314 @Override 315 public void actionPerformed(ActionEvent e) { 316 super.actionPerformed(e); 317 // Dialog to ask for a case name. 318 Query query = new Query(); 319 query.addLine("case", "Pattern that the control input must match", 320 ""); 321 ComponentDialog dialog = new ComponentDialog(CaseGraphFrame.this, 322 "Add Case", query); 323 if (dialog.buttonPressed().equals("OK")) { 324 final String pattern = query.getStringValue("case"); 325 // NOTE: We do not use a TransitionRefinement because we don't 326 // want the sibling input ports that come with output ports. 327 String moml = "<entity name=\"" 328 + StringUtilities.escapeForXML(pattern) + "\" class=\"" 329 + _case.refinementClassName() + "\"/>"; 330 331 // The following is, regrettably, copied from ModalTransitionController. 332 MoMLChangeRequest change = new MoMLChangeRequest(this, _case, 333 moml) { 334 @Override 335 protected void _execute() throws Exception { 336 super._execute(); 337 338 // Mirror the ports of the container in the refinement. 339 // Note that this is done here rather than as part of 340 // the MoML because we have set protected variables 341 // in the refinement to prevent it from trying to again 342 // mirror the changes in the container. 343 Refinement entity = (Refinement) _case 344 .getEntity(pattern); 345 346 // Get the initial port configuration from the container. 347 Iterator<?> ports = _case.portList().iterator(); 348 349 Set<Port> portsToMirror = new HashSet<Port>(); 350 while (ports.hasNext()) { 351 Port port = (Port) ports.next(); 352 353 // see if we should mirror the port 354 if (port != _case.control.getPort()) { 355 portsToMirror.add(port); 356 } 357 } 358 359 MultiCompositeActor.mirrorContainerPortsInRefinement( 360 entity, portsToMirror); 361 362 JGraph jgraph = _addTabbedPane(entity, true); 363 ((CaseGraphController) _controller)._addHotKeys(jgraph); 364 } 365 }; 366 367 _case.requestChange(change); 368 } 369 } 370 } 371 372 /** Specialized graph controller that handles multiple graph models. */ 373 public class CaseGraphController extends ActorEditorGraphController { 374 /** Override the base class to select the graph model associated 375 * with the selected pane. 376 */ 377 @Override 378 public GraphModel getGraphModel() { 379 if (_tabbedPane != null) { 380 Component tab = _tabbedPane.getSelectedComponent(); 381 if (tab instanceof JGraph) { 382 GraphPane pane = ((JGraph) tab).getGraphPane(); 383 return pane.getGraphModel(); 384 } 385 } 386 // Fallback position. 387 return super.getGraphModel(); 388 } 389 390 /** Add hot keys to the actions in the given JGraph. 391 * 392 * @param jgraph The JGraph to which hot keys are to be added. 393 */ 394 @Override 395 protected void _addHotKeys(JGraph jgraph) { 396 super._addHotKeys(jgraph); 397 } 398 } 399 400 /** Class implementing the Remove Case menu command. */ 401 public class RemoveCaseAction extends FigureAction { 402 403 /** Create a case action with label "Add Case". */ 404 public RemoveCaseAction() { 405 super("Remove Case"); 406 putValue(MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_R)); 407 } 408 409 /////////////////////////////////////////////////////////////////////////////// 410 //// public methods //// 411 412 /** Perform the action. */ 413 @Override 414 public void actionPerformed(ActionEvent e) { 415 super.actionPerformed(e); 416 // Dialog to ask for a case name. 417 Query query = new Query(); 418 List<?> refinements = _case.entityList(Refinement.class); 419 if (refinements.size() < 2) { 420 MessageHandler.error("No cases to remove."); 421 } else { 422 String[] caseNames = new String[refinements.size() - 1]; 423 Iterator<?> cases = refinements.iterator(); 424 int i = 0; 425 while (cases.hasNext()) { 426 String name = ((Nameable) cases.next()).getName(); 427 if (!name.equals("default")) { 428 caseNames[i] = name; 429 i++; 430 } 431 } 432 query.addChoice("case", "Remove case", caseNames, caseNames[0]); 433 ComponentDialog dialog = new ComponentDialog( 434 CaseGraphFrame.this, "Remove Case", query); 435 if (dialog.buttonPressed().equals("OK")) { 436 final String name = query.getStringValue("case"); 437 String moml = "<deleteEntity name=\"" 438 + StringUtilities.escapeForXML(name) + "\"/>"; 439 440 // The following is, regrettably, copied from ModalTransitionController. 441 MoMLChangeRequest change = new MoMLChangeRequest(this, 442 _case, moml) { 443 @Override 444 protected void _execute() throws Exception { 445 super._execute(); 446 // Find the tabbed pane that matches the name and remove it. 447 int count = _tabbedPane.getTabCount(); 448 for (int i = 0; i < count; i++) { 449 if (name.equals(_tabbedPane.getTitleAt(i))) { 450 _tabbedPane.remove(i); 451 break; 452 } 453 } 454 } 455 }; 456 _case.requestChange(change); 457 } 458 } 459 } 460 } 461}