001/* A panel containing controls for a Ptolemy II model.
002
003 Copyright (c) 1998-2014 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 */
027package ptolemy.actor.gui;
028
029import java.awt.Color;
030import java.awt.Container;
031import java.awt.Dimension;
032import java.awt.Window;
033import java.awt.event.ActionEvent;
034import java.awt.event.ActionListener;
035import java.awt.event.KeyAdapter;
036import java.awt.event.KeyEvent;
037import java.awt.event.MouseAdapter;
038import java.awt.event.MouseEvent;
039import java.util.Iterator;
040import java.util.List;
041
042import javax.swing.BorderFactory;
043import javax.swing.Box;
044import javax.swing.BoxLayout;
045import javax.swing.JButton;
046import javax.swing.JLabel;
047import javax.swing.JPanel;
048import javax.swing.JRootPane;
049
050import ptolemy.actor.CompositeActor;
051import ptolemy.actor.Director;
052import ptolemy.actor.Manager;
053import ptolemy.actor.injection.PortablePlaceable;
054import ptolemy.data.expr.Parameter;
055import ptolemy.gui.CloseListener;
056import ptolemy.kernel.util.IllegalActionException;
057import ptolemy.util.MessageHandler;
058
059///////////////////////////////////////////////////////////////////
060//// ModelPane
061
062/**
063
064 ModelPane is a panel for interacting with an executing Ptolemy II model.
065 It has optional controls for setting top-level and director parameters,
066 a set of buttons for controlling the execution, and a panel for displaying
067 results of the execution.  Any entity in the model that implements
068 the Placeable interface is placed in the display region.
069
070 @see Placeable
071 @author Edward A. Lee, Elaine Cheong
072 @version $Id$
073 @since Ptolemy II 0.4
074 @Pt.ProposedRating Green (eal)
075 @Pt.AcceptedRating Yellow (janneck)
076 */
077@SuppressWarnings("serial")
078public class ModelPane extends JPanel implements CloseListener {
079    /** Construct a panel for interacting with the specified Ptolemy II model.
080     *  This uses the default layout, which is horizontal, and shows
081     *  control buttons, top-level parameters, and director parameters.
082     *  @param model The model to control.
083     */
084    public ModelPane(CompositeActor model) {
085        this(model, HORIZONTAL, BUTTONS | TOP_PARAMETERS | DIRECTOR_PARAMETERS);
086    }
087
088    /** Construct a panel for interacting with the specified Ptolemy II model.
089     *  The layout argument should be one of HORIZONTAL, VERTICAL, or
090     *  CONTROLS_ONLY; it determines whether the controls are put to
091     *  the left of, or above the placeable displays. If CONTROLS_ONLY
092     *  is given, then no displays are created for placeable objects.
093     *  <p>
094     *  The show argument is a bitwise
095     *  or of any of BUTTONS, TOP_PARAMETERS, or DIRECTOR_PARAMETERS.
096     *  Or it can be 0, in which case, no controls are shown.
097     *  If BUTTONS is included, then a panel of buttons, go, pause,
098     *  resume, and stop, are shown.  If TOP_PARAMETERS is included,
099     *  then the top-level parameters of the model are included.
100     *  If DIRECTOR_PARAMETERS is included, then the parameters of
101     *  the director are included.
102     *  @param model The model to control.
103     *  @param layout HORIZONTAL or VERTICAL layout.
104     *  @param show Indicator of which controls to show.
105     */
106    public ModelPane(final CompositeActor model, int layout, int show) {
107        if (layout == HORIZONTAL) {
108            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
109        } else {
110            setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
111        }
112
113        _layout = layout;
114
115        if (show != 0) {
116            // Add run controls.
117            _controlPanel = new JPanel();
118            _controlPanel
119                    .setLayout(new BoxLayout(_controlPanel, BoxLayout.Y_AXIS));
120            _controlPanel
121                    .setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
122
123            // Add a listener that requests the focus when we click
124            // in the pane. This allows keyboard bindings to work.
125            ClickListener clickListener = new ClickListener();
126            _controlPanel.addMouseListener(clickListener);
127            _controlPanel.addKeyListener(new CommandListener());
128
129            if ((show & BUTTONS) != 0) {
130                _buttonPanel = new JPanel();
131                _buttonPanel.setLayout(
132                        new BoxLayout(_buttonPanel, BoxLayout.X_AXIS));
133                _buttonPanel.addMouseListener(clickListener);
134
135                // Padding top and bottom...
136                _buttonPanel.setBorder(
137                        BorderFactory.createEmptyBorder(10, 0, 10, 0));
138                _buttonPanel.setAlignmentX(LEFT_ALIGNMENT);
139
140                _goButton = new JButton("Go");
141                _goButton.setToolTipText("Execute the model");
142                _goButton.setAlignmentX(LEFT_ALIGNMENT);
143                _buttonPanel.add(_goButton);
144                _buttonPanel.add(Box.createRigidArea(new Dimension(10, 0)));
145                _goButton.addActionListener(new ActionListener() {
146                    @Override
147                    public void actionPerformed(ActionEvent event) {
148                        startRun();
149                    }
150                });
151
152                _pauseButton = new JButton("Pause");
153                _pauseButton.setToolTipText("Pause execution of the model");
154                _buttonPanel.add(_pauseButton);
155                _buttonPanel.add(Box.createRigidArea(new Dimension(10, 0)));
156                _pauseButton.addActionListener(new ActionListener() {
157                    @Override
158                    public void actionPerformed(ActionEvent event) {
159                        pauseRun();
160                    }
161                });
162
163                _resumeButton = new JButton("Resume");
164                _resumeButton.setToolTipText("Resume executing the model");
165                _buttonPanel.add(_resumeButton);
166                _buttonPanel.add(Box.createRigidArea(new Dimension(10, 0)));
167                _resumeButton.addActionListener(new ActionListener() {
168                    @Override
169                    public void actionPerformed(ActionEvent event) {
170                        resumeRun();
171                    }
172                });
173
174                _stopButton = new JButton("Stop");
175                _stopButton.setToolTipText("Stop executing the model");
176                _buttonPanel.add(_stopButton);
177                _stopButton.addActionListener(new ActionListener() {
178                    @Override
179                    public void actionPerformed(ActionEvent event) {
180                        stopRun();
181                    }
182                });
183                _controlPanel.add(_buttonPanel);
184                _buttonPanel.setBackground(null);
185            }
186
187            add(_controlPanel);
188            _controlPanel.setBackground(null);
189        }
190
191        _show = show;
192
193        // Do this last so that the display pane for placeable objects
194        // goes on the right or below.
195        setModel(model);
196    }
197
198    ///////////////////////////////////////////////////////////////////
199    ////                         public methods                    ////
200
201    /** Return the container for model displays.
202     *  @return A container for graphical displays.
203     *  @see #setDisplayPane(Container)
204     */
205    public Container getDisplayPane() {
206        if (_displays == null) {
207            _displays = new JPanel();
208            _displays.setBackground(null);
209            add(_displays);
210        }
211
212        return _displays;
213    }
214
215    /** Get the associated model.
216     *  @return The associated model.
217     *  @see #setModel(CompositeActor)
218     */
219    public CompositeActor getModel() {
220        return _model;
221    }
222
223    /** If the model has a manager and is executing, then
224     *  pause execution by calling the pause() method of the manager.
225     *  If there is no manager, do nothing.
226     */
227    public void pauseRun() {
228        if (_manager != null) {
229            _manager.pause();
230        }
231    }
232
233    /** If the model has a manager and is executing, then
234     *  resume execution by calling the resume() method of the manager.
235     *  If there is no manager, do nothing.
236     */
237    public void resumeRun() {
238        if (_manager != null) {
239            _manager.resume();
240        }
241    }
242
243    /** Make the Go button the default button for the root pane.
244     *  You should call this after placing this pane in a container with
245     *  a root pane.
246     */
247    public void setDefaultButton() {
248        JRootPane root = getRootPane();
249
250        if (root != null && (_show & BUTTONS) != 0) {
251            root.setDefaultButton(_goButton);
252            _goButton.setMnemonic(KeyEvent.VK_G);
253            _pauseButton.setMnemonic(KeyEvent.VK_P);
254            _resumeButton.setMnemonic(KeyEvent.VK_R);
255            _stopButton.setMnemonic(KeyEvent.VK_S);
256        }
257    }
258
259    /** Set the container for model displays.  This method sets the
260     *  background of the specified pane to match that of this panel.
261     *  @param pane The pane that is to be the container.
262     *  @deprecated It is no longer necessary to specify a display pane.
263     *   The displays are handled by setModel().
264     *  @see #getDisplayPane()
265     */
266    @Deprecated
267    public void setDisplayPane(Container pane) {
268        if (_displays != null) {
269            remove(_displays);
270        }
271
272        _displays = pane;
273        add(_displays);
274        _displays.setBackground(null);
275    }
276
277    /** Set the associated model and add a query box with its top-level
278     *  parameters, and those of its director, if it has one.
279     *  All placeable objects in the hierarchy will get placed.
280     *  @param model The associated model.
281     *  @see #getModel()
282     */
283    public void setModel(CompositeActor model) {
284        // If there was a previous model, close its displays.
285        _closeDisplays();
286        _model = model;
287
288        // For the new model, in case displays are open, close them.
289        _closeDisplays();
290
291        if (_parameterQuery != null) {
292            _controlPanel.remove(_parameterQuery);
293            _parameterQuery = null;
294        }
295
296        if (_directorQuery != null) {
297            _controlPanel.remove(_directorQuery);
298            _directorQuery = null;
299        }
300
301        if (model != null) {
302            _manager = _model.getManager();
303
304            if ((_show & TOP_PARAMETERS) != 0) {
305                List parameterList = _model.attributeList(Parameter.class);
306
307                if (parameterList.size() > 0) {
308                    JLabel pTitle = new JLabel("Model parameters:");
309
310                    // Use a dark blue for the text color.
311                    pTitle.setForeground(new Color(0, 0, 128));
312                    _controlPanel.add(pTitle);
313                    _controlPanel.add(Box.createRigidArea(new Dimension(0, 8)));
314                    _parameterQuery = new Configurer(model);
315
316                    if (_layout == HORIZONTAL) {
317                        _parameterQuery.setAlignmentX(LEFT_ALIGNMENT);
318                    } else {
319                        // If we don't align this with CENTER, then
320                        // the scrollbars in the Query get messed up when
321                        // the orientation of an applet is vertical
322                        // See SDF/Eye for an applet that displays
323                        // toplevel parameters, but not director parameters.
324                        _parameterQuery.setAlignmentX(CENTER_ALIGNMENT);
325                    }
326
327                    _parameterQuery.setBackground(null);
328                    _controlPanel.add(_parameterQuery);
329
330                    if ((_show & DIRECTOR_PARAMETERS) != 0) {
331                        _controlPanel
332                                .add(Box.createRigidArea(new Dimension(0, 15)));
333                    }
334                }
335            }
336
337            if ((_show & DIRECTOR_PARAMETERS) != 0) {
338                // Director parameters.
339                Director director = _model.getDirector();
340
341                if (director != null) {
342                    List dirParameterList = director
343                            .attributeList(Parameter.class);
344
345                    if (dirParameterList.size() > 0) {
346                        JLabel pTitle = new JLabel("Director parameters:");
347
348                        // Use a dark blue for the text color.
349                        pTitle.setForeground(new Color(0, 0, 128));
350                        _controlPanel.add(pTitle);
351                        _controlPanel
352                                .add(Box.createRigidArea(new Dimension(0, 8)));
353                        _directorQuery = new Configurer(director);
354
355                        if (_layout == HORIZONTAL) {
356                            _directorQuery.setAlignmentX(LEFT_ALIGNMENT);
357                        } else {
358                            // If we don't align this with CENTER, then
359                            // the scrollbars in the Query get messed up when
360                            // the orientation of an applet is vertical
361                            _directorQuery.setAlignmentX(CENTER_ALIGNMENT);
362                        }
363
364                        _directorQuery.setBackground(null);
365                        _controlPanel.add(_directorQuery);
366                    }
367                }
368            }
369
370            if (_controlPanel != null && _layout == HORIZONTAL) {
371                // Why they call this glue is beyond me, but what it does
372                // is make extra space to fill in the bottom.
373                _controlPanel.add(Box.createVerticalGlue());
374            }
375
376            // If there are two queries, make them the same width.
377            if (_parameterQuery != null && _directorQuery != null) {
378                Dimension modelSize = _parameterQuery.getPreferredSize();
379                Dimension directorSize = _directorQuery.getPreferredSize();
380
381                if (directorSize.width > modelSize.width) {
382                    _parameterQuery.setPreferredSize(new Dimension(
383                            directorSize.width, modelSize.height));
384                } else {
385                    _directorQuery.setPreferredSize(new Dimension(
386                            modelSize.width, directorSize.height));
387                }
388            }
389
390            if (_layout != CONTROLS_ONLY) {
391                _createPlaceable(model);
392            }
393        }
394    }
395
396    /** If the model has a manager and is not already running,
397     *  then execute the model in a new thread.  Otherwise, do nothing.
398     */
399    public void startRun() {
400        if (_manager != null) {
401            try {
402                _manager.startRun();
403            } catch (IllegalActionException ex) {
404                // Model is already running.  Ignore.
405            }
406        }
407    }
408
409    /** If the model has a manager and is executing, then
410     *  stop execution by calling the stop() method of the manager.
411     *  If there is no manager, do nothing.
412     */
413    public void stopRun() {
414        if (_manager != null) {
415            _manager.stop();
416        }
417    }
418
419    /** Notify the contained instances of PtolemyQuery that the window
420     *  has been closed, and remove all Placeable displays by calling
421     *  place(null).  This method is called if this pane is contained
422     *  within a container that supports such notification.
423     *  @param window The window that closed.
424     *  @param button The name of the button that was used to close the window.
425     */
426    @Override
427    public void windowClosed(Window window, String button) {
428        if (_directorQuery != null) {
429            _directorQuery.windowClosed(window, button);
430        }
431
432        if (_parameterQuery != null) {
433            _parameterQuery.windowClosed(window, button);
434        }
435
436        if (_model != null) {
437            _closeDisplays();
438        }
439    }
440
441    ///////////////////////////////////////////////////////////////////
442    ////                         public variables                  ////
443
444    /** Indicator to use a horizontal layout. */
445    public static final int HORIZONTAL = 0;
446
447    /** Indicator to use a vertical layout. */
448    public static final int VERTICAL = 1;
449
450    /** Indicator to create only buttons. */
451    public static final int CONTROLS_ONLY = 2;
452
453    /** Indicator to include control buttons. */
454    public static final int BUTTONS = 1;
455
456    /** Indicator to include top-level parameters in the controls. */
457    public static final int TOP_PARAMETERS = 2;
458
459    /** Indicator to include director parameters in the controls. */
460    public static final int DIRECTOR_PARAMETERS = 4;
461
462    ///////////////////////////////////////////////////////////////////
463    ////                         protected methods                 ////
464
465    /** Place the placeable objects in the model in the display pane.
466     *  This method places all placeables vertically. Derived classes
467     *  may override this method if the placeable objects are to be
468     *  placed differently.
469     *  @param model The model that contains the placeable objects.
470     */
471    protected void _createPlaceable(CompositeActor model) {
472        if (_displays != null) {
473            remove(_displays);
474            _displays = null;
475        }
476
477        // place the placeable objects in the model
478        _displays = new JPanel();
479
480        add(_displays);
481        _displays.setLayout(new BoxLayout(_displays, BoxLayout.Y_AXIS));
482        _displays.setBackground(null);
483
484        // Put placeable objects in a reasonable place.
485        Iterator atomicEntities = model.allAtomicEntityList().iterator();
486
487        while (atomicEntities.hasNext()) {
488            Object object = atomicEntities.next();
489
490            if (object instanceof Placeable) {
491                ((Placeable) object).place(_displays);
492            } else if (object instanceof PortablePlaceable) {
493                ((PortablePlaceable) object).place(new AWTContainer(_displays));
494            }
495        }
496    }
497
498    ///////////////////////////////////////////////////////////////////
499    ////                         protected variables               ////
500
501    /** A panel into which to place model displays. */
502    protected Container _displays;
503
504    ///////////////////////////////////////////////////////////////////
505    ////                         private variables                 ////
506    // The panel for the control buttons.
507    private JPanel _buttonPanel;
508
509    // The control panel on the left.
510    private JPanel _controlPanel;
511
512    // The query box for the director parameters.
513    private Configurer _directorQuery;
514
515    // The go button.
516    private JButton _goButton;
517
518    // The layout specified in the constructor.
519    private int _layout;
520
521    // The manager of the associated model.
522    private Manager _manager;
523
524    // The associated model.
525    private CompositeActor _model;
526
527    // The query box for the top-level parameters.
528    private Configurer _parameterQuery;
529
530    // The stop button.
531    private JButton _stopButton;
532
533    // The pause button.
534    private JButton _pauseButton;
535
536    // The resume button.
537    private JButton _resumeButton;
538
539    // Indicator given to the constructor of how much to show.
540    private int _show;
541
542    ///////////////////////////////////////////////////////////////////
543    ////                         private methods                   ////
544
545    /** Close any open displays by calling place(null).
546     */
547    private void _closeDisplays() {
548        if (_model != null) {
549            Iterator atomicEntities = _model.allAtomicEntityList().iterator();
550
551            while (atomicEntities.hasNext()) {
552                Object object = atomicEntities.next();
553
554                if (object instanceof Placeable) {
555                    ((Placeable) object).place(null);
556                } else if (object instanceof PortablePlaceable) {
557                    // The bug was that if we did:
558                    // 1. $PTII/bin/vergil $PTII/ptolemy/domains/sdf/demo/Spectrum/Spectrum.xml
559                    // 2. Ran the model, the two plots came up
560                    // 3. View -> Run Window.
561                    // 4. BUG: the two plots were not closed.
562                    ((PortablePlaceable) object).place(null);
563                }
564            }
565        }
566    }
567
568    ///////////////////////////////////////////////////////////////////
569    ////                         inner classes                     ////
570    private class ClickListener extends MouseAdapter {
571        @Override
572        public void mouseClicked(MouseEvent e) {
573            _controlPanel.requestFocus();
574        }
575    }
576
577    private class CommandListener extends KeyAdapter {
578        @Override
579        public void keyPressed(KeyEvent e) {
580            int keycode = e.getKeyCode();
581
582            switch (keycode) {
583            case KeyEvent.VK_CONTROL:
584                _control = true;
585                break;
586
587            case KeyEvent.VK_SHIFT:
588                _shift = true;
589                break;
590
591            case KeyEvent.VK_ENTER:
592                startRun();
593                break;
594
595            case KeyEvent.VK_G:
596
597                if (_control) {
598                    startRun();
599                }
600
601                break;
602
603            case KeyEvent.VK_H:
604
605                if (_control) {
606                    MessageHandler.message(_helpString);
607                }
608
609                break;
610
611            case KeyEvent.VK_M:
612
613                if (_control && _model != null) {
614                    System.out.println(_model.exportMoML());
615                    MessageHandler.message("Exported MoML to standard out.");
616                }
617
618                break;
619
620            case KeyEvent.VK_P:
621
622                if (_control) {
623                    pauseRun();
624                }
625
626                break;
627
628            case KeyEvent.VK_R:
629
630                if (_control) {
631                    resumeRun();
632                }
633
634                break;
635
636            case KeyEvent.VK_S:
637
638                if (_control) {
639                    stopRun();
640                }
641
642                break;
643
644            case KeyEvent.VK_SLASH:
645
646                if (_shift) {
647                    // Question mark is SHIFT-SLASH
648                    MessageHandler.message(_helpString);
649                }
650
651                break;
652
653            default:
654                // None
655            }
656        }
657
658        @Override
659        public void keyReleased(KeyEvent e) {
660            int keycode = e.getKeyCode();
661
662            switch (keycode) {
663            case KeyEvent.VK_CONTROL:
664                _control = false;
665                break;
666
667            case KeyEvent.VK_SHIFT:
668                _shift = false;
669                break;
670
671            default:
672                // None
673            }
674        }
675
676        private boolean _control = false;
677
678        private boolean _shift = false;
679
680        private String _helpString = "Key bindings in button panel:\n"
681                + "  Control-G: Start a run.\n" + "  Control-H: Display help.\n"
682                + "  Control-M: Export MoML to standard out.\n"
683                + "  Control-P: Pause a run.\n" + "  Control-R: Resume a run.\n"
684                + "  Control-S: Stop a run.\n" + "  Control-?: Display help.\n";
685    }
686}