001/* The graph controller for models that can be executed. 002 003 Copyright (c) 1999-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.basic; 029 030import java.awt.Frame; 031import java.awt.Toolkit; 032import java.awt.event.ActionEvent; 033import java.awt.event.KeyEvent; 034import java.util.Iterator; 035 036import javax.swing.Action; 037import javax.swing.JButton; 038import javax.swing.JMenu; 039import javax.swing.JToolBar; 040import javax.swing.KeyStroke; 041 042import diva.graph.JGraph; 043import diva.gui.GUIUtilities; 044import ptolemy.actor.CompositeActor; 045import ptolemy.actor.ExecutionListener; 046import ptolemy.actor.Manager; 047import ptolemy.actor.TypeConflictException; 048import ptolemy.graph.Inequality; 049import ptolemy.graph.InequalityTerm; 050import ptolemy.gui.UndeferredGraphicalMessageHandler; 051import ptolemy.kernel.util.ChangeRequest; 052import ptolemy.kernel.util.IllegalActionException; 053import ptolemy.kernel.util.InternalErrorException; 054import ptolemy.kernel.util.KernelException; 055import ptolemy.kernel.util.KernelRuntimeException; 056import ptolemy.kernel.util.Nameable; 057import ptolemy.kernel.util.NamedObj; 058import ptolemy.util.MessageHandler; 059import ptolemy.vergil.toolbox.FigureAction; 060 061/////////////////////////////////////////////////////////////////// 062//// RunnableGraphController 063 064/** 065 A graph controller for models that can be executed. 066 This controller provides toolbar buttons for executing 067 the model. If the model being controlled is not a top-level 068 model, then execution commands are propagated up to the top level. 069 070 @author Edward A. Lee 071 @version $Id$ 072 @since Ptolemy II 2.1 073 @Pt.ProposedRating Red (eal) 074 @Pt.AcceptedRating Red (johnr) 075 */ 076public abstract class RunnableGraphController extends WithIconGraphController 077 implements ExecutionListener { 078 /** Create a new controller. 079 */ 080 public RunnableGraphController() { 081 super(); 082 } 083 084 /////////////////////////////////////////////////////////////////// 085 //// public methods //// 086 087 /** Add execution commands to the toolbar. 088 * @param menu The menu to add to, which is ignored. 089 * @param toolbar The toolbar to add to, or null if none. 090 */ 091 @Override 092 public void addToMenuAndToolbar(JMenu menu, JToolBar toolbar) { 093 super.addToMenuAndToolbar(menu, toolbar); 094 GUIUtilities.addToolBarButton(toolbar, _runModelAction); 095 GUIUtilities.addToolBarButton(toolbar, _pauseModelAction); 096 GUIUtilities.addToolBarButton(toolbar, _stopModelAction); 097 ((ButtonFigureAction) _stopModelAction).setSelected(true); 098 } 099 100 /** Report that an execution error has occurred. This method 101 * is called by the specified manager. 102 * @param manager The manager calling this method. 103 * @param throwable The throwable being reported. 104 */ 105 @Override 106 public void executionError(Manager manager, Throwable throwable) { 107 _report(throwable); 108 109 if (throwable instanceof KernelException) { 110 highlightError(((KernelException) throwable).getNameable1()); 111 highlightError(((KernelException) throwable).getNameable2()); 112 113 // Type conflict errors need to be handled specially. 114 if (throwable instanceof TypeConflictException) { 115 Iterator<?> inequalities = ((TypeConflictException) throwable) 116 .inequalityList().iterator(); 117 while (inequalities.hasNext()) { 118 Object item = inequalities.next(); 119 if (item instanceof InequalityTerm) { 120 Object object = ((InequalityTerm) item) 121 .getAssociatedObject(); 122 if (object instanceof Nameable) { 123 highlightError((Nameable) object); 124 } 125 } else if (item instanceof Inequality) { 126 Inequality inequality = (Inequality) item; 127 InequalityTerm term = inequality.getGreaterTerm(); 128 if (term != null) { 129 Object object = term.getAssociatedObject(); 130 if (object instanceof Nameable) { 131 highlightError((Nameable) object); 132 } 133 } 134 term = inequality.getLesserTerm(); 135 if (term != null) { 136 Object object = term.getAssociatedObject(); 137 if (object instanceof Nameable) { 138 highlightError((Nameable) object); 139 } 140 } 141 } 142 } 143 } 144 } else if (throwable instanceof KernelRuntimeException) { 145 Iterator<?> causes = ((KernelRuntimeException) throwable) 146 .getNameables().iterator(); 147 while (causes.hasNext()) { 148 highlightError((Nameable) causes.next()); 149 } 150 } 151 } 152 153 /** Report that execution of the model has finished. 154 * @param manager The manager calling this method. 155 */ 156 @Override 157 public synchronized void executionFinished(Manager manager) { 158 // Display the amount of time and memory used. 159 // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5571 160 // There is similar code in ptolemy/actor/gui/ModelFrame.java 161 String statusMessage = manager.getStatusMessage(); 162 if (!statusMessage.isEmpty()) { 163 statusMessage = ": " + statusMessage; 164 } else { 165 statusMessage = "."; 166 } 167 _report("execution finished" + statusMessage); 168 } 169 170 /** Report that a manager state has changed. 171 * This method is called by the specified manager. 172 * @param manager The manager calling this method. 173 */ 174 @Override 175 public void managerStateChanged(Manager manager) { 176 Manager.State newState = manager.getState(); 177 178 if (newState != _previousState) { 179 // In case there were errors, we 180 // clear any error reporting highlights that may be present. 181 // Do this only if there are actually error highlights because 182 // it triggers a repaint. 183 // We also request the extra repaint when the new state becomes 184 // idle (and the previous one was something else), since we want 185 // to update visual effects that might have changed by running the 186 // model. 187 if (newState == Manager.IDLE || _areThereActiveErrorHighlights()) { 188 ChangeRequest request = _getClearAllErrorHighlightsChangeRequest(); 189 manager.requestChange(request); 190 } 191 192 // There is similar code in ptolemy/actor/gui/ModelFrame.java 193 String statusMessage = manager.getStatusMessage(); 194 if (statusMessage.equals(_previousStatusMessage)) { 195 _previousStatusMessage = statusMessage; 196 statusMessage = ""; 197 } else { 198 _previousStatusMessage = statusMessage; 199 } 200 201 if (!statusMessage.isEmpty()) { 202 statusMessage = ": " + statusMessage; 203 } else { 204 statusMessage = "."; 205 } 206 _report(manager.getState().getDescription() + statusMessage); 207 _previousState = newState; 208 209 if (newState == Manager.INITIALIZING 210 || newState == Manager.ITERATING 211 || newState == Manager.PREINITIALIZING 212 || newState == Manager.RESOLVING_TYPES 213 || newState == Manager.WRAPPING_UP 214 || newState == Manager.EXITING) { 215 ((ButtonFigureAction) _runModelAction).setSelected(true); 216 ((ButtonFigureAction) _pauseModelAction).setSelected(false); 217 ((ButtonFigureAction) _stopModelAction).setSelected(false); 218 } else if (newState == Manager.PAUSED) { 219 ((ButtonFigureAction) _runModelAction).setSelected(false); 220 ((ButtonFigureAction) _pauseModelAction).setSelected(true); 221 ((ButtonFigureAction) _stopModelAction).setSelected(false); 222 } else { 223 ((ButtonFigureAction) _runModelAction).setSelected(false); 224 ((ButtonFigureAction) _pauseModelAction).setSelected(false); 225 ((ButtonFigureAction) _stopModelAction).setSelected(true); 226 } 227 } 228 } 229 230 /////////////////////////////////////////////////////////////////// 231 //// protected methods //// 232 233 /** Add hot keys to the actions in the given JGraph. 234 * 235 * @param jgraph The JGraph to which hot keys are to be added. 236 */ 237 @Override 238 protected void _addHotKeys(JGraph jgraph) { 239 super._addHotKeys(jgraph); 240 GUIUtilities.addHotKey(jgraph, _runModelAction); 241 GUIUtilities.addHotKey(jgraph, _pauseModelAction); 242 GUIUtilities.addHotKey(jgraph, _stopModelAction); 243 } 244 245 /** Get the manager for the top-level of the associated model, 246 * if there is one, or create one if there is not. 247 * @return The manager. 248 * @exception IllegalActionException If the associated model is 249 * not a CompositeActor, or if the manager cannot be created. 250 */ 251 protected Manager _getManager() throws IllegalActionException { 252 AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) getGraphModel(); 253 NamedObj toplevel = graphModel.getPtolemyModel().toplevel(); 254 255 if (!(toplevel instanceof CompositeActor)) { 256 throw new IllegalActionException(toplevel, 257 "Cannot get a manager because the model is not a CompositeActor."); 258 } 259 260 Manager manager = ((CompositeActor) toplevel).getManager(); 261 262 if (manager == null) { 263 try { 264 manager = new Manager(toplevel.workspace(), "manager"); 265 ((CompositeActor) toplevel).setManager(manager); 266 } catch (IllegalActionException ex) { 267 // Should not occur. 268 throw new InternalErrorException(ex); 269 } 270 } 271 272 if (manager != _manager) { 273 // If there was a previous manager, unlisten. 274 if (_manager != null) { 275 _manager.removeExecutionListener(this); 276 } 277 278 manager.addExecutionListener(this); 279 _manager = manager; 280 } 281 282 return manager; 283 } 284 285 /** If there is an associated BasicGraphFrame, use it to report a message. 286 * Otherwise, report to standard output. 287 * @param message The message. 288 */ 289 protected void _report(String message) { 290 BasicGraphFrame frame = getFrame(); 291 if (frame != null) { 292 frame.report(message); 293 } else { 294 System.out.println(message); 295 } 296 } 297 298 /** If there is an associated BasicGraphFrame, use it to report an error. 299 * Otherwise, report to standard error. 300 * @param error The throwable. 301 */ 302 protected void _report(Throwable error) { 303 BasicGraphFrame frame = getFrame(); 304 if (frame != null) { 305 frame.report(error); 306 } else { 307 System.err.println(error); 308 } 309 } 310 311 /////////////////////////////////////////////////////////////////// 312 //// private variables //// 313 314 /** The manager we are currently listening to. */ 315 private Manager _manager = null; 316 317 /** Action for pausing the model. */ 318 private Action _pauseModelAction = new PauseModelAction("Pause the model"); 319 320 /** The previous state of the manager, to avoid reporting 321 * it if it hasn't changed. */ 322 private Manager.State _previousState; 323 324 /** The Manager status message from the previous state. 325 */ 326 private String _previousStatusMessage = ""; 327 328 /** Action for running the model. */ 329 private Action _runModelAction = new RunModelAction( 330 "Run or Resume the model"); 331 332 /** Action for stopping the model. */ 333 private Action _stopModelAction = new StopModelAction("Stop the model"); 334 335 /** An action to run the model that includes a button. */ 336 @SuppressWarnings("serial") 337 private class ButtonFigureAction extends FigureAction { 338 public ButtonFigureAction(String description) { 339 super(description); 340 } 341 342 public void setSelected(boolean state) { 343 JButton button = (JButton) getValue("toolBarButton"); 344 button.setSelected(state); 345 } 346 } 347 348 /////////////////////////////////////////////////////////////////// 349 //// inner classes //// 350 /////////////////////////////////////////////////////////////////// 351 //// RunModelAction 352 353 /** An action to run the model. */ 354 @SuppressWarnings("serial") 355 private class RunModelAction extends ButtonFigureAction { 356 /** Run the model without opening a run-control window. 357 * @param description The description used for menu entries and 358 * tooltips. 359 */ 360 public RunModelAction(String description) { 361 super(description); 362 363 // Load the image by using the absolute path to the gif. 364 // Using a relative location should work, but it does not. 365 // Use the resource locator of the class. 366 // For more information, see 367 // jdk1.3/docs/guide/resources/resources.html 368 GUIUtilities.addIcons(this, 369 new String[][] { 370 { "/ptolemy/vergil/basic/img/run.gif", 371 GUIUtilities.LARGE_ICON }, 372 { "/ptolemy/vergil/basic/img/run_o.gif", 373 GUIUtilities.ROLLOVER_ICON }, 374 { "/ptolemy/vergil/basic/img/run_ov.gif", 375 GUIUtilities.ROLLOVER_SELECTED_ICON }, 376 { "/ptolemy/vergil/basic/img/run_on.gif", 377 GUIUtilities.SELECTED_ICON } }); 378 379 putValue("tooltip", description + " (Ctrl+R)"); 380 putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( 381 KeyEvent.VK_R, 382 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 383 } 384 385 /** Run the model. */ 386 @Override 387 public void actionPerformed(ActionEvent e) { 388 super.actionPerformed(e); 389 390 try { 391 // Formerly, if the user opens up a composite actor and then 392 // runs the top level and there is an error, then the composite 393 // actor window pops up with the error message. Instead 394 // the current window (the top level) should stay up. 395 // The problem is that when the composite actor is opened, 396 // Top calls GraphicalMessageHandler.setContext(). 397 // Instead, if the user runs the model, we should set the 398 // context to that window. 399 Frame frame = getFrame(); 400 if (frame != null) { 401 UndeferredGraphicalMessageHandler.setContext(getFrame()); 402 } 403 _getManager().startRun(); 404 } catch (IllegalActionException ex) { 405 // Model may be already running. Attempt to resume. 406 try { 407 _getManager().resume(); 408 } catch (IllegalActionException ex1) { 409 MessageHandler.error("Failed to run/resume.", ex); 410 } 411 } 412 } 413 } 414 415 /////////////////////////////////////////////////////////////////// 416 //// PauseModelAction 417 418 /** An action to pause the model. */ 419 @SuppressWarnings("serial") 420 private class PauseModelAction extends ButtonFigureAction { 421 /** Pause the model if it is running. 422 * @param description The description used for menu entries and 423 * tooltips. 424 */ 425 public PauseModelAction(String description) { 426 super(description); 427 428 // Load the image by using the absolute path to the gif. 429 // Using a relative location should work, but it does not. 430 // Use the resource locator of the class. 431 // For more information, see 432 // jdk1.3/docs/guide/resources/resources.html 433 GUIUtilities.addIcons(this, 434 new String[][] { 435 { "/ptolemy/vergil/basic/img/pause.gif", 436 GUIUtilities.LARGE_ICON }, 437 { "/ptolemy/vergil/basic/img/pause_o.gif", 438 GUIUtilities.ROLLOVER_ICON }, 439 { "/ptolemy/vergil/basic/img/pause_ov.gif", 440 GUIUtilities.ROLLOVER_SELECTED_ICON }, 441 { "/ptolemy/vergil/basic/img/pause_on.gif", 442 GUIUtilities.SELECTED_ICON } }); 443 444 putValue("tooltip", description + " (Ctrl+U)"); 445 putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( 446 KeyEvent.VK_U, 447 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 448 } 449 450 /** Pause the model. */ 451 @Override 452 public void actionPerformed(ActionEvent e) { 453 super.actionPerformed(e); 454 455 try { 456 _getManager().pause(); 457 } catch (IllegalActionException ex) { 458 MessageHandler.error("failed to pause.", ex); 459 } 460 } 461 } 462 463 /////////////////////////////////////////////////////////////////// 464 //// StopModelAction 465 466 /** An action to stop the model. */ 467 @SuppressWarnings("serial") 468 private class StopModelAction extends ButtonFigureAction { 469 /** Stop the model, if it is running. 470 * @param description The description used for menu entries and 471 * tooltips. 472 */ 473 public StopModelAction(String description) { 474 super(description); 475 476 // Load the image by using the absolute path to the gif. 477 // Using a relative location should work, but it does not. 478 // Use the resource locator of the class. 479 // For more information, see 480 // jdk1.3/docs/guide/resources/resources.html 481 GUIUtilities.addIcons(this, 482 new String[][] { 483 { "/ptolemy/vergil/basic/img/stop.gif", 484 GUIUtilities.LARGE_ICON }, 485 { "/ptolemy/vergil/basic/img/stop_o.gif", 486 GUIUtilities.ROLLOVER_ICON }, 487 { "/ptolemy/vergil/basic/img/stop_ov.gif", 488 GUIUtilities.ROLLOVER_SELECTED_ICON }, 489 { "/ptolemy/vergil/basic/img/stop_on.gif", 490 GUIUtilities.SELECTED_ICON } }); 491 492 putValue("tooltip", description + " (Ctrl+H)"); 493 putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( 494 KeyEvent.VK_H, 495 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 496 } 497 498 /** Stop the model. */ 499 @Override 500 public void actionPerformed(ActionEvent e) { 501 super.actionPerformed(e); 502 503 try { 504 _getManager().stop(); 505 } catch (IllegalActionException ex) { 506 MessageHandler.error("failed to stop.", ex); 507 } 508 } 509 } 510}