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}