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}