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}