001/* Query dialog. 002 003 Copyright (c) 1998-2018 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.gui; 029 030/////////////////////////////////////////////////////////////////// 031// IMPORTANT!!!!! 032// Avoid importing any packages from ptolemy.* here so that we 033// can ship Ptplot. 034/////////////////////////////////////////////////////////////////// 035 036import java.awt.BorderLayout; 037import java.awt.Color; 038import java.awt.Component; 039import java.awt.Dimension; 040import java.awt.FileDialog; 041import java.awt.FlowLayout; 042import java.awt.Font; 043import java.awt.GridBagConstraints; 044import java.awt.GridBagLayout; 045import java.awt.Insets; 046import java.awt.KeyboardFocusManager; 047import java.awt.Toolkit; 048import java.awt.event.ActionEvent; 049import java.awt.event.ActionListener; 050import java.awt.event.FocusEvent; 051import java.awt.event.FocusListener; 052import java.awt.event.ItemEvent; 053import java.awt.event.ItemListener; 054import java.io.File; 055import java.io.FilenameFilter; 056import java.io.IOException; 057import java.net.URI; 058import java.util.Enumeration; 059import java.util.HashMap; 060import java.util.HashSet; 061import java.util.Iterator; 062import java.util.Map; 063import java.util.NoSuchElementException; 064import java.util.Set; 065import java.util.StringTokenizer; 066import java.util.Vector; 067 068import javax.swing.AbstractAction; 069import javax.swing.BorderFactory; 070import javax.swing.Box; 071import javax.swing.BoxLayout; 072import javax.swing.ButtonGroup; 073import javax.swing.JButton; 074import javax.swing.JCheckBox; 075import javax.swing.JColorChooser; 076import javax.swing.JComboBox; 077import javax.swing.JComponent; 078import javax.swing.JFileChooser; 079import javax.swing.JLabel; 080import javax.swing.JOptionPane; 081import javax.swing.JPanel; 082import javax.swing.JPasswordField; 083import javax.swing.JRadioButton; 084import javax.swing.JScrollBar; 085import javax.swing.JScrollPane; 086import javax.swing.JSeparator; 087import javax.swing.JSlider; 088import javax.swing.JTextArea; 089import javax.swing.JTextField; 090import javax.swing.JToggleButton; 091import javax.swing.KeyStroke; 092import javax.swing.ScrollPaneConstants; 093import javax.swing.SwingConstants; 094import javax.swing.event.ChangeEvent; 095import javax.swing.event.ChangeListener; 096import javax.swing.plaf.basic.BasicComboBoxEditor; 097 098//import ptolemy.actor.gui.EditParametersDialog; 099 100/////////////////////////////////////////////////////////////////// 101// IMPORTANT!!!!! 102// Avoid importing any packages from ptolemy.* here so that we 103// can ship Ptplot. 104/////////////////////////////////////////////////////////////////// 105 106/////////////////////////////////////////////////////////////////// 107//// Query 108 109/** 110 Create a query with various types of entry boxes and controls. Each type 111 of entry box has a colon and space appended to the end of its label, to 112 ensure uniformity. 113 Here is one example of creating a query with a radio button: 114 <pre> 115 query = new Query(); 116 getContentPane().add(query); 117 String[] options = {"water", "soda", "juice", "none"}; 118 query.addRadioButtons("radio", "Radio buttons", options, "water"); 119 </pre> 120 121 @author Edward A. Lee, Manda Sutijono, Elaine Cheong, Contributor: Peter Reutemann, Christoph Daniel Schulze 122 @version $Id$ 123 @since Ptolemy II 0.3 124 @Pt.ProposedRating Yellow (eal) 125 @Pt.AcceptedRating Red (eal) 126 */ 127@SuppressWarnings("serial") 128public class Query extends JPanel { 129 /** Construct a panel with no entries in it. 130 */ 131 public Query() { 132 _grid = new GridBagLayout(); 133 _constraints = new GridBagConstraints(); 134 _constraints.fill = GridBagConstraints.HORIZONTAL; 135 136 // If the next line is commented out, then the PtolemyApplet 137 // model parameters will have an entry that is less than one 138 // character wide unless the window is made to be fairly large. 139 _constraints.weightx = 1.0; 140 _constraints.anchor = GridBagConstraints.NORTHWEST; 141 _entryPanel.setLayout(_grid); 142 143 // It's not clear whether the following has any real significance... 144 // _entryPanel.setOpaque(true); 145 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 146 147 _entryPanel.setAlignmentX(Component.LEFT_ALIGNMENT); 148 149 // Add a message panel into which a message can be placed using 150 // setMessage(). 151 _messageArea = new JTextArea(""); 152 _messageArea.setFont(new Font("SansSerif", Font.PLAIN, 12)); 153 _messageArea.setEditable(false); 154 _messageArea.setLineWrap(true); 155 _messageArea.setWrapStyleWord(true); 156 157 // It seems like setLineWrap is somewhat broken. Really, 158 // setLineWrap works best with scrollbars. We have 159 // a couple of choices: use scrollbars or hack in something 160 // that guesses the number of lines. Note that to 161 // use scrollbars, the tutorial at 162 // http://java.sun.com/docs/books/tutorial/uiswing/components/simpletext.html#textarea 163 // suggests: "If you put a text area in a scroll pane, be 164 // sure to set the scroll pane's preferred size or use a 165 // text area constructor that sets the number of rows and 166 // columns for the text area." 167 _messageArea.setBackground(null); 168 169 _messageArea.setAlignmentX(Component.LEFT_ALIGNMENT); 170 171 _messageScrollPane = new JScrollPane(_messageArea); 172 _messageScrollPane.setVerticalScrollBarPolicy( 173 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); 174 175 // Get rid of the border. 176 _messageScrollPane.setBorder(BorderFactory.createEmptyBorder()); 177 _messageScrollPane.getViewport().setBackground(null); 178 179 // Useful for debugging: 180 //_messageScrollPane.setBorder( 181 // BorderFactory.createLineBorder(Color.pink)); 182 // We add the _messageScrollPane when we first use it. 183 _entryScrollPane = new JScrollPane(_entryPanel); 184 185 // Get rid of the border. 186 _entryScrollPane.setBorder(BorderFactory.createEmptyBorder()); 187 _entryScrollPane.getViewport().setBackground(null); 188 _entryScrollPane.setBackground(null); 189 add(_entryScrollPane); 190 191 // Setting the background to null allegedly means it inherits the 192 // background color from the container. 193 _entryPanel.setBackground(null); 194 } 195 196 /////////////////////////////////////////////////////////////////// 197 //// public methods //// 198 199 /** Create an on-off check box. 200 * @param name The name used to identify the entry (when calling get). 201 * @param label The label to attach to the entry. 202 * @param defaultValue The default value (true for on). 203 * @return The check box. 204 */ 205 public JCheckBox addCheckBox(String name, String label, 206 boolean defaultValue) { 207 JLabel lbl = _constructLabel(label); 208 209 JCheckBox checkbox = new JCheckBox(); 210 checkbox.setBackground(_background); 211 checkbox.setOpaque(false); 212 checkbox.setSelected(defaultValue); 213 _addPair(name, lbl, checkbox, checkbox); 214 215 // Add the listener last so that there is no notification 216 // of the first value. 217 checkbox.addItemListener(new QueryItemListener(this, name)); 218 219 return checkbox; 220 } 221 222 /** Create an uneditable choice menu. 223 * @param name The name used to identify the entry (when calling get). 224 * @param label The label to attach to the entry. 225 * @param values The list of possible choices. 226 * @param defaultChoice Default choice. 227 * @return The combo box for the choice. 228 */ 229 public JComboBox addChoice(String name, String label, Object[] values, 230 Object defaultChoice) { 231 return addChoice(name, label, values, defaultChoice, false); 232 } 233 234 /** Create a choice menu. 235 * @param name The name used to identify the entry (when calling get). 236 * @param label The label to attach to the entry. 237 * @param values The list of possible choices. 238 * @param defaultChoice Default choice. 239 * @param editable True if an arbitrary choice can be entered, in addition 240 * to the choices in values. 241 * @return The combo box for the choice. 242 */ 243 public JComboBox addChoice(String name, String label, Object[] values, 244 Object defaultChoice, boolean editable) { 245 return addChoice(name, label, values, defaultChoice, editable, 246 Color.white, Color.black); 247 } 248 249 /** Create a choice menu. 250 * @param name The name used to identify the entry (when calling get). 251 * @param label The label to attach to the entry. 252 * @param values The list of possible choices. 253 * @param defaultChoice Default choice. 254 * @param editable True if an arbitrary choice can be entered, in addition 255 * to the choices in values. 256 * @param background The background color for the editable part. 257 * @param foreground The foreground color for the editable part. 258 * @return The combo box for the choice. 259 */ 260 public JComboBox addChoice(String name, String label, Object[] values, 261 Object defaultChoice, boolean editable, final Color background, 262 final Color foreground) { 263 JLabel lbl = _constructLabel(label); 264 lbl.setBackground(_background); 265 266 JComboBox combobox = new JComboBox(values); 267 combobox.setEditable(editable); 268 269 // NOTE: Typical of Swing, the following does not set 270 // the background color. So we have to specify a 271 // custom editor. #$(#&$#(@#!! 272 // combobox.setBackground(background); 273 combobox.setEditor(new BasicComboBoxEditor() { 274 @Override 275 public Component getEditorComponent() { 276 Component result = super.getEditorComponent(); 277 result.setBackground(background); 278 result.setForeground(foreground); 279 return result; 280 } 281 }); 282 combobox.setSelectedItem(defaultChoice); 283 _addPair(name, lbl, combobox, combobox); 284 285 // Add the listener last so that there is no notification 286 // of the first value. 287 combobox.addItemListener(new QueryItemListener(this, name)); 288 289 return combobox; 290 } 291 292 /** Create a ColorChooser. 293 * @param name The name used to identify the entry (when calling get). 294 * @param label The label to attach to the entry. 295 * @param defaultColor The default color to use. 296 * @return The color chooser. 297 */ 298 public QueryColorChooser addColorChooser(String name, String label, 299 String defaultColor) { 300 JLabel lbl = _constructLabel(label); 301 lbl.setBackground(_background); 302 303 QueryColorChooser colorChooser = new QueryColorChooser(this, name, 304 defaultColor); 305 _addPair(name, lbl, colorChooser, colorChooser); 306 return colorChooser; 307 } 308 309 /** Create a simple one-line text display, a non-editable value that 310 * is set externally using the setDisplay() method. 311 * @param name The name used to identify the entry (when calling get). 312 * @param label The label to attach to the entry. 313 * @param theValue Default string to display. 314 * @return The text area that displays the value. 315 */ 316 public JTextArea addDisplay(String name, String label, String theValue) { 317 return addDisplay(name, label, theValue, null, null); 318 } 319 320 /** Create a simple one-line text display, a non-editable value that 321 * is set externally using the setDisplay() method. 322 * @param name The name used to identify the entry (when calling get). 323 * @param label The label to attach to the entry. 324 * @param theValue Default string to display. 325 * @param background The background color, or null to use defaults. 326 * @param foreground The foreground color, or null to use defaults. 327 * @return The text area that displays the value. 328 */ 329 public JTextArea addDisplay(String name, String label, String theValue, 330 Color background, Color foreground) { 331 JLabel lbl = _constructLabel(label); 332 lbl.setBackground(_background); 333 334 // NOTE: JLabel would be a reasonable choice here, but at 335 // least in the current version of swing, JLabel.setText() does 336 // not work. 337 JTextArea displayField = new JTextArea(theValue, 1, 10); 338 displayField.setEditable(false); 339 if (background == null) { 340 background = _background; 341 } 342 displayField.setBackground(background); 343 _addPair(name, lbl, displayField, displayField); 344 return displayField; 345 } 346 347 /** Create a FileChooser that selects files only, not directories, and 348 * has the default colors (white in the background, black in the 349 * foreground). 350 * @param name The name used to identify the entry (when calling get). 351 * @param label The label to attach to the entry. 352 * @param defaultName The default file name to use. 353 * @param base The URI with respect to which to give 354 * relative file names, or null to give absolute file name. 355 * @param startingDirectory The directory to open the file chooser in. 356 */ 357 public void addFileChooser(String name, String label, String defaultName, 358 URI base, File startingDirectory) { 359 addFileChooser(name, label, defaultName, base, startingDirectory, true, 360 false, false, Color.white, Color.black); 361 } 362 363 /** Create a FileChooser that selects files only, not directories, and 364 * has the default colors (white in the background, black in the 365 * foreground). 366 * @param name The name used to identify the entry (when calling get). 367 * @param label The label to attach to the entry. 368 * @param defaultName The default file name to use. 369 * @param base The URI with respect to which to give 370 * relative file names, or null to give absolute file name. 371 * @param startingDirectory The directory to open the file chooser in. 372 * @param save Whether the file is to be saved or opened. 373 */ 374 public void addFileChooser(String name, String label, String defaultName, 375 URI base, File startingDirectory, boolean save) { 376 addFileChooser(name, label, defaultName, base, startingDirectory, true, 377 false, save, Color.white, Color.black); 378 } 379 380 /** Create a FileChooser with default colors (white in the foreground, 381 * black in the background). 382 * @param name The name used to identify the entry (when calling get). 383 * @param label The label to attach to the entry. 384 * @param defaultName The default file name to use. 385 * @param base The URI with respect to which to give 386 * relative file names, or null to give absolute file name. 387 * @param startingDirectory The directory to open the file chooser in. 388 * @param allowFiles True if regular files may be chosen. 389 * @param allowDirectories True if directories may be chosen. 390 */ 391 public void addFileChooser(String name, String label, String defaultName, 392 URI base, File startingDirectory, boolean allowFiles, 393 boolean allowDirectories) { 394 addFileChooser(name, label, defaultName, base, startingDirectory, 395 allowFiles, allowDirectories, false, Color.white, Color.black); 396 } 397 398 /** Create a FileChooser that selects files only, not directories. 399 * @param name The name used to identify the entry (when calling get). 400 * @param label The label to attach to the entry. 401 * @param defaultName The default file name to use. 402 * @param base The URI with respect to which to give 403 * relative file names, or null to give absolute file name. 404 * @param startingDirectory The directory to open the file chooser in. 405 * @param background The background color for the text entry box. 406 * @param foreground The foreground color for the text entry box. 407 */ 408 public void addFileChooser(String name, String label, String defaultName, 409 URI base, File startingDirectory, Color background, 410 Color foreground) { 411 addFileChooser(name, label, defaultName, base, startingDirectory, true, 412 false, false, background, foreground); 413 } 414 415 /** Create a FileChooser. 416 * @param name The name used to identify the entry (when calling get). 417 * @param label The label to attach to the entry. 418 * @param defaultName The default file name to use. 419 * @param base The URI with respect to which to give 420 * relative file names, or null to give absolute file name. 421 * @param startingDirectory The directory to open the file chooser in. 422 * @param allowFiles True if regular files may be chosen. 423 * @param allowDirectories True if directories may be chosen. 424 * @param background The background color for the text entry box. 425 * @param foreground The foreground color for the text entry box. 426 * @return The file chooser. 427 */ 428 public QueryFileChooser addFileChooser(String name, String label, 429 String defaultName, URI base, File startingDirectory, 430 boolean allowFiles, boolean allowDirectories, Color background, 431 Color foreground) { 432 return addFileChooser(name, label, defaultName, base, startingDirectory, 433 allowFiles, allowDirectories, false, background, foreground); 434 } 435 436 /** Create a FileChooser. 437 * @param name The name used to identify the entry (when calling get). 438 * @param label The label to attach to the entry. 439 * @param defaultName The default file name to use. 440 * @param base The URI with respect to which to give 441 * relative file names, or null to give absolute file name. 442 * @param startingDirectory The directory to open the file chooser in. 443 * @param allowFiles True if regular files may be chosen. 444 * @param allowDirectories True if directories may be chosen. 445 * @param save Whether the file is to be saved or opened. 446 * @param background The background color for the text entry box. 447 * @param foreground The foreground color for the text entry box. 448 * @return The file chooser. 449 */ 450 public QueryFileChooser addFileChooser(String name, String label, 451 String defaultName, URI base, File startingDirectory, 452 boolean allowFiles, boolean allowDirectories, boolean save, 453 Color background, Color foreground) { 454 return addFileChooser(name, label, defaultName, base, startingDirectory, 455 allowFiles, allowDirectories, save, background, foreground, 456 null); 457 } 458 459 /** Create a FileChooser. 460 * @param name The name used to identify the entry (when calling get). 461 * @param label The label to attach to the entry. 462 * @param defaultName The default file name to use. 463 * @param base The URI with respect to which to give 464 * relative file names, or null to give absolute file name. 465 * @param startingDirectory The directory to open the file chooser in. 466 * @param allowFiles True if regular files may be chosen. 467 * @param allowDirectories True if directories may be chosen. 468 * @param save Whether the file is to be saved or opened. 469 * @param background The background color for the text entry box. 470 * @param foreground The foreground color for the text entry box. 471 * @param filter A filename filter, or null to not have one. 472 * @return The file chooser. 473 */ 474 public QueryFileChooser addFileChooser(String name, String label, 475 String defaultName, URI base, File startingDirectory, 476 boolean allowFiles, boolean allowDirectories, boolean save, 477 Color background, Color foreground, FilenameFilter filter) { 478 JLabel lbl = _constructLabel(label); 479 lbl.setBackground(_background); 480 481 QueryFileChooser fileChooser = new QueryFileChooser(this, name, 482 defaultName, base, startingDirectory, allowFiles, 483 allowDirectories, save, background, foreground, filter); 484 _addPair(name, lbl, fileChooser, fileChooser); 485 return fileChooser; 486 } 487 488 /** Create a single-line entry box with the specified name, label, and 489 * default value. To control the width of the box, call setTextWidth() 490 * first. 491 * @param name The name used to identify the entry (when accessing 492 * the entry). 493 * @param label The label to attach to the entry. 494 * @param defaultValue Default value to appear in the entry box. 495 */ 496 public void addLine(String name, String label, String defaultValue) { 497 addLine(name, label, defaultValue, Color.white, Color.black); 498 } 499 500 /** Create a single-line entry box with the specified name, label, 501 * default value, and background color. To control the width of 502 * the box, call setTextWidth() first. 503 * @param name The name used to identify the entry (when accessing 504 * the entry). 505 * @param label The label to attach to the entry. 506 * @param defaultValue Default value to appear in the entry box. 507 * @param background The background color. 508 * @param foreground The foreground color. 509 */ 510 public void addLine(String name, String label, String defaultValue, 511 Color background, Color foreground) { 512 JLabel lbl = _constructLabel(label); 513 lbl.setBackground(_background); 514 515 JTextField entryBox = new JTextField(defaultValue, _width); 516 entryBox.setBackground(background); 517 entryBox.setForeground(foreground); 518 _addPair(name, lbl, entryBox, entryBox); 519 520 // Add the listener last so that there is no notification 521 // of the first value. 522 entryBox.addActionListener(new QueryActionListener(this, name)); 523 524 // Add a listener for loss of focus. When the entry gains 525 // and then loses focus, listeners are notified of an update, 526 // but only if the value has changed since the last notification. 527 // FIXME: Unfortunately, Java calls this listener some random 528 // time after the window has been closed. It is not even a 529 // a queued event when the window is closed. Thus, we have 530 // a subtle bug where if you enter a value in a line, do not 531 // hit return, and then click on the X to close the window, 532 // the value is restored to the original, and then sometime 533 // later, the focus is lost and the entered value becomes 534 // the value of the parameter. I don't know of any workaround. 535 entryBox.addFocusListener(new QueryFocusListener(this, name)); 536 } 537 538 /** Create a single-line password box with the specified name, label, and 539 * default value. To control the width of the box, call setTextWidth() 540 * first. A value that is entered in the password box should be 541 * accessed using getCharArrayValue(). The value returned by 542 * stringValue() is whatever you specify as a defaultValue. 543 * @param name The name used to identify the entry (when accessing 544 * the entry). 545 * @param label The label to attach to the entry. 546 * @param defaultValue Default value to appear in the entry box. 547 * @return The password field for the input. 548 * @since Ptolemy II 3.1 549 */ 550 public JPasswordField addPassword(String name, String label, 551 String defaultValue) { 552 return addPassword(name, label, defaultValue, Color.white, Color.black); 553 } 554 555 /** Create a single-line password box with the specified name, 556 * label, and default value. To control the width of the box, 557 * call setTextWidth() first. 558 * To get the value, call getCharArrayValue(). 559 * Calling getStringValue() on a password entry will result in an 560 * error because it is less secure to pass around passwords as 561 * Strings than as arrays of characters. 562 * <p>The underlying class that is used to implement the password 563 * facility is javax.swing.JPasswordField. For details about how to 564 * use JPasswordField, see the 565 * <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/passwordfield.html" target="_top">Java Tutorial</a> 566 * 567 * @param name The name used to identify the entry (when accessing 568 * the entry). 569 * @param label The label to attach to the entry. 570 * @param defaultValue Default value to appear in the entry box. 571 * @param background The background color. 572 * @param foreground The foreground color. 573 * @return The password field for the input. 574 * @since Ptolemy II 3.1 575 */ 576 public JPasswordField addPassword(String name, String label, 577 String defaultValue, Color background, Color foreground) { 578 JLabel lbl = _constructLabel(label); 579 lbl.setBackground(_background); 580 581 JPasswordField entryBox = new JPasswordField(defaultValue, _width); 582 entryBox.setBackground(background); 583 entryBox.setForeground(foreground); 584 _addPair(name, lbl, entryBox, entryBox); 585 586 // Add the listener last so that there is no notification 587 // of the first value. 588 entryBox.addActionListener(new QueryActionListener(this, name)); 589 590 // Add a listener for loss of focus. When the entry gains 591 // and then loses focus, listeners are notified of an update, 592 // but only if the value has changed since the last notification. 593 // FIXME: Unfortunately, Java calls this listener some random 594 // time after the window has been closed. It is not even a 595 // a queued event when the window is closed. Thus, we have 596 // a subtle bug where if you enter a value in a line, do not 597 // hit return, and then click on the X to close the window, 598 // the value is restored to the original, and then sometime 599 // later, the focus is lost and the entered value becomes 600 // the value of the parameter. I don't know of any workaround. 601 entryBox.addFocusListener(new QueryFocusListener(this, name)); 602 603 return entryBox; 604 } 605 606 /** Add a listener. The changed() method of the listener will be 607 * called when any of the entries is changed. Note that "line" 608 * entries only trigger this call when Return or Enter is pressed, or 609 * when the entry gains and then loses the keyboard focus. 610 * Notice that the currently selected line loses focus when the 611 * panel is destroyed, so notification of any changes that 612 * have been made will be done at that time. That notification 613 * will occur in the UI thread, and may be later than expected. 614 * Notification due to loss of focus only occurs if the value 615 * of the entry has changed since the last notification. 616 * If the listener has already been added, then do nothing. 617 * @param listener The listener to add. 618 * @see #removeQueryListener(QueryListener) 619 */ 620 public void addQueryListener(QueryListener listener) { 621 if (_listeners == null) { 622 _listeners = new Vector(); 623 } 624 625 if (_listeners.contains(listener)) { 626 return; 627 } 628 629 _listeners.add(listener); 630 } 631 632 /** Create a bank of radio buttons. A radio button provides a list of 633 * choices, only one of which may be chosen at a time. 634 * @param name The name used to identify the entry (when calling get). 635 * @param label The label to attach to the entry. 636 * @param values The list of possible choices. 637 * @param defaultValue Default value. 638 */ 639 public void addRadioButtons(String name, String label, String[] values, 640 String defaultValue) { 641 JLabel lbl = _constructLabel(label); 642 lbl.setBackground(_background); 643 644 FlowLayout flow = new FlowLayout(); 645 flow.setAlignment(FlowLayout.LEFT); 646 647 // This must be a JPanel, not a Panel, or the scroll bars won't work. 648 JPanel buttonPanel = new JPanel(flow); 649 650 ButtonGroup group = new ButtonGroup(); 651 QueryActionListener listener = new QueryActionListener(this, name); 652 653 // Regrettably, ButtonGroup provides no method to find out 654 // which button is selected, so we have to go through a 655 // song and dance here... 656 JRadioButton[] buttons = new JRadioButton[values.length]; 657 658 for (int i = 0; i < values.length; i++) { 659 JRadioButton checkbox = new JRadioButton(values[i]); 660 buttons[i] = checkbox; 661 checkbox.setBackground(_background); 662 663 // The following (essentially) undocumented method does nothing... 664 // checkbox.setContentAreaFilled(true); 665 checkbox.setOpaque(false); 666 667 if (values[i].equals(defaultValue)) { 668 checkbox.setSelected(true); 669 } 670 671 group.add(checkbox); 672 buttonPanel.add(checkbox); 673 674 // Add the listener last so that there is no notification 675 // of the first value. 676 checkbox.addActionListener(listener); 677 } 678 679 _addPair(name, lbl, buttonPanel, buttons); 680 } 681 682 /** Create a bank of buttons that provides a list of 683 * choices, any subset of which may be chosen at a time. 684 * @param name The name used to identify the entry (when calling get). 685 * @param label The label to attach to the entry. 686 * @param values The list of possible choices. 687 * @param initiallySelected The initially selected choices, or null 688 * to indicate that none are selected. 689 */ 690 public void addSelectButtons(String name, String label, String[] values, 691 Set initiallySelected) { 692 JLabel lbl = _constructLabel(label); 693 lbl.setBackground(_background); 694 695 FlowLayout flow = new FlowLayout(); 696 flow.setAlignment(FlowLayout.LEFT); 697 698 // This must be a JPanel, not a Panel, or the scroll bars won't work. 699 JPanel buttonPanel = new JPanel(flow); 700 701 QueryActionListener listener = new QueryActionListener(this, name); 702 703 if (initiallySelected == null) { 704 initiallySelected = new HashSet(); 705 } 706 707 JRadioButton[] buttons = new JRadioButton[values.length]; 708 709 for (int i = 0; i < values.length; i++) { 710 JRadioButton checkbox = new JRadioButton(values[i]); 711 buttons[i] = checkbox; 712 checkbox.setBackground(_background); 713 714 // The following (essentially) undocumented method does nothing... 715 // checkbox.setContentAreaFilled(true); 716 checkbox.setOpaque(false); 717 718 if (initiallySelected.contains(values[i])) { 719 checkbox.setSelected(true); 720 } 721 722 buttonPanel.add(checkbox); 723 724 // Add the listener last so that there is no notification 725 // of the first value. 726 checkbox.addActionListener(listener); 727 } 728 729 _addPair(name, lbl, buttonPanel, buttons); 730 } 731 732 /** Create a horizontal separator between components. 733 */ 734 public void addSeparator() { 735 JPanel panel = new JPanel(); 736 panel.setLayout(new BorderLayout()); 737 738 JPanel top = new JPanel(); 739 top.setPreferredSize(new Dimension(-1, 5)); 740 panel.add(top, BorderLayout.NORTH); 741 742 JSeparator separator = new JSeparator(SwingConstants.HORIZONTAL); 743 panel.add(separator, BorderLayout.CENTER); 744 745 JPanel bottom = new JPanel(); 746 bottom.setPreferredSize(new Dimension(-1, 5)); 747 panel.add(bottom, BorderLayout.SOUTH); 748 749 _constraints.gridwidth = GridBagConstraints.REMAINDER; 750 _constraints.insets = _insets; 751 _grid.setConstraints(panel, _constraints); 752 _entryPanel.add(panel); 753 754 _recalculatePreferredSize(panel); 755 } 756 757 /** Create a slider with the specified name, label, default 758 * value, maximum, and minimum. 759 * @param name The name used to identify the slider. 760 * @param label The label to attach to the slider. 761 * @param defaultValue Initial position of slider. 762 * @param maximum Maximum value of slider. 763 * @param minimum Minimum value of slider. 764 * @return The slider. 765 * @exception IllegalArgumentException If the desired default value 766 * is not between the minimum and maximum. 767 */ 768 public JSlider addSlider(String name, String label, int defaultValue, 769 int minimum, int maximum) throws IllegalArgumentException { 770 return addSlider(name, label, defaultValue, minimum, maximum, null, 771 null); 772 } 773 774 /** Create a slider with the specified name, label, default value, 775 * maximum, minimum, and label texts for the maximum and minimum 776 * slider positions. 777 * @param name The name used to identify the slider. 778 * @param label The label to attach to the slider. 779 * @param defaultValue Initial position of slider. 780 * @param maximum Maximum value of slider. 781 * @param minimum Minimum value of slider. 782 * @param minLabelText Text to be displayed at the slider's minimum 783 * setting. Set to {@code null} or the empty 784 * String to hide the minimum label. 785 * @param maxLabelText Text to be displayed at the slider's maximum 786 * setting. Set to {@code null} or the empty 787 * String to hide the maximum label. 788 * @return The slider. 789 * @exception IllegalArgumentException If the desired default value 790 * is not between the minimum and maximum. 791 */ 792 public JSlider addSlider(String name, String label, int defaultValue, 793 int minimum, int maximum, String minLabelText, String maxLabelText) 794 throws IllegalArgumentException { 795 JLabel lbl = _constructLabel(label); 796 797 if (minimum > maximum) { 798 int temp = minimum; 799 minimum = maximum; 800 maximum = temp; 801 } 802 803 if (defaultValue > maximum || defaultValue < minimum) { 804 throw new IllegalArgumentException("Desired default " + "value \"" 805 + defaultValue + "\" does not fall " 806 + "between the minimum and maximum."); 807 } 808 809 JSlider slider = new JSlider(minimum, maximum, defaultValue); 810 811 // Determine if minimum and maximum labels have to be created 812 if (minLabelText == null || minLabelText.isEmpty() 813 || maxLabelText == null || maxLabelText.isEmpty()) { 814 _addPair(name, lbl, slider, slider); 815 } else { 816 // Add labels to the slider and put everything into a panel 817 JPanel panel = new JPanel(); 818 panel.setLayout(new GridBagLayout()); 819 820 // Configure and add the slider 821 GridBagConstraints c = new GridBagConstraints(); 822 c.fill = GridBagConstraints.HORIZONTAL; 823 c.gridx = 0; 824 c.gridy = 0; 825 c.gridwidth = 2; 826 c.weightx = 1.0; 827 828 slider.setPaintTicks(true); 829 slider.setMajorTickSpacing(maximum - minimum); 830 panel.add(slider, c); 831 832 // Insets to leave some space below the slider's labels, if any 833 Insets labelInsets = new Insets(0, 0, 10, 0); 834 835 // Minimum label 836 if (minLabelText != null && minLabelText.length() > 0) { 837 JLabel minLabel = new JLabel(minLabelText); 838 839 c = new GridBagConstraints(); 840 c.anchor = GridBagConstraints.LINE_START; 841 c.gridx = 0; 842 c.gridy = 1; 843 c.insets = labelInsets; 844 845 panel.add(minLabel, c); 846 } 847 848 // Maximum label 849 if (maxLabelText != null && maxLabelText.length() > 0) { 850 JLabel maxLabel = new JLabel(maxLabelText); 851 852 c = new GridBagConstraints(); 853 c.anchor = GridBagConstraints.LINE_END; 854 c.gridx = 1; 855 c.gridy = 1; 856 c.insets = labelInsets; 857 858 panel.add(maxLabel, c); 859 } 860 861 _addPair(name, lbl, panel, slider); 862 } 863 864 slider.addChangeListener(new SliderListener(this, name)); 865 return slider; 866 } 867 868 /** Add text to the query. 869 * @param text The text to be added. 870 * @param color The color of the text to be added. 871 * @param alignment The alignment, which is a value suitable 872 * for the JPanel() constructor. 873 */ 874 public void addText(String text, Color color, int alignment) { 875 JPanel panel = new JPanel(); 876 panel.setLayout(new BorderLayout()); 877 878 JPanel top = new JPanel(); 879 top.setPreferredSize(new Dimension(-1, 5)); 880 panel.add(top, BorderLayout.NORTH); 881 882 JLabel label = new JLabel(text, alignment); 883 label.setForeground(color); 884 panel.add(label, BorderLayout.CENTER); 885 886 JPanel bottom = new JPanel(); 887 bottom.setPreferredSize(new Dimension(-1, 5)); 888 panel.add(bottom, BorderLayout.SOUTH); 889 890 _constraints.gridwidth = GridBagConstraints.REMAINDER; 891 _constraints.insets = _insets; 892 _grid.setConstraints(panel, _constraints); 893 _entryPanel.add(panel); 894 895 _recalculatePreferredSize(panel); 896 } 897 898 /** Create a text area. 899 * @param name The name used to identify the entry (when calling get). 900 * @param label The label to attach to the entry. 901 * @param theValue The value of this text area 902 */ 903 public void addTextArea(String name, String label, String theValue) { 904 addTextArea(name, label, theValue, Color.white, Color.black, _height, 905 _width); 906 } 907 908 /** Create a text area. 909 * @param name The name used to identify the entry (when calling get). 910 * @param label The label to attach to the entry. 911 * @param theValue The value of this text area. 912 * @param background The background color. 913 * @param foreground The foreground color. 914 */ 915 public void addTextArea(String name, String label, String theValue, 916 Color background, Color foreground) { 917 addTextArea(name, label, theValue, background, foreground, _height, 918 _width); 919 } 920 921 /** Create a text area with the specified height and width (in 922 * characters). 923 * @param name The name used to identify the entry (when calling get). 924 * @param label The label to attach to the entry. 925 * @param theValue The value of this text area. 926 * @param background The background color. 927 * @param foreground The foreground color. 928 * @param height The height. 929 * @param width The width. 930 * @return The text area. 931 */ 932 public JTextArea addTextArea(String name, String label, String theValue, 933 Color background, Color foreground, int height, int width) { 934 JLabel lbl = _constructLabel(label); 935 lbl.setBackground(_background); 936 937 JTextArea textArea = new JTextArea(theValue, height, width); 938 textArea.setEditable(true); 939 textArea.setBackground(background); 940 textArea.setForeground(foreground); 941 942 QueryScrollPane textPane = new QueryScrollPane(textArea); 943 _addPair(name, lbl, textPane, textPane); 944 textArea.addFocusListener(new QueryFocusListener(this, name)); 945 946 textArea.setFocusTraversalKeys( 947 KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null); 948 textArea.setFocusTraversalKeys( 949 KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null); 950 951 textArea.getInputMap().put(KeyStroke.getKeyStroke("shift ENTER"), 952 "TRANSFER_TEXT"); 953 final JTextArea area = textArea; 954 textArea.getActionMap().put("TRANSFER_TEXT", new AbstractAction() { 955 @Override 956 public void actionPerformed(ActionEvent e) { 957 // This code is necessary for r64799: Behavior of a 958 // dialogue: shift+ENTER = new line, ENTER = commit 959 // This was not consistent with the dialogue of 960 // transitions in modal models, in fact, it was not 961 // possible to add new lines to, e.g. guard 962 // expressions, anymore. Changed to new ENTER, 963 // shift+ENTER behavior. 964 965 area.insert("\n", area.getCaretPosition()); 966 if (area.getRows() < 4) { 967 area.setRows(area.getRows() + 1); 968 area.revalidate(); 969 Component parent = area.getParent(); 970 while (parent != null 971 && !(parent instanceof EditableParametersDialog)) { 972 parent = parent.getParent(); 973 } 974 if (parent instanceof EditableParametersDialog) { 975 // We use an interface her to avoid a 976 // dependency on 977 // ptolemy.actor.gui.EditParametersDialog. 978 EditableParametersDialog dialog = (EditableParametersDialog) parent; 979 dialog.doLayoutAndPack(); 980 } 981 } 982 } 983 }); 984 985 return textArea; 986 } 987 988 /** Get the current value in the entry with the given name 989 * and return as a boolean. If the entry is not a checkbox, 990 * then throw an exception. 991 * @param name The name of the entry. 992 * @deprecated Use getBooleanValue(String name) instead. 993 * @return The state of the checkbox. 994 * @exception NoSuchElementException If there is no item with the 995 * specified name. Note that this is a runtime exception, so it 996 * need not be declared explicitly. 997 * @exception IllegalArgumentException If the entry is not a 998 * checkbox. This is a runtime exception, so it 999 * need not be declared explicitly. 1000 */ 1001 @Deprecated 1002 public boolean booleanValue(String name) 1003 throws NoSuchElementException, IllegalArgumentException { 1004 return getBooleanValue(name); 1005 } 1006 1007 /** Get the current value in the entry with the given name 1008 * and return as a double value. If the entry is not a line, 1009 * then throw an exception. If the value of the entry is not 1010 * a double, then throw an exception. 1011 * @param name The name of the entry. 1012 * @deprecated Use getDoubleValue(String name) instead. 1013 * @return The value currently in the entry as a double. 1014 * @exception NoSuchElementException If there is no item with the 1015 * specified name. Note that this is a runtime exception, so it 1016 * need not be declared explicitly. 1017 * @exception NumberFormatException If the value of the entry cannot 1018 * be converted to a double. This is a runtime exception, so it 1019 * need not be declared explicitly. 1020 * @exception IllegalArgumentException If the entry is not a 1021 * line. This is a runtime exception, so it 1022 * need not be declared explicitly. 1023 */ 1024 @Deprecated 1025 public double doubleValue(String name) throws IllegalArgumentException, 1026 NoSuchElementException, NumberFormatException { 1027 return getDoubleValue(name); 1028 } 1029 1030 /** Get the current value in the entry with the given name 1031 * and return as a boolean. If the entry is not a checkbox, 1032 * then throw an exception. 1033 * @param name The name of the entry. 1034 * @return The state of the checkbox. 1035 * @exception NoSuchElementException If there is no item with the 1036 * specified name. Note that this is a runtime exception, so it 1037 * need not be declared explicitly. 1038 * @exception IllegalArgumentException If the entry is not a 1039 * checkbox. This is a runtime exception, so it 1040 * need not be declared explicitly. 1041 */ 1042 public boolean getBooleanValue(String name) 1043 throws NoSuchElementException, IllegalArgumentException { 1044 Object result = _entries.get(name); 1045 1046 if (result == null) { 1047 throw new NoSuchElementException( 1048 "No item named \"" + name + "\" in the query box."); 1049 } 1050 1051 if (result instanceof JToggleButton) { 1052 return ((JToggleButton) result).isSelected(); 1053 } else { 1054 throw new IllegalArgumentException("Item named \"" + name 1055 + "\" is not a radio button, and hence does not have " 1056 + "a boolean value."); 1057 } 1058 } 1059 1060 /** Get the current value in the entry with the given name 1061 * and return as an array of characters. 1062 * <p>If the entry is a password field, then it is recommended for 1063 * strong security that each element of the array be set to 0 1064 * after use. 1065 * @param name The name of the entry. 1066 * @return The state of the entry 1067 * @exception NoSuchElementException If there is no item with the 1068 * specified name. Note that this is a runtime exception, so it 1069 * need not be declared explicitly. 1070 * @exception IllegalArgumentException If the entry type does not 1071 * have a string representation (this should not be thrown). 1072 * This is a runtime exception, so it need not be declared explicitly. 1073 * @since Ptolemy II 3.1 1074 */ 1075 public char[] getCharArrayValue(String name) 1076 throws NoSuchElementException, IllegalArgumentException { 1077 Object result = _entries.get(name); 1078 1079 if (result == null) { 1080 throw new NoSuchElementException( 1081 "No item named \"" + name + "\" in the query box."); 1082 } 1083 1084 if (result instanceof JPasswordField) { 1085 // Calling JPasswordField.getText() is deprecated 1086 return ((JPasswordField) result).getPassword(); 1087 } else { 1088 return getStringValue(name).toCharArray(); 1089 } 1090 } 1091 1092 /** Get the current value in the entry with the given name 1093 * and return as a double value. If the entry is not a line, 1094 * then throw an exception. If the value of the entry is not 1095 * a double, then throw an exception. 1096 * @param name The name of the entry. 1097 * @return The value currently in the entry as a double. 1098 * @exception NoSuchElementException If there is no item with the 1099 * specified name. Note that this is a runtime exception, so it 1100 * need not be declared explicitly. 1101 * @exception NumberFormatException If the value of the entry cannot 1102 * be converted to a double. This is a runtime exception, so it 1103 * need not be declared explicitly. 1104 * @exception IllegalArgumentException If the entry is not a 1105 * line. This is a runtime exception, so it 1106 * need not be declared explicitly. 1107 */ 1108 public double getDoubleValue(String name) throws IllegalArgumentException, 1109 NoSuchElementException, NumberFormatException { 1110 Object result = _entries.get(name); 1111 1112 if (result == null) { 1113 throw new NoSuchElementException( 1114 "No item named \"" + name + " \" in the query box."); 1115 } 1116 1117 if (result instanceof JPasswordField) { 1118 // Note that JPasswordField extends JTextField, so 1119 // we should check for JPasswordField first. 1120 throw new IllegalArgumentException("For security reasons, " 1121 + "calling getDoubleValue() on a password field is " 1122 + "not permitted. Instead, call getCharArrayValue()"); 1123 } else if (result instanceof JTextField) { 1124 return Double.parseDouble(((JTextField) result).getText()); 1125 } else { 1126 throw new IllegalArgumentException("Item named \"" + name 1127 + "\" is not a text line, and hence cannot be converted " 1128 + "to a double value."); 1129 } 1130 } 1131 1132 /** Get the current value in the entry with the given name 1133 * and return as an integer. If the entry is not a line, 1134 * choice, or slider, then throw an exception. 1135 * If it is a choice or radio button, then return the 1136 * index of the first selected item. 1137 * @param name The name of the entry. 1138 * @return The value currently in the entry as an integer. 1139 * @exception NoSuchElementException If there is no item with the 1140 * specified name. Note that this is a runtime exception, so it 1141 * need not be declared explicitly. 1142 * @exception NumberFormatException If the value of the entry cannot 1143 * be converted to an integer. This is a runtime exception, so it 1144 * need not be declared explicitly. 1145 * @exception IllegalArgumentException If the entry is not a 1146 * choice, line, or slider. This is a runtime exception, so it 1147 * need not be declared explicitly. 1148 */ 1149 public int getIntValue(String name) throws IllegalArgumentException, 1150 NoSuchElementException, NumberFormatException { 1151 Object result = _entries.get(name); 1152 1153 if (result == null) { 1154 throw new NoSuchElementException( 1155 "No item named \"" + name + " \" in the query box."); 1156 } 1157 1158 if (result instanceof JPasswordField) { 1159 // Note that JPasswordField extends JTextField, so 1160 // we should check for JPasswordField first. 1161 throw new IllegalArgumentException("For security reasons, " 1162 + "calling getIntValue() on a password field is " 1163 + "not permitted. Instead, call getCharArrayValue()"); 1164 } else if (result instanceof JTextField) { 1165 return Integer.parseInt(((JTextField) result).getText()); 1166 } else if (result instanceof JSlider) { 1167 return ((JSlider) result).getValue(); 1168 } else if (result instanceof JComboBox) { 1169 return ((JComboBox) result).getSelectedIndex(); 1170 } else if (result instanceof JToggleButton[]) { 1171 // Regrettably, ButtonGroup gives no way to determine 1172 // which button is selected, so we have to search... 1173 JToggleButton[] buttons = (JToggleButton[]) result; 1174 1175 for (int i = 0; i < buttons.length; i++) { 1176 if (buttons[i].isSelected()) { 1177 return i; 1178 } 1179 } 1180 1181 // In theory, we shouldn't get here, but the compiler 1182 // is unhappy without a return. 1183 return -1; 1184 } else { 1185 throw new IllegalArgumentException("Item named \"" + name 1186 + "\" is not a text line or slider, and hence " 1187 + "cannot be converted to " + "an integer value."); 1188 } 1189 } 1190 1191 /** Return the preferred height, but set the width to the maximum 1192 * possible value. Currently (JDK 1.3), only BoxLayout pays any 1193 * attention to getMaximumSize(). 1194 * 1195 * @return The maximum desired size. 1196 */ 1197 @Override 1198 public Dimension getMaximumSize() { 1199 // Unfortunately, if we don't have a message, then we end up with 1200 // an empty space that is difficult to control the size of, which 1201 // requires us to set the maximum size to be the same as 1202 // the preferred size 1203 // If you change this, be sure to try applets that have both 1204 // horizontal and vertical layout. 1205 Dimension preferred = getPreferredSize(); 1206 preferred.width = Short.MAX_VALUE; 1207 return preferred; 1208 } 1209 1210 /** Get the current value in the entry with the given name, 1211 * and return as an Object. This is different from {@link 1212 * #getStringValue(String)} in that if the entry is a combo box, the 1213 * selected object is returned, which need not be a string. 1214 * All entry types support this. 1215 * Note that this method should be called from the event dispatch 1216 * thread, since it needs to query to UI widgets for their current 1217 * values. If it is called from another thread, there is no 1218 * assurance that the value returned will be the current value. 1219 * @param name The name of the entry. 1220 * @return The value currently in the entry as a String, unless 1221 * the entry is a combo box, in which case the selected object 1222 * is returned, or null is returned if no object is selected. 1223 * @exception NoSuchElementException If there is no item with the 1224 * specified name. Note that this is a runtime exception, so it 1225 * need not be declared explicitly. 1226 * @exception IllegalArgumentException If the entry type does not 1227 * have a string representation (this should not be thrown). 1228 */ 1229 public Object getObjectValue(String name) 1230 throws NoSuchElementException, IllegalArgumentException { 1231 Object result = _entries.get(name); 1232 1233 if (result == null) { 1234 throw new NoSuchElementException( 1235 "No item named \"" + name + " \" in the query box."); 1236 } 1237 1238 if (result instanceof JTextField) { 1239 return ((JTextField) result).getText(); 1240 } else if (result instanceof QueryColorChooser) { 1241 return ((QueryColorChooser) result).getSelectedColor(); 1242 } else if (result instanceof QueryFileChooser) { 1243 return ((QueryFileChooser) result).getSelectedFileName(); 1244 } else if (result instanceof JTextArea) { 1245 return ((JTextArea) result).getText(); 1246 } else if (result instanceof JToggleButton) { 1247 // JRadioButton and JCheckButton are subclasses of JToggleButton 1248 JToggleButton toggleButton = (JToggleButton) result; 1249 1250 if (toggleButton.isSelected()) { 1251 return "true"; 1252 } else { 1253 return "false"; 1254 } 1255 } else if (result instanceof JSlider) { 1256 return "" + ((JSlider) result).getValue(); 1257 } else if (result instanceof JComboBox) { 1258 return ((JComboBox) result).getSelectedItem(); 1259 } else if (result instanceof JToggleButton[]) { 1260 // JRadioButton and JCheckButton are subclasses of JToggleButton 1261 // Regrettably, ButtonGroup gives no way to determine 1262 // which button is selected, so we have to search... 1263 JToggleButton[] buttons = (JToggleButton[]) result; 1264 StringBuffer toReturn = null; 1265 1266 for (JToggleButton button : buttons) { 1267 if (button.isSelected()) { 1268 if (toReturn == null) { 1269 toReturn = new StringBuffer(button.getText()); 1270 } else { 1271 toReturn.append(", " + button.getText()); 1272 } 1273 } 1274 } 1275 1276 if (toReturn == null) { 1277 toReturn = new StringBuffer(); 1278 } 1279 1280 return toReturn.toString(); 1281 } else if (result instanceof QueryScrollPane) { 1282 return ((QueryScrollPane) result).getText(); 1283 } else if (result instanceof SettableQueryChooser) { 1284 return ((SettableQueryChooser) result).getQueryValue(); 1285 } else { 1286 throw new IllegalArgumentException("Query class cannot generate" 1287 + " a string representation for entries of type " 1288 + result.getClass()); 1289 } 1290 } 1291 1292 /** Get the current value in the entry with the given name, 1293 * and return as a String. All entry types support this. 1294 * Note that this method should be called from the event dispatch 1295 * thread, since it needs to query to UI widgets for their current 1296 * values. If it is called from another thread, there is no 1297 * assurance that the value returned will be the current value. 1298 * @param name The name of the entry. 1299 * @return The value currently in the entry as a String. 1300 * @exception NoSuchElementException If there is no item with the 1301 * specified name. Note that this is a runtime exception, so it 1302 * need not be declared explicitly. 1303 * @exception IllegalArgumentException If the entry type does not 1304 * have a string representation (this should not be thrown). 1305 */ 1306 public String getStringValue(String name) 1307 throws NoSuchElementException, IllegalArgumentException { 1308 // NOTE: getObjectValue() may return null if the entry 1309 // is a combo box and no object is selected. In that case, 1310 // return an empty string. 1311 Object result = getObjectValue(name); 1312 if (result != null) { 1313 return result.toString(); 1314 } 1315 return ""; 1316 } 1317 1318 /** Get the preferred number of lines to be used for entry boxes created 1319 * in using addTextArea(). The preferred height is set using 1320 * setTextHeight(). 1321 * @return The preferred height in lines. 1322 * @see #addTextArea(String, String, String) 1323 * @see #setTextHeight(int) 1324 */ 1325 public int getTextHeight() { 1326 return _height; 1327 } 1328 1329 /** Get the preferred width in characters to be used for entry 1330 * boxes created in using addLine(). The preferred width is set 1331 * using setTextWidth(). 1332 * @return The preferred width of an entry box in characters. 1333 * @see #setTextWidth(int) 1334 */ 1335 public int getTextWidth() { 1336 return _width; 1337 } 1338 1339 /** Return whether an entry exists with the specified name. 1340 * 1341 * @param name The name. 1342 * @return True if the entry exists; false otherwise. 1343 */ 1344 public boolean hasEntry(String name) { 1345 return _entries.containsKey(name); 1346 } 1347 1348 /** Get the current value in the entry with the given name 1349 * and return as an integer. If the entry is not a line, 1350 * choice, or slider, then throw an exception. 1351 * If it is a choice or radio button, then return the 1352 * index of the first selected item. 1353 * @param name The name of the entry. 1354 * @deprecated Use getIntValue(String name) instead. 1355 * @return The value currently in the entry as an integer. 1356 * @exception NoSuchElementException If there is no item with the 1357 * specified name. Note that this is a runtime exception, so it 1358 * need not be declared explicitly. 1359 * @exception NumberFormatException If the value of the entry cannot 1360 * be converted to an integer. This is a runtime exception, so it 1361 * need not be declared explicitly. 1362 * @exception IllegalArgumentException If the entry is not a 1363 * choice, line, or slider. This is a runtime exception, so it 1364 * need not be declared explicitly. 1365 */ 1366 @Deprecated 1367 public int intValue(String name) throws IllegalArgumentException, 1368 NoSuchElementException, NumberFormatException { 1369 return getIntValue(name); 1370 } 1371 1372 /** Notify listeners of the current value of all entries, unless 1373 * those entries have not changed since the last notification. 1374 */ 1375 public void notifyListeners() { 1376 Iterator names = _entries.keySet().iterator(); 1377 1378 while (names.hasNext()) { 1379 String name = (String) names.next(); 1380 _notifyListeners(name); 1381 } 1382 } 1383 1384 /** Remove a listener. If the listener has not been added, then 1385 * do nothing. 1386 * @param listener The listener to remove. 1387 * @see #addQueryListener(QueryListener) 1388 */ 1389 public void removeQueryListener(QueryListener listener) { 1390 if (_listeners == null) { 1391 return; 1392 } 1393 1394 _listeners.remove(listener); 1395 } 1396 1397 /** Set the value in the entry with the given name. 1398 * The second argument must be a string that can be parsed to the 1399 * proper type for the given entry, or an exception is thrown. 1400 * Note that this does NOT trigger the notification of listeners, and 1401 * intended to allow a way to set the query to reflect the current state. 1402 * @param name The name used to identify the entry (when calling get). 1403 * @param value The value to set the entry to. 1404 * @exception NoSuchElementException If there is no item with the 1405 * specified name. Note that this is a runtime exception, so it 1406 * need not be declared explicitly. 1407 * @exception IllegalArgumentException If the value does not parse 1408 * to the appropriate type. 1409 */ 1410 public void set(String name, String value) 1411 throws NoSuchElementException, IllegalArgumentException { 1412 Object result = _entries.get(name); 1413 1414 if (result == null) { 1415 throw new NoSuchElementException( 1416 "No item named \"" + name + " \" in the query box."); 1417 } 1418 1419 // FIXME: Surely there is a better way to do this... 1420 // We should define a set of inner classes, one for each entry type. 1421 // Currently, this has to be updated each time a new entry type 1422 // is added. 1423 if (result instanceof JTextField) { 1424 ((JTextField) result).setText(value); 1425 } else if (result instanceof JTextArea) { 1426 ((JTextArea) result).setText(value); 1427 } else if (result instanceof QueryScrollPane) { 1428 ((QueryScrollPane) result).setText(value); 1429 } else if (result instanceof JToggleButton) { 1430 // JRadioButton and JCheckButton are subclasses of JToggleButton 1431 Boolean flag = Boolean.parseBoolean(value); 1432 setBoolean(name, flag.booleanValue()); 1433 } else if (result instanceof JSlider) { 1434 Integer parsed = Integer.parseInt(value); 1435 ((JSlider) result).setValue(parsed.intValue()); 1436 } else if (result instanceof JComboBox) { 1437 ((JComboBox) result).setSelectedItem(value); 1438 } else if (result instanceof JToggleButton[]) { 1439 // First, parse the value, which may be a comma-separated list. 1440 Set selectedValues = new HashSet(); 1441 StringTokenizer tokenizer = new StringTokenizer(value, ","); 1442 1443 while (tokenizer.hasMoreTokens()) { 1444 selectedValues.add(tokenizer.nextToken().trim()); 1445 } 1446 1447 JToggleButton[] buttons = (JToggleButton[]) result; 1448 1449 for (JToggleButton button : buttons) { 1450 if (selectedValues.contains(button.getText())) { 1451 button.setSelected(true); 1452 } else { 1453 button.setSelected(false); 1454 } 1455 } 1456 } else if (result instanceof QueryColorChooser) { 1457 ((QueryColorChooser) result).setColor(value); 1458 } else if (result instanceof SettableQueryChooser) { 1459 ((SettableQueryChooser) result).setQueryValue(value); 1460 } else if (result instanceof QueryFileChooser) { 1461 ((QueryFileChooser) result).setFileName(value); 1462 } else { 1463 throw new IllegalArgumentException("Query class cannot set" 1464 + " a string representation for entries of type " 1465 + result.getClass()); 1466 } 1467 1468 // Record the new value as if it was the previously notified 1469 // value. Thus, any future change from this value will trigger 1470 // notification. 1471 _previous.put(name, value); 1472 } 1473 1474 /** Set the value in the entry with the given name and notify listeners. 1475 * The second argument must be a string that can be parsed to the 1476 * proper type for the given entry, or an exception is thrown. 1477 * @param name The name used to identify the entry (when calling get). 1478 * @param value The value to set the entry to. 1479 * @exception NoSuchElementException If there is no item with the 1480 * specified name. Note that this is a runtime exception, so it 1481 * need not be declared explicitly. 1482 * @exception IllegalArgumentException If the value does not parse 1483 * to the appropriate type. 1484 */ 1485 public void setAndNotify(String name, String value) 1486 throws NoSuchElementException, IllegalArgumentException { 1487 set(name, value); 1488 _notifyListeners(name); 1489 } 1490 1491 /** Set the background color for all the widgets. 1492 * @param color The background color. 1493 */ 1494 @Override 1495 public void setBackground(Color color) { 1496 super.setBackground(color); 1497 _background = color; 1498 1499 // Set the background of any components that already exist. 1500 Component[] components = getComponents(); 1501 1502 for (int i = 0; i < components.length; i++) { 1503 if (!(components[i] instanceof JTextField)) { 1504 components[i].setBackground(_background); 1505 } 1506 } 1507 } 1508 1509 /** Set the current value in the entry with the given name. 1510 * If the entry is not a checkbox, then throw an exception. 1511 * Notify listeners that the value has changed. 1512 * @param name The name of the entry. 1513 * @param value The new value of the entry. 1514 * @exception NoSuchElementException If there is no item with the 1515 * specified name. Note that this is a runtime exception, so it 1516 * need not be declared explicitly. 1517 * @exception IllegalArgumentException If the entry is not a 1518 * checkbox. This is a runtime exception, so it 1519 * need not be declared explicitly. 1520 */ 1521 public void setBoolean(String name, boolean value) 1522 throws NoSuchElementException, IllegalArgumentException { 1523 Object result = _entries.get(name); 1524 1525 if (result == null) { 1526 throw new NoSuchElementException( 1527 "No item named \"" + name + "\" in the query box."); 1528 } 1529 1530 if (result instanceof JToggleButton) { 1531 // JRadioButton and JCheckButton are subclasses of JToggleButton 1532 ((JToggleButton) result).setSelected(value); 1533 } else { 1534 throw new IllegalArgumentException("Item named \"" + name 1535 + "\" is not a radio button, and hence does not have " 1536 + "a boolean value."); 1537 } 1538 1539 _notifyListeners(name); 1540 } 1541 1542 /** Specify the number of columns to use. 1543 * The default is one. If an integer larger than one is specified 1544 * here, then the queries will be arranged using the specified number 1545 * of columns. As queries are added, they are put in the first row 1546 * until that row is full. Then they are put in the second row, etc. 1547 * @param columns The number of columns. 1548 */ 1549 public void setColumns(int columns) { 1550 if (columns <= 0) { 1551 throw new IllegalArgumentException( 1552 "Query.setColumns() requires a strictly positive " 1553 + "argument."); 1554 } 1555 1556 _columns = columns; 1557 } 1558 1559 /** Set the displayed text of an entry that has been added using 1560 * addDisplay. 1561 * Notify listeners that the value has changed. 1562 * @param name The name of the entry. 1563 * @param value The string to display. 1564 * @exception NoSuchElementException If there is no entry with the 1565 * specified name. Note that this is a runtime exception, so it 1566 * need not be declared explicitly. 1567 * @exception IllegalArgumentException If the entry is not a 1568 * display. This is a runtime exception, so it 1569 * need not be declared explicitly. 1570 */ 1571 public void setDisplay(String name, String value) 1572 throws NoSuchElementException, IllegalArgumentException { 1573 Object result = _entries.get(name); 1574 1575 if (result == null) { 1576 throw new NoSuchElementException( 1577 "No item named \"" + name + " \" in the query box."); 1578 } 1579 1580 if (result instanceof JTextArea) { 1581 JTextArea label = (JTextArea) result; 1582 label.setText(value); 1583 } else { 1584 throw new IllegalArgumentException("Item named \"" + name 1585 + "\" is not a display, and hence cannot be set using " 1586 + "setDisplay()."); 1587 } 1588 1589 _notifyListeners(name); 1590 } 1591 1592 /** For line, display, check box, slider, radio button, or choice 1593 * entries made, if the second argument is false, then it will 1594 * be disabled. 1595 * @param name The name of the entry. 1596 * @param value If false, disables the entry. 1597 */ 1598 public void setEnabled(String name, boolean value) { 1599 Object result = _entries.get(name); 1600 1601 if (result == null) { 1602 throw new NoSuchElementException( 1603 "No item named \"" + name + " \" in the query box."); 1604 } 1605 1606 if (result instanceof JComponent) { 1607 ((JComponent) result).setEnabled(value); 1608 } else if (result instanceof JToggleButton[]) { 1609 JToggleButton[] buttons = (JToggleButton[]) result; 1610 1611 for (JToggleButton button : buttons) { 1612 button.setEnabled(value); 1613 } 1614 } 1615 } 1616 1617 /** Set the insets for the GridBagLayout manager used to layout the 1618 * components. 1619 * 1620 * @param insets The insets. 1621 */ 1622 public void setInsets(Insets insets) { 1623 _insets = insets; 1624 } 1625 1626 /** Set the displayed text of an item that has been added using 1627 * addLine. Notify listeners that the value has changed. 1628 * @param name The name of the entry. 1629 * @param value The string to display. 1630 * @exception NoSuchElementException If there is no item with the 1631 * specified name. Note that this is a runtime exception, so it 1632 * need not be declared explicitly. 1633 * @exception IllegalArgumentException If the entry is not a 1634 * display. This is a runtime exception, so it 1635 * need not be declared explicitly. 1636 */ 1637 public void setLine(String name, String value) { 1638 Object result = _entries.get(name); 1639 1640 if (result == null) { 1641 throw new NoSuchElementException( 1642 "No item named \"" + name + " \" in the query box."); 1643 } 1644 1645 if (result instanceof JTextField) { 1646 JTextField line = (JTextField) result; 1647 line.setText(value); 1648 } else { 1649 throw new IllegalArgumentException("Item named \"" + name 1650 + "\" is not a line, and hence cannot be set using " 1651 + "setLine()."); 1652 } 1653 1654 _notifyListeners(name); 1655 } 1656 1657 /** Specify a message to be displayed above the query. 1658 * @param message The message to display. 1659 */ 1660 public void setMessage(String message) { 1661 if (!_messageScrollPaneAdded) { 1662 _messageScrollPaneAdded = true; 1663 add(_messageScrollPane, 1); 1664 1665 // Add a spacer. 1666 add(Box.createRigidArea(new Dimension(0, 10)), 2); 1667 } 1668 1669 _messageArea.setText(message); 1670 1671 // I'm not sure why we need to add 1 here? 1672 int lineCount = _messageArea.getLineCount() + 1; 1673 1674 // Keep the line count to less than 30 lines. If 1675 // we have more than 30 lines, we get a scroll bar. 1676 if (lineCount > 30) { 1677 lineCount = 30; 1678 } 1679 1680 _messageArea.setRows(lineCount); 1681 _messageArea.setColumns(_width); 1682 1683 // In case size has changed. 1684 validate(); 1685 } 1686 1687 /** Set the position of an item that has been added using 1688 * addSlider. Notify listeners that the value has changed. 1689 * @param name The name of the entry. 1690 * @param value The value to set the slider position. 1691 * @exception NoSuchElementException If there is no item with the 1692 * specified name. Note that this is a runtime exception, so it 1693 * need not be declared explicitly. 1694 * @exception IllegalArgumentException If the entry is not a 1695 * slider. This is a runtime exception, so it 1696 * need not be declared explicitly. 1697 */ 1698 public void setSlider(String name, int value) { 1699 Object result = _entries.get(name); 1700 1701 if (result == null) { 1702 throw new NoSuchElementException( 1703 "No item named \"" + name + " \" in the query box."); 1704 } 1705 1706 if (result instanceof JSlider) { 1707 JSlider theSlider = (JSlider) result; 1708 1709 // Set the new slider position. 1710 theSlider.setValue(value); 1711 } else { 1712 throw new IllegalArgumentException("Item named \"" + name 1713 + "\" is not a slider, and hence cannot be set using " 1714 + "setSlider()."); 1715 } 1716 1717 _notifyListeners(name); 1718 } 1719 1720 /** Specify the preferred height to be used for entry boxes created 1721 * in using addTextArea(). If this is called multiple times, then 1722 * it only affects subsequent calls. 1723 * @param characters The preferred height. 1724 * @see #addTextArea(String, String, String) 1725 * @see #getTextHeight() 1726 */ 1727 public void setTextHeight(int characters) { 1728 _height = characters; 1729 } 1730 1731 /** Specify the preferred width to be used for entry boxes created 1732 * in using addLine(). If this is called multiple times, then 1733 * it only affects subsequent calls. 1734 * @param characters The preferred width. 1735 * @see #getTextWidth() 1736 */ 1737 public void setTextWidth(int characters) { 1738 _width = characters; 1739 } 1740 1741 /** Specify a tool tip to appear when the mouse lingers over the label. 1742 * @param name The name of the entry. 1743 * @param tip The text of the tool tip. 1744 */ 1745 public void setToolTip(String name, String tip) { 1746 JLabel label = (JLabel) _labels.get(name); 1747 1748 if (label != null) { 1749 label.setToolTipText(tip); 1750 } 1751 } 1752 1753 /** Convert the specified string to a color. The string 1754 * has the form "{r, g, b, a}", where each of the letters 1755 * is a number between 0.0 and 1.0, representing red, green, 1756 * blue, and alpha. 1757 * @param description The description of the color, or white 1758 * if any parse error occurs. 1759 * @return A string representing the color. 1760 */ 1761 public static Color stringToColor(String description) { 1762 String[] specArray = description.split("[{},]"); 1763 float red = 0f; 1764 float green = 0f; 1765 float blue = 0f; 1766 float alpha = 1.0f; 1767 1768 // If any exceptions occur during the attempt to parse, 1769 // then just use the default color. 1770 try { 1771 int i = 0; 1772 1773 // Ignore any blank strings that this simple parsing produces. 1774 while (specArray[i].trim().equals("")) { 1775 i++; 1776 } 1777 1778 if (specArray.length > i) { 1779 red = Float.parseFloat(specArray[i]); 1780 } 1781 1782 i++; 1783 1784 while (specArray[i].trim().equals("")) { 1785 i++; 1786 } 1787 1788 if (specArray.length > i) { 1789 green = Float.parseFloat(specArray[i]); 1790 } 1791 1792 i++; 1793 1794 while (specArray[i].trim().equals("")) { 1795 i++; 1796 } 1797 1798 if (specArray.length > i) { 1799 blue = Float.parseFloat(specArray[i]); 1800 } 1801 1802 i++; 1803 1804 while (specArray[i].trim().equals("")) { 1805 i++; 1806 } 1807 1808 if (specArray.length > i) { 1809 alpha = Float.parseFloat(specArray[i]); 1810 } 1811 } catch (Exception ex) { 1812 // Ignore and use default color. 1813 } 1814 return new Color(red, green, blue, alpha); 1815 } 1816 1817 /** Get the current value in the entry with the given name, 1818 * and return as a String. All entry types support this. 1819 * Note that this method should be called from the event dispatch 1820 * thread, since it needs to query to UI widgets for their current 1821 * values. If it is called from another thread, there is no 1822 * assurance that the value returned will be the current value. 1823 * @param name The name of the entry. 1824 * @deprecated Use getStringValue(String name) instead. 1825 * @return The value currently in the entry as a String. 1826 * @exception NoSuchElementException If there is no item with the 1827 * specified name. Note that this is a runtime exception, so it 1828 * need not be declared explicitly. 1829 * @exception IllegalArgumentException If the entry type does not 1830 * have a string representation (this should not be thrown). 1831 */ 1832 @Deprecated 1833 public String stringValue(String name) 1834 throws NoSuchElementException, IllegalArgumentException { 1835 return getStringValue(name); 1836 } 1837 1838 /////////////////////////////////////////////////////////////////// 1839 //// public variables //// 1840 1841 /** The default height of entries created with addText(). */ 1842 public static final int DEFAULT_ENTRY_HEIGHT = 10; 1843 1844 /** The default width of entries created with addLine(). */ 1845 public static final int DEFAULT_ENTRY_WIDTH = 30; 1846 1847 /////////////////////////////////////////////////////////////////// 1848 //// protected methods //// 1849 1850 /** Add a label and a widget to the panel. 1851 * @param name The name of the entry. 1852 * @param label The label. 1853 * @param widget The interactive entry to the right of the label. 1854 * @param entry The object that contains user data. 1855 */ 1856 protected void _addPair(String name, JLabel label, Component widget, 1857 Object entry) { 1858 // Surely there is a better layout manager in swing... 1859 // Note that Box and BoxLayout do not work because they do not 1860 // support gridded layout. 1861 _constraints.gridwidth = 1; 1862 _constraints.insets = _leftPadding; 1863 _constraints.weightx = 0; 1864 _grid.setConstraints(label, _constraints); 1865 _entryPanel.add(label); 1866 1867 _constraints.insets = _insets; 1868 1869 if (_columns > 1 && (_entries.size() + 1) % _columns != 0) { 1870 _constraints.gridwidth = 1; 1871 } else { 1872 _constraints.gridwidth = GridBagConstraints.REMAINDER; 1873 } 1874 _constraints.weightx = 1; 1875 _grid.setConstraints(widget, _constraints); 1876 _entryPanel.add(widget); 1877 1878 _entries.put(name, entry); 1879 _labels.put(name, label); 1880 _previous.put(name, getObjectValue(name)); 1881 1882 _recalculatePreferredSize(widget); 1883 } 1884 1885 /** Construct a lable. 1886 * @param label The text for the label. If the value of the label 1887 * argument is non-empty, then ": " is appended. 1888 * @return The label. 1889 */ 1890 protected JLabel _constructLabel(String label) { 1891 JLabel lbl; 1892 if (!label.trim().equals("")) { 1893 lbl = new JLabel(label + ": "); 1894 } else { 1895 lbl = new JLabel(""); 1896 } 1897 lbl.setBackground(_background); 1898 return lbl; 1899 } 1900 1901 /** Recalculate the preferred size of the entry panel. 1902 * @param widget The widget added to the entry panel. 1903 */ 1904 protected void _recalculatePreferredSize(Component widget) { 1905 Dimension preferredSize = _entryPanel.getPreferredSize(); 1906 1907 // Add some slop to the width to take in to account 1908 // the width of the vertical scrollbar. 1909 preferredSize.width += 25; 1910 1911 // Applets seem to need this, see CT/SigmaDelta 1912 _widgetsHeight += widget.getPreferredSize().height + _insets.top 1913 + _insets.bottom; 1914 preferredSize.height = _widgetsHeight; 1915 1916 Toolkit tk = Toolkit.getDefaultToolkit(); 1917 1918 if (preferredSize.height > tk.getScreenSize().height) { 1919 // Fudge factor to keep this window smaller than the screen 1920 // height. CGSUnitBase and the Code Generator are good tests. 1921 preferredSize.height = (int) (tk.getScreenSize().height * 0.75); 1922 _entryScrollPane.setPreferredSize(preferredSize); 1923 } 1924 1925 _entryScrollPane.setPreferredSize(preferredSize); 1926 1927 // Call revalidate for the scrollbar. 1928 _entryPanel.revalidate(); 1929 } 1930 1931 /** Resize the textArea and repack the containing ComponentDialog. 1932 * This method is used to handle scrollbars in entries when the 1933 * user types in more text than will fit in a line or else uses 1934 * shift-enter to create a newline in an entry. 1935 * @param textArea The text area to be have its rows set and its parent packed. 1936 * @param minimumNumberOfRows If the text area has less than this 1937 * number of rows, then one row is added and the parent 1938 * ComponentDialog of the text area is repacked. 1939 */ 1940 protected static void _textAreaSetRowsAndRepackParent(JTextArea textArea, 1941 int minimumNumberOfRows) { 1942 // This method is based on code by Patricia Derler that was 1943 // originally in PtolemyQuery. 1944 1945 // One test for this is to drag in a StringConst and type in 1946 // lots of characters. You should get a scrollbar. See 1947 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5587 1948 if (textArea.getRows() < minimumNumberOfRows) { 1949 textArea.setRows(textArea.getRows() + 1); 1950 textArea.revalidate(); 1951 Component parent = textArea.getParent(); 1952 while (parent != null && !(parent instanceof ComponentDialog)) { 1953 parent = parent.getParent(); 1954 } 1955 if (parent instanceof ComponentDialog) { 1956 ComponentDialog dialog = (ComponentDialog) parent; 1957 dialog.doLayout(); 1958 dialog.pack(); 1959 } 1960 } 1961 } 1962 1963 /////////////////////////////////////////////////////////////////// 1964 //// protected variables //// 1965 1966 /** The background color as set by setBackground(). 1967 * This defaults to null, which indicates that the background 1968 * is the same as the container. 1969 */ 1970 protected Color _background = null; 1971 1972 /** Standard constraints for use with _grid. */ 1973 protected GridBagConstraints _constraints; 1974 1975 /** The hashtable of items in the query. */ 1976 protected Map _entries = new HashMap(); 1977 1978 /** Layout control. */ 1979 protected GridBagLayout _grid; 1980 1981 /** List of registered listeners. */ 1982 protected Vector _listeners; 1983 1984 /////////////////////////////////////////////////////////////////// 1985 //// friendly methods //// 1986 1987 /** Notify all registered listeners that something changed for the 1988 * specified entry, if it indeed has changed. The getStringValue() 1989 * method is used to check the current value against the previously 1990 * notified value, or the original value if there have been no 1991 * notifications. 1992 * @param name The entry that may have changed. 1993 */ 1994 void _notifyListeners(String name) { 1995 if (_listeners != null) { 1996 String previous = (String) _previous.get(name); 1997 String newValue = getStringValue(name); 1998 1999 if (newValue.equals(previous)) { 2000 return; 2001 } 2002 2003 // Store the new value to prevent repeated notification. 2004 // This must be done before listeners are notified, because 2005 // the notified listeners might do something that again triggers 2006 // notification, and we do not want that notification to occur 2007 // if the value has not changed. 2008 _previous.put(name, newValue); 2009 2010 Enumeration listeners = _listeners.elements(); 2011 2012 while (listeners.hasMoreElements()) { 2013 QueryListener queryListener = (QueryListener) listeners 2014 .nextElement(); 2015 queryListener.changed(name); 2016 } 2017 } 2018 } 2019 2020 /////////////////////////////////////////////////////////////////// 2021 //// private variables //// 2022 // The number of columns. 2023 private int _columns = 1; 2024 2025 // A panel within which the entries are placed. 2026 private JPanel _entryPanel = new JPanel(); 2027 2028 // A scroll pane that contains the _entryPanel. 2029 private JScrollPane _entryScrollPane; 2030 2031 // The number of lines in a text box. 2032 private int _height = DEFAULT_ENTRY_HEIGHT; 2033 2034 // The hashtable of labels in the query. 2035 private Map _labels = new HashMap(); 2036 2037 // Left padding insets. 2038 private Insets _leftPadding = new Insets(0, 10, 0, 0); 2039 2040 // Area for messages. 2041 private JTextArea _messageArea = null; 2042 2043 // A scroll pane that contains the _messageArea. 2044 private JScrollPane _messageScrollPane; 2045 2046 // True if we have added the _messageScrollPane 2047 private boolean _messageScrollPaneAdded = false; 2048 2049 // No padding insets. 2050 private Insets _insets = new Insets(0, 0, 0, 0); 2051 2052 // The hashtable of previous values, indexed by entry name. 2053 private Map _previous = new HashMap(); 2054 2055 // The sum of the height of the widgets added using _addPair 2056 // If you adjust this, try the GR/Pendulum demo, which has 2057 // only one parameter. 2058 private int _widgetsHeight = 20; 2059 2060 // The number of horizontal characters in a text box. 2061 private int _width = DEFAULT_ENTRY_WIDTH; 2062 2063 /////////////////////////////////////////////////////////////////// 2064 //// inner classes //// 2065 2066 /** Listener for "line" and radio button entries. 2067 */ 2068 public static class QueryActionListener implements ActionListener { 2069 /** Construct a listener for line and radio button entries. 2070 * @param owner The owner query 2071 * @param name The name of the query 2072 */ 2073 public QueryActionListener(Query owner, String name) { 2074 _name = name; 2075 _owner = owner; 2076 } 2077 2078 /** Call all registered QueryListeners. 2079 * @param event The event, ignored in this method. 2080 */ 2081 @Override 2082 public void actionPerformed(ActionEvent event) { 2083 _owner._notifyListeners(_name); 2084 } 2085 2086 private Query _owner; 2087 2088 private String _name; 2089 } 2090 2091 /** Panel containing an entry box and button that opens a color chooser. 2092 */ 2093 public static class QueryColorChooser extends Box 2094 implements ActionListener { 2095 /** Create a panel containing an entry box and a color chooser. 2096 * @param owner The owner query 2097 * @param name The name of the query 2098 * @param defaultColor The initial default color of the color chooser. 2099 */ 2100 public QueryColorChooser(Query owner, String name, 2101 String defaultColor) { 2102 super(BoxLayout.X_AXIS); 2103 _owner = owner; 2104 //_defaultColor = defaultColor; 2105 _entryBox = new JTextField(defaultColor, _owner.getTextWidth()); 2106 2107 _button = new JButton("Choose"); 2108 _button.addActionListener(this); 2109 add(_entryBox); 2110 add(_button); 2111 2112 // Add the listener last so that there is no notification 2113 // of the first value. 2114 _entryBox.addActionListener(new QueryActionListener(_owner, name)); 2115 2116 // Add a listener for loss of focus. When the entry gains 2117 // and then loses focus, listeners are notified of an update, 2118 // but only if the value has changed since the last notification. 2119 // FIXME: Unfortunately, Java calls this listener some random 2120 // time after the window has been closed. It is not even a 2121 // a queued event when the window is closed. Thus, we have 2122 // a subtle bug where if you enter a value in a line, do not 2123 // hit return, and then click on the X to close the window, 2124 // the value is restored to the original, and then sometime 2125 // later, the focus is lost and the entered value becomes 2126 // the value of the parameter. I don't know of any workaround. 2127 _entryBox.addFocusListener(new QueryFocusListener(_owner, name)); 2128 2129 _name = name; 2130 } 2131 2132 @Override 2133 public void actionPerformed(ActionEvent e) { 2134 // Read the current color from the text field. 2135 String spec = getSelectedColor().trim(); 2136 Color newColor = JColorChooser.showDialog(_owner, "Choose Color", 2137 stringToColor(spec)); 2138 2139 if (newColor != null) { 2140 float[] components = newColor.getRGBComponents(null); 2141 StringBuffer string = new StringBuffer("{"); 2142 2143 // Use the syntax of arrays. 2144 for (int j = 0; j < components.length; j++) { 2145 string.append(components[j]); 2146 2147 if (j < components.length - 1) { 2148 string.append(","); 2149 } else { 2150 string.append("}"); 2151 } 2152 } 2153 2154 _entryBox.setText(string.toString()); 2155 _owner._notifyListeners(_name); 2156 } 2157 } 2158 2159 /** Get the selected color name. 2160 * @return the value of the text in the entry box. 2161 */ 2162 public String getSelectedColor() { 2163 return _entryBox.getText(); 2164 } 2165 2166 /** Set selected color name. 2167 * @param name The value of the text in the entry box. 2168 */ 2169 public void setColor(String name) { 2170 _entryBox.setText(name); 2171 } 2172 2173 /** Specify whether the entry is editable or not. 2174 * @param enabled False to disable changing the value. 2175 */ 2176 @Override 2177 public void setEnabled(boolean enabled) { 2178 _entryBox.setEditable(enabled); 2179 _button.setEnabled(enabled); 2180 } 2181 2182 private JButton _button; 2183 2184 private JTextField _entryBox; 2185 2186 private String _name; 2187 2188 private Query _owner; 2189 2190 //private String _defaultColor; 2191 } 2192 2193 /** Panel containing an entry box and file chooser. 2194 * 2195 */ 2196 public/*static*/class QueryFileChooser extends Box 2197 implements ActionListener { 2198 // This class cannot be static because the FileDialog needs to be owned 2199 // by the parent Query. 2200 2201 /** Construct a query file chooser. The background will be white and 2202 * the foreground will be black. 2203 * @param owner The query object that owns the file chooser 2204 * @param name The name of the query object 2205 * @param defaultName The default name that appears in the text field 2206 * @param base The base for this query file chooser 2207 * @param startingDirectory the directory in which the file query is initially opened 2208 * @param allowFiles true if files are allowed to be selected. 2209 * @param allowDirectories if directories are allowed to be selected. 2210 */ 2211 public QueryFileChooser(Query owner, String name, String defaultName, 2212 URI base, File startingDirectory, boolean allowFiles, 2213 boolean allowDirectories) { 2214 this(owner, name, defaultName, base, startingDirectory, allowFiles, 2215 allowDirectories, false, Color.white, Color.black); 2216 } 2217 2218 /** Construct a query file chooser. 2219 * @param owner The query object that owns the file chooser 2220 * @param name The name of the query object 2221 * @param defaultName The default name that appears in the text field 2222 * @param base The base for this query file chooser 2223 * @param startingDirectory the directory in which the file query is initially opened 2224 * @param allowFiles true if files are allowed to be selected. 2225 * @param allowDirectories if directories are allowed to be selected. 2226 * @param save Whether the file is to be saved or opened. 2227 * @param background The color of the background. 2228 * @param foreground The color of the foreground. 2229 */ 2230 public QueryFileChooser(Query owner, String name, String defaultName, 2231 URI base, File startingDirectory, boolean allowFiles, 2232 boolean allowDirectories, boolean save, Color background, 2233 Color foreground) { 2234 this(owner, name, defaultName, base, startingDirectory, allowFiles, 2235 allowDirectories, save, background, foreground, null); 2236 } 2237 2238 /** Construct a query file chooser. 2239 * @param owner The query object that owns the file chooser 2240 * @param name The name of the query object 2241 * @param defaultName The default name that appears in the text field 2242 * @param base The base for this query file chooser 2243 * @param startingDirectory the directory in which the file query is initially opened 2244 * @param allowFiles true if files are allowed to be selected. 2245 * @param allowDirectories if directories are allowed to be selected. 2246 * @param save Whether the file is to be saved or opened. 2247 * @param background The color of the background. 2248 * @param foreground The color of the foreground. 2249 * @param filter A filter for filenames, or null to not give one. 2250 */ 2251 public QueryFileChooser(Query owner, String name, String defaultName, 2252 URI base, File startingDirectory, boolean allowFiles, 2253 boolean allowDirectories, boolean save, Color background, 2254 Color foreground, FilenameFilter filter) { 2255 super(BoxLayout.X_AXIS); 2256 _owner = owner; 2257 _base = base; 2258 _save = save; 2259 _startingDirectory = startingDirectory; 2260 2261 if (!allowFiles && !allowDirectories) { 2262 throw new IllegalArgumentException( 2263 "QueryFileChooser: nothing to be chosen."); 2264 } 2265 2266 _allowFiles = allowFiles; 2267 _allowDirectories = allowDirectories; 2268 _entryBox = new JTextField(defaultName, _owner.getTextWidth()); 2269 _entryBox.setBackground(background); 2270 _entryBox.setForeground(foreground); 2271 2272 _button = new JButton("Browse "); 2273 _button.addActionListener(this); 2274 add(_entryBox); 2275 add(_button); 2276 2277 // Add the listener last so that there is no notification 2278 // of the first value. 2279 _entryBox.addActionListener(new QueryActionListener(_owner, name)); 2280 2281 // Add a listener for loss of focus. When the entry gains 2282 // and then loses focus, listeners are notified of an update, 2283 // but only if the value has changed since the last notification. 2284 // FIXME: Unfortunately, Java calls this listener some random 2285 // time after the window has been closed. It is not even a 2286 // a queued event when the window is closed. Thus, we have 2287 // a subtle bug where if you enter a value in a line, do not 2288 // hit return, and then click on the X to close the window, 2289 // the value is restored to the original, and then sometime 2290 // later, the focus is lost and the entered value becomes 2291 // the value of the parameter. I don't know of any workaround. 2292 _entryBox.addFocusListener(new QueryFocusListener(_owner, name)); 2293 2294 _name = name; 2295 2296 _filter = filter; 2297 } 2298 2299 /** Create a file browser dialog and get the user input. If 2300 * {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} returns 2301 * true, then {@link #_actionPerformedFileDialog(ActionEvent)} uses 2302 * this method. Otherwise, {@link #_actionPerformedJFileChooser(ActionEvent)} 2303 * is used. 2304 */ 2305 @Override 2306 public void actionPerformed(ActionEvent event) { 2307 if (PtGUIUtilities.useFileDialog()) { 2308 _actionPerformedFileDialog(event); 2309 } else { 2310 _actionPerformedJFileChooser(event); 2311 } 2312 } 2313 2314 /** Get the selected file name. 2315 * @return the value of the text in the entry box. 2316 */ 2317 public String getSelectedFileName() { 2318 return _entryBox.getText(); 2319 } 2320 2321 /** Specify whether the entry is editable or not. 2322 * @param enabled False to disable changing the value. 2323 */ 2324 @Override 2325 public void setEnabled(boolean enabled) { 2326 _entryBox.setEditable(enabled); 2327 _button.setEnabled(enabled); 2328 } 2329 2330 /** Set selected file name. 2331 * @param name The value of the text in the entry box. 2332 */ 2333 public void setFileName(String name) { 2334 _entryBox.setText(name); 2335 } 2336 2337 /** Create a java.awt.FileDialog and get the user input. If 2338 * {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} returns 2339 * true, then {@link #actionPerformed(ActionEvent)} uses this method. 2340 * Otherwise, {@link #_actionPerformedJFileChooser(ActionEvent)} is used. 2341 * <p>Under Mac OS X, this method is preferred over 2342 * _actionPerformedJFileChooser().</p> 2343 * 2344 * <p>Under Bash, to test this method, use:</p> 2345 * <pre> 2346 * export JAVAFLAGS=-Dptolemy.ptII.useFileDialog=true 2347 * $PTII/bin/vergil ~/ptII/ptolemy/actor/lib/io/test/auto/FileReader.xml 2348 * </pre> 2349 */ 2350 private void _actionPerformedFileDialog(ActionEvent e) { 2351 FileDialog fileDialog = new FileDialog( 2352 JOptionPane.getFrameForComponent(Query.this), "Select", 2353 FileDialog.LOAD); 2354 if (_startingDirectory != null) { 2355 fileDialog.setDirectory(_startingDirectory.toString()); 2356 } 2357 2358 String fileName = getSelectedFileName().trim(); 2359 if (!fileName.equals("")) { 2360 fileDialog.setFile(fileName); 2361 } 2362 2363 try { 2364 // Use a system property to determine whether to allow choosing 2365 // directories only or both directories and files. 2366 // FIXME: This doesn't really do the right thing. 2367 // If _allowDirectories is set, then you cannot select files! 2368 // However, this is the way Apple intended this mechanism to be used. 2369 // Cfr https://developer.apple.com/library/mac/documentation/Java/Reference/Java_PropertiesRef/Articles/JavaSystemProperties.html 2370 2371 // Also, this seems like a poor mechanism, since it sets a property 2372 // that affects anything running in this JVM! 2373 if (_allowDirectories) { 2374 System.setProperty("apple.awt.fileDialogForDirectories", 2375 "true"); 2376 } else { 2377 System.setProperty("apple.awt.fileDialogForDirectories", 2378 "false"); 2379 } 2380 2381 if (_filter != null) { 2382 fileDialog.setFilenameFilter(_filter); 2383 } 2384 2385 fileDialog.show(); 2386 } finally { 2387 // Revert to the default, which is to allow the selection of files. 2388 System.setProperty("apple.awt.fileDialogForDirectories", 2389 "false"); 2390 } 2391 2392 if (fileDialog.getFile() == null) { 2393 return; 2394 } 2395 2396 File file = null; 2397 2398 // FIXME: should we set both of these? 2399 _startingDirectory = new File(fileDialog.getDirectory()); 2400 //_base = new File(fileDialog.getDirectory()).toURI(); 2401 if (_startingDirectory != null) { 2402 file = new File(_startingDirectory, fileDialog.getFile()); 2403 } else { 2404 String currentWorkingDirectory = null; 2405 try { 2406 currentWorkingDirectory = System.getProperty("user.dir"); 2407 } catch (Throwable throwable) { 2408 // Ignore, we are probably in an applet. 2409 } 2410 if (currentWorkingDirectory != null) { 2411 file = new File(currentWorkingDirectory, 2412 fileDialog.getFile()); 2413 } else { 2414 file = new File(fileDialog.getFile()); 2415 } 2416 } 2417 2418 if (file.exists() && fileDialog.getMode() == FileDialog.SAVE) { 2419 // Ask for confirmation before overwriting a file. 2420 String queryString = file.getName() 2421 + " already exists. Overwrite?"; 2422 int selected = JOptionPane.showOptionDialog(null, queryString, 2423 "Confirm save", JOptionPane.YES_NO_OPTION, 2424 JOptionPane.QUESTION_MESSAGE, null, null, null); 2425 if (selected == 1) { 2426 return; 2427 } 2428 } 2429 2430 // FIXME: lots of duplicated code here. Consider creating 2431 // a method that takes a File argument? 2432 if (_base == null) { 2433 // Absolute file name. 2434 try { 2435 _entryBox.setText(new File(fileDialog.getDirectory(), 2436 fileDialog.getFile()).getCanonicalPath()); 2437 } catch (IOException ex) { 2438 // If we can't get a path, then just use the name. 2439 _entryBox.setText(fileDialog.getFile()); 2440 } 2441 } else { 2442 // Relative file name. 2443 File selectedFile = new File(fileDialog.getDirectory(), 2444 fileDialog.getFile()); 2445 2446 // FIXME: There is a bug here under Windows XP 2447 // at least... Sometimes, the drive ID (like c:) 2448 // is lower case, and sometimes it's upper case. 2449 // When we open a MoML file, it's upper case. 2450 // When we do "save as", it's lower case. 2451 // This despite the fact that both use the same 2452 // file browser to determine the file name. 2453 // Beats me... Consequence is that if you save as, 2454 // then the following relativize call doesn't work 2455 // until you close and reopen the file. 2456 try { 2457 selectedFile = selectedFile.getCanonicalFile(); 2458 } catch (IOException ex) { 2459 // Ignore, since we can't do much about it anyway. 2460 } 2461 2462 URI relativeURI = _base.relativize(selectedFile.toURI()); 2463 if (relativeURI != null && relativeURI.getScheme() != null 2464 && relativeURI.getScheme().equals("file")) { 2465 // Fix for "undesired file:\ prefix added by FileParameter" 2466 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4022 2467 String pathName = relativeURI.getPath(); 2468 // Sigh. Under Windows, getPath() returns a leading / 2469 file = new File(pathName.replace("%20", " ")); 2470 try { 2471 _entryBox.setText( 2472 file.getCanonicalPath().replace('\\', '/')); 2473 } catch (IOException ex) { 2474 _entryBox.setText(file.toString()); 2475 } 2476 } else { 2477 _entryBox.setText(relativeURI.toString()); 2478 } 2479 } 2480 _owner._notifyListeners(_name); 2481 } 2482 2483 /** Create a javax.swing.JFileChooser and get the user input. 2484 * If {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} returns false, 2485 * then {@link #actionPerformed(ActionEvent)} uses this method. Otherwise, 2486 * {@link #_actionPerformedFileDialog(ActionEvent)} is used. 2487 2488 * <p>Under Bash, to test this method, use:</p> 2489 * <pre> 2490 * export JAVAFLAGS=-Dptolemy.ptII.useFileDialog=false 2491 * $PTII/bin/vergil ~/ptII/ptolemy/actor/lib/io/test/auto/FileReader.xml 2492 * </pre> 2493 */ 2494 private void _actionPerformedJFileChooser(ActionEvent e) { 2495 // Swap backgrounds and avoid white boxes in "common places" dialog 2496 JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix(); 2497 Color background = null; 2498 try { 2499 background = jFileChooserBugFix.saveBackground(); 2500 // NOTE: If the last argument is null, then choose a 2501 // default dir. 2502 JFileChooser fileChooser = new JFileChooser( 2503 _startingDirectory) { 2504 @Override 2505 public void approveSelection() { 2506 File file = getSelectedFile(); 2507 if (file.exists() && getDialogType() == SAVE_DIALOG) { 2508 String queryString = file.getName() 2509 + " already exists. Overwrite?"; 2510 int selected = JOptionPane.showOptionDialog(null, 2511 queryString, "Confirm save", 2512 JOptionPane.YES_NO_OPTION, 2513 JOptionPane.QUESTION_MESSAGE, null, null, 2514 null); 2515 if (selected == 0) { 2516 super.approveSelection(); 2517 } 2518 } else { 2519 super.approveSelection(); 2520 } 2521 } 2522 }; 2523 String fileName = getSelectedFileName().trim(); 2524 if (!fileName.equals("")) { 2525 fileChooser.setSelectedFile(new File(fileName)); 2526 } 2527 fileChooser.setApproveButtonText("Select"); 2528 2529 // FIXME: The following doesn't have any effect. 2530 fileChooser.setApproveButtonMnemonic('S'); 2531 2532 if (_allowFiles && _allowDirectories) { 2533 fileChooser.setFileSelectionMode( 2534 JFileChooser.FILES_AND_DIRECTORIES); 2535 } else if (_allowFiles && !_allowDirectories) { 2536 // This is the default. 2537 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 2538 } else if (!_allowFiles && _allowDirectories) { 2539 fileChooser.setFileSelectionMode( 2540 JFileChooser.DIRECTORIES_ONLY); 2541 } else { 2542 // Usually, we would use InternalErrorException 2543 // here, but if we do, then this package would 2544 // depend on kernel.util, which causes problems 2545 // when we ship Ptplot. 2546 throw new RuntimeException( 2547 "QueryFileChooser: nothing to be chosen."); 2548 } 2549 2550 int returnValue = _save ? fileChooser.showSaveDialog(_owner) 2551 : fileChooser.showOpenDialog(_owner); 2552 2553 if (returnValue == JFileChooser.APPROVE_OPTION) { 2554 if (_base == null) { 2555 // Absolute file name. 2556 try { 2557 _entryBox.setText(fileChooser.getSelectedFile() 2558 .getCanonicalPath()); 2559 } catch (IOException ex) { 2560 // If we can't get a path, then just use the name. 2561 _entryBox.setText( 2562 fileChooser.getSelectedFile().getName()); 2563 } 2564 } else { 2565 // Relative file name. 2566 File selectedFile = fileChooser.getSelectedFile(); 2567 2568 // FIXME: There is a bug here under Windows XP 2569 // at least... Sometimes, the drive ID (like c:) 2570 // is lower case, and sometimes it's upper case. 2571 // When we open a MoML file, it's upper case. 2572 // When we do "save as", it's lower case. 2573 // This despite the fact that both use the same 2574 // file browser to determine the file name. 2575 // Beats me... Consequence is that if you save as, 2576 // then the following relativize call doesn't work 2577 // until you close and reopen the file. 2578 try { 2579 selectedFile = selectedFile.getCanonicalFile(); 2580 } catch (IOException ex) { 2581 // Ignore, since we can't do much about it anyway. 2582 } 2583 2584 URI relativeURI = _base 2585 .relativize(selectedFile.toURI()); 2586 if (relativeURI != null 2587 && relativeURI.getScheme() != null 2588 && relativeURI.getScheme().equals("file")) { 2589 // Fix for "undesired file:\ prefix added by FileParameter" 2590 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4022 2591 String pathName = relativeURI.getPath(); 2592 // Sigh. Under Windows, getPath() returns a leading / 2593 File file = new File(pathName.replace("%20", " ")); 2594 try { 2595 _entryBox.setText(file.getCanonicalPath() 2596 .replace('\\', '/')); 2597 } catch (IOException ex) { 2598 _entryBox.setText(file.toString()); 2599 } 2600 } else { 2601 _entryBox.setText(relativeURI.toString()); 2602 } 2603 } 2604 2605 _owner._notifyListeners(_name); 2606 } 2607 } finally { 2608 jFileChooserBugFix.restoreBackground(background); 2609 } 2610 } 2611 2612 private Query _owner; 2613 2614 private URI _base; 2615 2616 private JButton _button; 2617 2618 private JTextField _entryBox; 2619 2620 private String _name; 2621 2622 private boolean _save; 2623 2624 private File _startingDirectory; 2625 2626 private boolean _allowFiles; 2627 2628 private boolean _allowDirectories; 2629 2630 private FilenameFilter _filter; 2631 } 2632 2633 /** Listener for line entries, for when they lose the focus. 2634 */ 2635 public static class QueryFocusListener implements FocusListener { 2636 /** Construct a listener for when line entries lose the focus. 2637 * @param owner The query object that owns the file chooser 2638 * @param name The name of the query object 2639 */ 2640 public QueryFocusListener(Query owner, String name) { 2641 _name = name; 2642 _owner = owner; 2643 } 2644 2645 @Override 2646 public void focusGained(FocusEvent e) { 2647 // Nothing to do. 2648 } 2649 2650 @Override 2651 public void focusLost(FocusEvent e) { 2652 // NOTE: Java's lame AWT has no reliable way 2653 // to take action on window closing, so this focus lost 2654 // notification is the only reliable way we have of reacting 2655 // to a closing window. If the previous 2656 // notification was an erroneous one and the value has not 2657 // changed, then no further notification occurs. 2658 // This could be a problem for some users of this class. 2659 _owner._notifyListeners(_name); 2660 } 2661 2662 private Query _owner; 2663 2664 private String _name; 2665 } 2666 2667 /** Listener for "CheckBox" and "Choice" entries. 2668 */ 2669 public static class QueryItemListener implements ItemListener { 2670 /** Create a listener for the CheckBox and Choice entries. 2671 * @param owner The owner query 2672 * @param name The name of the query 2673 */ 2674 public QueryItemListener(Query owner, String name) { 2675 _name = name; 2676 _owner = owner; 2677 } 2678 2679 /** Call all registered QueryListeners. */ 2680 @Override 2681 public void itemStateChanged(ItemEvent e) { 2682 _owner._notifyListeners(_name); 2683 } 2684 2685 private Query _owner; 2686 2687 private String _name; 2688 } 2689 2690 /** Inner class to tie textArea to scroll pane. */ 2691 static class QueryScrollPane extends JScrollPane { 2692 // FindBugs suggests making this class static so as to decrease 2693 // the size of instances and avoid dangling references. 2694 2695 public JTextArea textArea; 2696 2697 QueryScrollPane(JTextArea c) { 2698 super(c); 2699 textArea = c; 2700 2701 // Set the undo listener 2702 textArea.getDocument() 2703 .addUndoableEditListener(new UndoListener(textArea)); 2704 2705 } 2706 2707 @Override 2708 public JScrollBar getHorizontalScrollBar() { 2709 // If the user types in lots of characters, eventually 2710 // create a scrollbar and make the textArea bigger. Note 2711 // that 2712 // http://docs.oracle.com/javase/tutorial/uiswing/components/textarea.html 2713 // says: "If you need to obtain only one line of input 2714 // from the user, you should use a text field." 2715 2716 JScrollBar scrollBar = super.getHorizontalScrollBar(); 2717 if (scrollBar != null) { 2718 if (scrollBar.isDisplayable() && scrollBar.getHeight() > 0) { 2719 Query._textAreaSetRowsAndRepackParent(textArea, 2); 2720 } 2721 } 2722 return scrollBar; 2723 } 2724 2725 @Override 2726 public Dimension getPreferredSize() { 2727 // For another possible solution, see 2728 // http://stackoverflow.com/questions/9370561/enabling-scroll-bars-when-jtextarea-exceeds-certain-amount-of-lines 2729 2730 // If we have a TextArea, display as many parameters as we 2731 // can of the TextArea before adding a scrollbar for all 2732 // the parameters. 2733 if (textArea.getLineCount() > 1) { 2734 return textArea.getPreferredSize(); 2735 } else { 2736 return super.getPreferredSize(); 2737 } 2738 } 2739 2740 public String getText() { 2741 String retval = textArea.getText(); 2742 return retval; 2743 } 2744 2745 public void setText(String s) { 2746 textArea.setText(s); 2747 } 2748 } 2749 2750 /** Listener for changes in slider. 2751 */ 2752 public static class SliderListener implements ChangeListener { 2753 /** Construct a listener for changes in a slider. 2754 * @param owner The query object that owns slider. 2755 * @param name The name of the query object 2756 */ 2757 public SliderListener(Query owner, String name) { 2758 _name = name; 2759 _owner = owner; 2760 } 2761 2762 /** Call all registered QueryListeners. */ 2763 @Override 2764 public void stateChanged(ChangeEvent event) { 2765 _owner._notifyListeners(_name); 2766 } 2767 2768 private Query _owner; 2769 2770 private String _name; 2771 } 2772}