/* The graph controller for models that can be executed. Copyright (c) 1999-2009 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.vergil.basic; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.Action; import javax.swing.JButton; import javax.swing.JMenu; import javax.swing.JToolBar; import javax.swing.KeyStroke; import ptolemy.actor.CompositeActor; import ptolemy.actor.ExecutionListener; import ptolemy.actor.Manager; import ptolemy.actor.TypeConflictException; import ptolemy.actor.gui.ColorAttribute; import ptolemy.graph.Inequality; import ptolemy.graph.InequalityTerm; import ptolemy.gui.UndeferredGraphicalMessageHandler; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.ChangeRequest; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.InternalErrorException; import ptolemy.kernel.util.KernelException; import ptolemy.kernel.util.KernelRuntimeException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.Nameable; import ptolemy.kernel.util.NamedObj; import ptolemy.kernel.util.Settable; import ptolemy.util.MessageHandler; import ptolemy.vergil.toolbox.FigureAction; import diva.graph.JGraph; import diva.gui.GUIUtilities; ////////////////////////////////////////////////////////////////////////// //// RunnableGraphController /** A graph controller for models that can be executed. This controller provides toolbar buttons for executing the model. If the model being controlled is not a top-level model, then execution commands are propagated up to the top level. @author Edward A. Lee @version $Id$ @since Ptolemy II 2.1 @Pt.ProposedRating Red (eal) @Pt.AcceptedRating Red (johnr) */ public abstract class RunnableGraphController extends WithIconGraphController implements ExecutionListener { /** Create a new controller. */ public RunnableGraphController() { super(); } /////////////////////////////////////////////////////////////////// //// public methods //// /** Add execution commands to the toolbar. * @param menu The menu to add to, which is ignored. * @param toolbar The toolbar to add to, or null if none. */ public void addToMenuAndToolbar(JMenu menu, JToolBar toolbar) { super.addToMenuAndToolbar(menu, toolbar); GUIUtilities.addToolBarButton(toolbar, _runModelAction); GUIUtilities.addToolBarButton(toolbar, _pauseModelAction); GUIUtilities.addToolBarButton(toolbar, _stopModelAction); ((ButtonFigureAction) _stopModelAction).setSelected(true); } /** Report that an execution error has occurred. This method * is called by the specified manager. * @param manager The manager calling this method. * @param throwable The throwable being reported. */ public void executionError(Manager manager, Throwable throwable) { getFrame().report(throwable); if (throwable instanceof KernelException) { _highlightError(((KernelException) throwable).getNameable1()); _highlightError(((KernelException) throwable).getNameable2()); // Type conflict errors need to be handled specially. if (throwable instanceof TypeConflictException) { Iterator<?> inequalities = ((TypeConflictException) throwable) .inequalityList().iterator(); while (inequalities.hasNext()) { Inequality inequality = (Inequality) inequalities.next(); if (inequality != null) { InequalityTerm term = inequality.getGreaterTerm(); if (term != null) { Object object = term.getAssociatedObject(); if (object instanceof Nameable) { _highlightError((Nameable) object); } } term = inequality.getLesserTerm(); if (term != null) { Object object = term.getAssociatedObject(); if (object instanceof Nameable) { _highlightError((Nameable) object); } } } } } } else if (throwable instanceof KernelRuntimeException) { Iterator<?> causes = ((KernelRuntimeException) throwable) .getNameables().iterator(); while (causes.hasNext()) { _highlightError((Nameable) causes.next()); } } } /** Report that execution of the model has finished. * @param manager The manager calling this method. */ public synchronized void executionFinished(Manager manager) { getFrame().report("execution finished."); } /** Report that a manager state has changed. * This method is called by the specified manager. * @param manager The manager calling this method. */ public void managerStateChanged(Manager manager) { Manager.State newState = manager.getState(); if (newState != _previousState) { // In case there were errors, we // clear any error reporting highlights that may be present. // Do this only if there are actually error highlights because // it triggers a repaint. // We also request the extra repaint when the new state becomes // idle (and the previous one was something else), since we want // to update visual effects that might have changed by running the // model. if (newState == Manager.IDLE || _errorHighlights.size() > 0) { ChangeRequest request = new ChangeRequest(this, "Error Highlight Clearer", true) { protected void _execute() throws Exception { for (Attribute highlight : _errorHighlights) { highlight.setContainer(null); } } }; // Mark the Error Highlight Clearer request as // non-persistant so that we don't mark the model as being // modified. ptolemy/actor/lib/jni/test/Scale/Scale.xml // required this change. request.setPersistent(false); manager.requestChange(request); } getFrame().report(manager.getState().getDescription()); _previousState = newState; if (newState == Manager.INITIALIZING || newState == Manager.ITERATING || newState == Manager.PREINITIALIZING || newState == Manager.RESOLVING_TYPES || newState == Manager.WRAPPING_UP || newState == Manager.EXITING) { ((ButtonFigureAction) _runModelAction).setSelected(true); ((ButtonFigureAction) _pauseModelAction).setSelected(false); ((ButtonFigureAction) _stopModelAction).setSelected(false); } else if (newState == Manager.PAUSED) { ((ButtonFigureAction) _runModelAction).setSelected(false); ((ButtonFigureAction) _pauseModelAction).setSelected(true); ((ButtonFigureAction) _stopModelAction).setSelected(false); } else { ((ButtonFigureAction) _runModelAction).setSelected(false); ((ButtonFigureAction) _pauseModelAction).setSelected(false); ((ButtonFigureAction) _stopModelAction).setSelected(true); } } } /////////////////////////////////////////////////////////////////// //// protected methods //// /** Add hot keys to the actions in the given JGraph. * * @param jgraph The JGraph to which hot keys are to be added. */ protected void _addHotKeys(JGraph jgraph) { super._addHotKeys(jgraph); GUIUtilities.addHotKey(jgraph, _runModelAction); GUIUtilities.addHotKey(jgraph, _pauseModelAction); GUIUtilities.addHotKey(jgraph, _stopModelAction); } /** Get the manager for the top-level of the associated model, * if there is one, or create one if there is not. * @return The manager. * @exception IllegalActionException If the associated model is * not a CompositeActor, or if the manager cannot be created. */ protected Manager _getManager() throws IllegalActionException { AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) getGraphModel(); NamedObj toplevel = graphModel.getPtolemyModel().toplevel(); if (!(toplevel instanceof CompositeActor)) { throw new IllegalActionException(toplevel, "Cannot get a manager because the model is not a CompositeActor."); } Manager manager = ((CompositeActor) toplevel).getManager(); if (manager == null) { try { manager = new Manager(toplevel.workspace(), "manager"); ((CompositeActor) toplevel).setManager(manager); } catch (IllegalActionException ex) { // Should not occur. throw new InternalErrorException(ex); } } if (manager != _manager) { // If there was a previous manager, unlisten. if (_manager != null) { _manager.removeExecutionListener(this); } manager.addExecutionListener(this); _manager = manager; } return manager; } /////////////////////////////////////////////////////////////////// //// private methods //// /** Add an error highlight color to the specified culprit if it is * not already present. * @param culprit The culprit to highlight. * @exception IllegalActionException If the highlight cannot be added. * @exception NameDuplicationException Should not be thrown. */ private void _addErrorHighlightIfNeeded(Nameable culprit) throws IllegalActionException, NameDuplicationException { Attribute highlightColor = ((NamedObj) culprit) .getAttribute("_highlightColor"); if (highlightColor == null) { highlightColor = new ColorAttribute((NamedObj) culprit, "_highlightColor"); ((ColorAttribute) highlightColor) .setExpression("{1.0, 0.0, 0.0, 1.0}"); highlightColor.setPersistent(false); ((ColorAttribute) highlightColor).setVisibility(Settable.EXPERT); _errorHighlights.add(highlightColor); } } /** Highlight the specified object and all its containers to * indicate that it is the source of an error. * @param culprit The culprit. */ private void _highlightError(final Nameable culprit) { if (culprit instanceof NamedObj) { ChangeRequest request = new ChangeRequest(this, "Error Highlighter") { protected void _execute() throws Exception { _addErrorHighlightIfNeeded(culprit); NamedObj container = culprit.getContainer(); while (container != null) { _addErrorHighlightIfNeeded(container); container = container.getContainer(); } } }; ((NamedObj) culprit).requestChange(request); } } /////////////////////////////////////////////////////////////////// //// private variables //// /** List of error highlight attributes we have created. */ private List<Attribute> _errorHighlights = new LinkedList<Attribute>(); /** The manager we are currently listening to. */ private Manager _manager = null; /** Action for pausing the model. */ private Action _pauseModelAction = new PauseModelAction("Pause the model"); /** The previous state of the manager, to avoid reporting * it if it hasn't changed. */ private Manager.State _previousState; /** Action for running the model. */ private Action _runModelAction = new RunModelAction( "Run or Resume the model"); /** Action for stopping the model. */ private Action _stopModelAction = new StopModelAction("Stop the model"); /** An action to run the model that includes a button. */ private class ButtonFigureAction extends FigureAction { public ButtonFigureAction(String description) { super(description); } public void setSelected(boolean state) { JButton button = (JButton) getValue("toolBarButton"); button.setSelected(state); } } /////////////////////////////////////////////////////////////////// //// inner classes //// /////////////////////////////////////////////////////////////////// //// RunModelAction /** An action to run the model. */ private class RunModelAction extends ButtonFigureAction { /** Run the model without opening a run-control window. * @param description The description used for menu entries and * tooltips. */ public RunModelAction(String description) { super(description); // Load the image by using the absolute path to the gif. // Using a relative location should work, but it does not. // Use the resource locator of the class. // For more information, see // jdk1.3/docs/guide/resources/resources.html GUIUtilities.addIcons(this, new String[][] { { "/ptolemy/vergil/basic/img/run.gif", GUIUtilities.LARGE_ICON }, { "/ptolemy/vergil/basic/img/run_o.gif", GUIUtilities.ROLLOVER_ICON }, { "/ptolemy/vergil/basic/img/run_ov.gif", GUIUtilities.ROLLOVER_SELECTED_ICON }, { "/ptolemy/vergil/basic/img/run_on.gif", GUIUtilities.SELECTED_ICON } }); putValue("tooltip", description + " (Ctrl+R)"); putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_R, Toolkit.getDefaultToolkit() .getMenuShortcutKeyMask())); } /** Run the model. */ public void actionPerformed(ActionEvent e) { super.actionPerformed(e); try { // Formerly, if the user opens up a composite actor and then // runs the top level and there is an error, then the composite // actor window pops up with the error message. Instead // the current window (the top level) should stay up. // The problem is that when the composite actor is opened, // Top calls GraphicalMessageHandler.setContext(). // Instead, if the user runs the model, we should set the // context to that window. UndeferredGraphicalMessageHandler.setContext(getFrame()); _getManager().startRun(); } catch (IllegalActionException ex) { // Model may be already running. Attempt to resume. try { _getManager().resume(); } catch (IllegalActionException ex1) { MessageHandler.error("Failed to run/resume.", ex); } } } } /////////////////////////////////////////////////////////////////// //// PauseModelAction /** An action to pause the model. */ private class PauseModelAction extends ButtonFigureAction { /** Pause the model if it is running. * @param description The description used for menu entries and * tooltips. */ public PauseModelAction(String description) { super(description); // Load the image by using the absolute path to the gif. // Using a relative location should work, but it does not. // Use the resource locator of the class. // For more information, see // jdk1.3/docs/guide/resources/resources.html GUIUtilities.addIcons(this, new String[][] { { "/ptolemy/vergil/basic/img/pause.gif", GUIUtilities.LARGE_ICON }, { "/ptolemy/vergil/basic/img/pause_o.gif", GUIUtilities.ROLLOVER_ICON }, { "/ptolemy/vergil/basic/img/pause_ov.gif", GUIUtilities.ROLLOVER_SELECTED_ICON }, { "/ptolemy/vergil/basic/img/pause_on.gif", GUIUtilities.SELECTED_ICON } }); putValue("tooltip", description + " (Ctrl+U)"); putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_U, Toolkit.getDefaultToolkit() .getMenuShortcutKeyMask())); } /** Pause the model. */ public void actionPerformed(ActionEvent e) { super.actionPerformed(e); try { _getManager().pause(); } catch (IllegalActionException ex) { MessageHandler.error("failed to pause.", ex); } } } /////////////////////////////////////////////////////////////////// //// StopModelAction /** An action to stop the model. */ private class StopModelAction extends ButtonFigureAction { /** Stop the model, if it is running. * @param description The description used for menu entries and * tooltips. */ public StopModelAction(String description) { super(description); // Load the image by using the absolute path to the gif. // Using a relative location should work, but it does not. // Use the resource locator of the class. // For more information, see // jdk1.3/docs/guide/resources/resources.html GUIUtilities.addIcons(this, new String[][] { { "/ptolemy/vergil/basic/img/stop.gif", GUIUtilities.LARGE_ICON }, { "/ptolemy/vergil/basic/img/stop_o.gif", GUIUtilities.ROLLOVER_ICON }, { "/ptolemy/vergil/basic/img/stop_ov.gif", GUIUtilities.ROLLOVER_SELECTED_ICON }, { "/ptolemy/vergil/basic/img/stop_on.gif", GUIUtilities.SELECTED_ICON } }); putValue("tooltip", description + " (Ctrl+H)"); putValue(GUIUtilities.ACCELERATOR_KEY, KeyStroke.getKeyStroke( KeyEvent.VK_H, Toolkit.getDefaultToolkit() .getMenuShortcutKeyMask())); } /** Stop the model. */ public void actionPerformed(ActionEvent e) { super.actionPerformed(e); try { _getManager().stop(); } catch (IllegalActionException ex) { MessageHandler.error("failed to stop.", ex); } } } }