PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/stable/src/ptolemy/gui/Query.java

http://j-sim.googlecode.com/
Java | 973 lines | 495 code | 67 blank | 411 comment | 107 complexity | 280e93a4ceada62feb088dc69c68d2df MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /* Query dialog.
  2. Copyright (c) 1998-2001 The Regents of the University of California.
  3. All rights reserved.
  4. Permission is hereby granted, without written agreement and without
  5. license or royalty fees, to use, copy, modify, and distribute this
  6. software and its documentation for any purpose, provided that the above
  7. copyright notice and the following two paragraphs appear in all copies
  8. of this software.
  9. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
  10. FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
  11. ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
  12. THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
  13. SUCH DAMAGE.
  14. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
  15. INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  16. MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
  17. PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
  18. CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
  19. ENHANCEMENTS, OR MODIFICATIONS.
  20. PT_COPYRIGHT_VERSION_2
  21. COPYRIGHTENDKEY
  22. @ProposedRating Yellow (eal@eecs.berkeley.edu)
  23. @AcceptedRating Red (eal@eecs.berkeley.edu)
  24. */
  25. package ptolemy.gui;
  26. import java.awt.*;
  27. import java.awt.event.*;
  28. import java.util.*;
  29. import java.lang.IllegalArgumentException;
  30. import javax.swing.*;
  31. import javax.swing.event.*;
  32. import javax.swing.event.ChangeListener;
  33. //////////////////////////////////////////////////////////////////////////
  34. //// Query
  35. /**
  36. Create a query with various types of entry boxes and controls. Each type
  37. of entry box has a colon and space appended to the end of its label, to
  38. ensure uniformity.
  39. Here is one example of creating a query with a radio button:
  40. <pre>
  41. query = new Query();
  42. getContentPane().add(query);
  43. String[] options = {"water", "soda", "juice", "none"};
  44. query.addRadioButtons("radio", "Radio buttons", options, "water");
  45. </pre>
  46. @author Edward A. Lee, Manda Sutijono
  47. @version $Id: Query.java,v 1.1.1.1 2004/01/26 21:52:02 hyuklim Exp $
  48. */
  49. public class Query extends JPanel {
  50. /** Construct a panel with no entries in it.
  51. */
  52. public Query() {
  53. _grid = new GridBagLayout();
  54. _constraints = new GridBagConstraints();
  55. _constraints.fill = GridBagConstraints.HORIZONTAL;
  56. _constraints.weightx = 1.0;
  57. _constraints.anchor = GridBagConstraints.NORTHWEST;
  58. _entryPanel.setLayout(_grid);
  59. // It's not clear whether the following has any real significance...
  60. // _entryPanel.setOpaque(true);
  61. setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
  62. // Left Justify.
  63. _entryPanel.setAlignmentX(0.0f);
  64. _messagePanel.setAlignmentX(0.0f);
  65. // Add a message panel into which a message can be placed using
  66. // setMessage().
  67. add(_messagePanel);
  68. add(_entryPanel);
  69. // Setting the background to null allegedly means it inherits the
  70. // background color from the container.
  71. _entryPanel.setBackground(null);
  72. _messagePanel.setBackground(null);
  73. }
  74. ///////////////////////////////////////////////////////////////////
  75. //// public methods ////
  76. /** Create an on-off check box.
  77. * @param name The name used to identify the entry (when calling get).
  78. * @param label The label to attach to the entry.
  79. * @param defaultValue The default value (true for on).
  80. */
  81. public void addCheckBox(String name, String label, boolean defaultValue) {
  82. JLabel lbl = new JLabel(label + ": ");
  83. lbl.setBackground(_background);
  84. JRadioButton checkbox = new JRadioButton();
  85. checkbox.setBackground(_background);
  86. checkbox.setOpaque(false);
  87. checkbox.setSelected(defaultValue);
  88. _addPair(name, lbl, checkbox, checkbox);
  89. // Add the listener last so that there is no notification
  90. // of the first value.
  91. checkbox.addItemListener(new QueryItemListener(name));
  92. }
  93. /** Create an uneditable choice menu.
  94. * @param name The name used to identify the entry (when calling get).
  95. * @param label The label to attach to the entry.
  96. * @param values The list of possible choices.
  97. * @param defaultChoice Default choice.
  98. */
  99. public void addChoice(String name, String label,
  100. String[] values, String defaultChoice) {
  101. addChoice(name, label, values, defaultChoice, false);
  102. }
  103. /** Create a choice menu.
  104. * @param name The name used to identify the entry (when calling get).
  105. * @param label The label to attach to the entry.
  106. * @param values The list of possible choices.
  107. * @param defaultChoice Default choice.
  108. * @param editable True if an arbitrary choice can be entered, in addition
  109. * to the choices in values.
  110. */
  111. public void addChoice(String name, String label,
  112. String[] values, String defaultChoice, boolean editable) {
  113. JLabel lbl = new JLabel(label + ": ");
  114. lbl.setBackground(_background);
  115. JComboBox combobox = new JComboBox(values);
  116. combobox.setEditable(editable);
  117. combobox.setBackground(Color.white);
  118. combobox.setSelectedItem(defaultChoice);
  119. _addPair(name, lbl, combobox, combobox);
  120. // Add the listener last so that there is no notification
  121. // of the first value.
  122. combobox.addItemListener(new QueryItemListener(name));
  123. }
  124. /** Create a simple one-line text display, a non-editable value that
  125. * is set externally using the setDisplay() method.
  126. * @param name The name used to identify the entry (when calling get).
  127. * @param label The label to attach to the entry.
  128. * @param theValue Default string to display.
  129. */
  130. public void addDisplay(String name, String label, String theValue) {
  131. JLabel lbl = new JLabel(label + ": ");
  132. lbl.setBackground(_background);
  133. // NOTE: JLabel would be a reasonable choice here, but at
  134. // least in the current version of swing, JLabel.setText() does
  135. // not work.
  136. JTextArea displayField = new JTextArea(theValue, 1, 10);
  137. displayField.setEditable(false);
  138. displayField.setBackground(_background);
  139. _addPair(name, lbl, displayField, displayField);
  140. }
  141. /** Create a single-line entry box with the specified name, label, and
  142. * default value. To control the width of the box, call setTextWidth()
  143. * first.
  144. * @param name The name used to identify the entry (when accessing
  145. * the entry).
  146. * @param label The label to attach to the entry.
  147. * @param defaultValue Default value to appear in the entry box.
  148. */
  149. public void addLine(String name, String label, String defaultValue) {
  150. JLabel lbl = new JLabel(label + ": ");
  151. lbl.setBackground(_background);
  152. JTextField entryBox = new JTextField(defaultValue, _width);
  153. entryBox.setBackground(Color.white);
  154. _addPair(name, lbl, entryBox, entryBox);
  155. // Add the listener last so that there is no notification
  156. // of the first value.
  157. entryBox.addActionListener(new QueryActionListener(name));
  158. // Add a listener for loss of focus. When the entry gains
  159. // and then loses focus, listeners are notified of an update,
  160. // but only if the value has changed since the last notification.
  161. // NOTE: Unfortunately, Java calls this listener some random
  162. // time after the window has been closed. It is not even a
  163. // a queued event when the window is closed. Thus, we have
  164. // a subtle bug where if you enter a value in a line, do not
  165. // hit return, and then click on the X to close the window,
  166. // the value is restored to the original, and then sometime
  167. // later, the focus is lost and the entered value becomes
  168. // the value of the parameter. I don't know of any workaround.
  169. entryBox.addFocusListener(new QueryFocusListener(name));
  170. }
  171. /** Add a listener. The changed() method of the listener will be
  172. * called when any of the entries is changed. Note that "line"
  173. * entries only trigger this call when Return or Enter is pressed, or
  174. * when the entry gains and then loses the keyboard focus.
  175. * Notice that the currently selected line loses focus when the
  176. * panel is destroyed, so notification of any changes that
  177. * have been made will be done at that time. That notification
  178. * will occur in the UI thread, and may be later than expected.
  179. * Notification due to loss of focus only occurs if the value
  180. * of the entry has changed since the last notification.
  181. * If the listener has already been added, then do nothing.
  182. * @param listener The listener to add.
  183. */
  184. public void addQueryListener(QueryListener listener) {
  185. if(_listeners == null) _listeners = new Vector();
  186. if(_listeners.contains(listener)) return;
  187. _listeners.add(listener);
  188. }
  189. /** Create a bank of radio buttons. A radio button provides a list of
  190. * choices, only one of which may be chosen at a time.
  191. * @param name The name used to identify the entry (when calling get).
  192. * @param label The label to attach to the entry.
  193. * @param values The list of possible choices.
  194. * @param defaultValue Default value.
  195. */
  196. public void addRadioButtons(String name, String label,
  197. String[] values, String defaultValue) {
  198. JLabel lbl = new JLabel(label + ": ");
  199. lbl.setBackground(_background);
  200. FlowLayout flow = new FlowLayout();
  201. flow.setAlignment(FlowLayout.LEFT);
  202. Panel buttonPanel = new Panel(flow);
  203. ButtonGroup group = new ButtonGroup();
  204. QueryActionListener listener = new QueryActionListener(name);
  205. // Regrettably, ButtonGroup provides no method to find out
  206. // which button is selected, so we have to go through a
  207. // song and dance here...
  208. JRadioButton[] buttons = new JRadioButton[values.length];
  209. for (int i = 0; i < values.length; i++) {
  210. JRadioButton checkbox = new JRadioButton(values[i]);
  211. buttons[i] = checkbox;
  212. checkbox.setBackground(_background);
  213. // The following (essentially) undocumented method does nothing...
  214. // checkbox.setContentAreaFilled(true);
  215. checkbox.setOpaque(false);
  216. if (values[i].equals(defaultValue)) {
  217. checkbox.setSelected(true);
  218. }
  219. group.add(checkbox);
  220. buttonPanel.add(checkbox);
  221. // Add the listener last so that there is no notification
  222. // of the first value.
  223. checkbox.addActionListener(listener);
  224. }
  225. _addPair(name, lbl, buttonPanel, buttons);
  226. }
  227. /** Create a bank of buttons that provides a list of
  228. * choices, any subset of which may be chosen at a time.
  229. * @param name The name used to identify the entry (when calling get).
  230. * @param label The label to attach to the entry.
  231. * @param values The list of possible choices.
  232. * @param initiallySelected The initally selected choices, or null
  233. * to indicate that none are selected.
  234. */
  235. public void addSelectButtons(String name, String label,
  236. String[] values, Set initiallySelected) {
  237. JLabel lbl = new JLabel(label + ": ");
  238. lbl.setBackground(_background);
  239. FlowLayout flow = new FlowLayout();
  240. flow.setAlignment(FlowLayout.LEFT);
  241. Panel buttonPanel = new Panel(flow);
  242. QueryActionListener listener = new QueryActionListener(name);
  243. if (initiallySelected == null) {
  244. initiallySelected = new HashSet();
  245. }
  246. JRadioButton[] buttons = new JRadioButton[values.length];
  247. for (int i = 0; i < values.length; i++) {
  248. JRadioButton checkbox = new JRadioButton(values[i]);
  249. buttons[i] = checkbox;
  250. checkbox.setBackground(_background);
  251. // The following (essentially) undocumented method does nothing...
  252. // checkbox.setContentAreaFilled(true);
  253. checkbox.setOpaque(false);
  254. if (initiallySelected.contains(values[i])) {
  255. checkbox.setSelected(true);
  256. }
  257. buttonPanel.add(checkbox);
  258. // Add the listener last so that there is no notification
  259. // of the first value.
  260. checkbox.addActionListener(listener);
  261. }
  262. _addPair(name, lbl, buttonPanel, buttons);
  263. }
  264. /** Create a slider with the specified name, label, default
  265. * value, maximum, and minimum.
  266. * @param name The name used to identify the slider.
  267. * @param label The label to attach to the slider.
  268. * @param defaultValue Initial position of slider.
  269. * @param maximum Maximum value of slider.
  270. * @param minimum Minimum value of slider.
  271. * @exception IllegalArgumentException If the desired default value
  272. * is not between the minimum and maximum.
  273. */
  274. public void addSlider(String name, String label, int defaultValue,
  275. int minimum, int maximum)
  276. throws IllegalArgumentException {
  277. JLabel lbl = new JLabel(label + ": ");
  278. if (minimum > maximum) {
  279. int temp = minimum;
  280. minimum = maximum;
  281. maximum = temp;
  282. }
  283. if ((defaultValue > maximum) || (defaultValue < minimum)) {
  284. throw new IllegalArgumentException("Desired default " +
  285. "value \"" + defaultValue + "\" does not fall " +
  286. "between the minimum and maximum.");
  287. }
  288. JSlider slider = new JSlider(minimum, maximum, defaultValue);
  289. _addPair(name, lbl, slider, slider);
  290. slider.addChangeListener(new SliderListener(name));
  291. }
  292. /** Get the current value in the entry with the given name
  293. * and return as a boolean. If the entry is not a checkbox,
  294. * then throw an exception.
  295. * @return The state of the checkbox.
  296. * @exception NoSuchElementException If there is no item with the
  297. * specified name. Note that this is a runtime exception, so it
  298. * need not be declared explicitly.
  299. * @exception IllegalArgumentException If the entry is not a
  300. * checkbox. This is a runtime exception, so it
  301. * need not be declared explicitly.
  302. */
  303. public boolean booleanValue(String name)
  304. throws NoSuchElementException, IllegalArgumentException {
  305. Object result = _entries.get(name);
  306. if(result == null) {
  307. throw new NoSuchElementException("No item named \"" +
  308. name + "\" in the query box.");
  309. }
  310. if (result instanceof JRadioButton) {
  311. return ((JRadioButton)result).isSelected();
  312. } else {
  313. throw new IllegalArgumentException("Item named \"" +
  314. name + "\" is not a radio button, and hence does not have "
  315. + "a boolean value.");
  316. }
  317. }
  318. /** Get the current value in the entry with the given name
  319. * and return as a double value. If the entry is not a line,
  320. * then throw an exception. If the value of the entry is not
  321. * a double, then throw an exception.
  322. * @return The value currently in the entry as a double.
  323. * @exception NoSuchElementException If there is no item with the
  324. * specified name. Note that this is a runtime exception, so it
  325. * need not be declared explicitly.
  326. * @exception NumberFormatException If the value of the entry cannot
  327. * be converted to a double. This is a runtime exception, so it
  328. * need not be declared explicitly.
  329. * @exception IllegalArgumentException If the entry is not a
  330. * line. This is a runtime exception, so it
  331. * need not be declared explicitly.
  332. */
  333. public double doubleValue(String name)
  334. throws IllegalArgumentException, NoSuchElementException,
  335. NumberFormatException {
  336. Object result = _entries.get(name);
  337. if(result == null) {
  338. throw new NoSuchElementException("No item named \"" +
  339. name + " \" in the query box.");
  340. }
  341. if (result instanceof JTextField) {
  342. return (new Double(((JTextField)result).getText())).doubleValue();
  343. } else {
  344. throw new IllegalArgumentException("Item named \"" +
  345. name + "\" is not a text line, and hence cannot be converted to "
  346. + "a double value.");
  347. }
  348. }
  349. /** Return the preferred size, since it usually does not make
  350. * sense to stretch a query box. Currently (JDK 1.3), only BoxLayout
  351. * pays any attention to this.
  352. * @return The maximum desired size.
  353. */
  354. public Dimension getMaximumSize() {
  355. return getPreferredSize();
  356. }
  357. /** Get the preferred width to be used for entry boxes created
  358. * in using addLine(). The preferred width is set using
  359. * setTextWidth.
  360. * @return The preferred width.
  361. */
  362. public int getTextWidth() {
  363. return _width;
  364. }
  365. /** Get the current value in the entry with the given name
  366. * and return as an integer. If the entry is not a line,
  367. * choice, or slider, then throw an exception.
  368. * If it is a choice or radio button, then return the
  369. * index of the first selected item.
  370. * @return The value currently in the entry as an integer.
  371. * @exception NoSuchElementException If there is no item with the
  372. * specified name. Note that this is a runtime exception, so it
  373. * need not be declared explicitly.
  374. * @exception NumberFormatException If the value of the entry cannot
  375. * be converted to an integer. This is a runtime exception, so it
  376. * need not be declared explicitly.
  377. * @exception IllegalArgumentException If the entry is not a
  378. * choice, line, or slider. This is a runtime exception, so it
  379. * need not be declared explicitly.
  380. */
  381. public int intValue(String name)
  382. throws IllegalArgumentException, NoSuchElementException,
  383. NumberFormatException {
  384. Object result = _entries.get(name);
  385. if(result == null) {
  386. throw new NoSuchElementException("No item named \"" +
  387. name + " \" in the query box.");
  388. }
  389. if (result instanceof JTextField) {
  390. return (new Integer(((JTextField)result).getText())).intValue();
  391. } else if (result instanceof JSlider) {
  392. return ((JSlider)result).getValue();
  393. } else if (result instanceof JComboBox) {
  394. return ((JComboBox)result).getSelectedIndex();
  395. } else if (result instanceof JRadioButton[]) {
  396. // Regrettably, ButtonGroup gives no way to determine
  397. // which button is selected, so we have to search...
  398. JRadioButton[] buttons = (JRadioButton[])result;
  399. for (int i = 0; i < buttons.length; i++) {
  400. if (buttons[i].isSelected()) {
  401. return i;
  402. }
  403. }
  404. // In theory, we shouldn't get here, but the compiler
  405. // is unhappy without a return.
  406. return -1;
  407. } else {
  408. throw new IllegalArgumentException("Item named \"" +
  409. name + "\" is not a text line or slider, and hence "
  410. + "cannot be converted to "
  411. + "an integer value.");
  412. }
  413. }
  414. /** Nofify listeners of the current value of all entries, unless
  415. * those entries have not changed since the last notification.
  416. */
  417. public void notifyListeners() {
  418. Iterator names = _entries.keySet().iterator();
  419. while(names.hasNext()) {
  420. String name = (String)names.next();
  421. _notifyListeners(name);
  422. }
  423. }
  424. /** Remove a listener. If the listener has not been added, then
  425. * do nothing.
  426. * @param listener The listener to remove.
  427. */
  428. public void removeQueryListener(QueryListener listener) {
  429. if(_listeners == null) return;
  430. _listeners.remove(listener);
  431. }
  432. /** Set the value in the entry with the given name.
  433. * The second argument must be a string that can be parsed to the
  434. * proper type for the given entry, or an exception is thrown.
  435. * Note that this does NOT trigger the notification of listeners, and
  436. * intended to allow a way to set the query to reflect the current state.
  437. * @param name The name used to identify the entry (when calling get).
  438. * @param value The value to set the entry to.
  439. * @exception NoSuchElementException If there is no item with the
  440. * specified name. Note that this is a runtime exception, so it
  441. * need not be declared explicitly.
  442. * @exception IllegalArgumentException If the value does not parse
  443. * to the appropriate type.
  444. */
  445. public void set(String name, String value)
  446. throws NoSuchElementException, IllegalArgumentException {
  447. Object result = _entries.get(name);
  448. if(result == null) {
  449. throw new NoSuchElementException("No item named \"" +
  450. name + " \" in the query box.");
  451. }
  452. // FIXME: Surely there is a better way to do this...
  453. // We should define a set of inner classes, one for each entry type.
  454. // Currently, this has to be updated each time a new entry type
  455. // is added.
  456. if (result instanceof JTextField) {
  457. ((JTextField)result).setText(value);
  458. } else if (result instanceof JTextArea) {
  459. ((JTextArea)result).setText(value);
  460. } else if (result instanceof JRadioButton) {
  461. Boolean flag = new Boolean(value);
  462. setBoolean(name, flag.booleanValue());
  463. } else if (result instanceof JSlider) {
  464. Integer parsed = new Integer(value);
  465. ((JSlider)result).setValue(parsed.intValue());
  466. } else if (result instanceof JComboBox) {
  467. ((JComboBox)result).setSelectedItem(value);
  468. } else if (result instanceof JRadioButton[]) {
  469. // First, parse the value, which may be a comma-separated list.
  470. Set selectedValues = new HashSet();
  471. StringTokenizer tokenizer = new StringTokenizer(value, ",");
  472. while (tokenizer.hasMoreTokens()) {
  473. selectedValues.add(tokenizer.nextToken().trim());
  474. }
  475. JRadioButton[] buttons = (JRadioButton[])result;
  476. for (int i = 0; i < buttons.length; i++) {
  477. if (selectedValues.contains(buttons[i].getText())) {
  478. buttons[i].setSelected(true);
  479. } else {
  480. buttons[i].setSelected(false);
  481. }
  482. }
  483. } else {
  484. throw new IllegalArgumentException("Query class cannot set"
  485. + " a string representation for entries of type "
  486. + result.getClass());
  487. }
  488. // Record the new value as if it was the previously notified
  489. // value. Thus, any future change from this value will trigger
  490. // notification.
  491. _previous.put(name, value);
  492. }
  493. /** Set the value in the entry with the given name and notify listeners.
  494. * The second argument must be a string that can be parsed to the
  495. * proper type for the given entry, or an exception is thrown.
  496. * @param name The name used to identify the entry (when calling get).
  497. * @param value The value to set the entry to.
  498. * @exception NoSuchElementException If there is no item with the
  499. * specified name. Note that this is a runtime exception, so it
  500. * need not be declared explicitly.
  501. * @exception IllegalArgumentException If the value does not parse
  502. * to the appropriate type.
  503. */
  504. public void setAndNotify(String name, String value)
  505. throws NoSuchElementException, IllegalArgumentException {
  506. set(name, value);
  507. _notifyListeners(name);
  508. }
  509. /** Set the background color for all the widgets.
  510. * @param color The background color.
  511. */
  512. public void setBackground(Color color) {
  513. super.setBackground(color);
  514. _background = color;
  515. // Set the background of any components that already exist.
  516. Component[] components = getComponents();
  517. for (int i = 0; i < components.length; i++) {
  518. if (!(components[i] instanceof JTextField)) {
  519. components[i].setBackground(_background);
  520. }
  521. }
  522. }
  523. /** Set the current value in the entry with the given name.
  524. * If the entry is not a checkbox, then throw an exception.
  525. * Notify listeners that the value has changed.
  526. * @exception NoSuchElementException If there is no item with the
  527. * specified name. Note that this is a runtime exception, so it
  528. * need not be declared explicitly.
  529. * @exception IllegalArgumentException If the entry is not a
  530. * checkbox. This is a runtime exception, so it
  531. * need not be declared explicitly.
  532. */
  533. public void setBoolean(String name, boolean value)
  534. throws NoSuchElementException, IllegalArgumentException {
  535. Object result = _entries.get(name);
  536. if(result == null) {
  537. throw new NoSuchElementException("No item named \"" +
  538. name + "\" in the query box.");
  539. }
  540. if (result instanceof JRadioButton) {
  541. ((JRadioButton)result).setSelected(value);
  542. } else {
  543. throw new IllegalArgumentException("Item named \"" +
  544. name + "\" is not a radio button, and hence does not have "
  545. + "a boolean value.");
  546. }
  547. _notifyListeners(name);
  548. }
  549. /** Set the displayed text of an entry that has been added using
  550. * addDisplay.
  551. * Notify listeners that the value has changed.
  552. * @param name The name of the entry.
  553. * @param value The string to display.
  554. * @exception NoSuchElementException If there is no entry with the
  555. * specified name. Note that this is a runtime exception, so it
  556. * need not be declared explicitly.
  557. * @exception IllegalArgumentException If the entry is not a
  558. * display. This is a runtime exception, so it
  559. * need not be declared explicitly.
  560. */
  561. public void setDisplay(String name, String value)
  562. throws NoSuchElementException, IllegalArgumentException {
  563. Object result = _entries.get(name);
  564. if(result == null) {
  565. throw new NoSuchElementException("No item named \"" +
  566. name + " \" in the query box.");
  567. }
  568. if (result instanceof JTextArea) {
  569. JTextArea label = (JTextArea)result;
  570. label.setText(value);
  571. } else {
  572. throw new IllegalArgumentException("Item named \"" +
  573. name + "\" is not a display, and hence cannot be set using "
  574. + "setDisplay().");
  575. }
  576. _notifyListeners(name);
  577. }
  578. /** For line, display, check box, slider, radio button, or choice
  579. * entries made, if the second argument is false, then it will
  580. * be disabled.
  581. * @param name The name of the entry.
  582. * @param value If false, disables the entry.
  583. */
  584. public void setEnabled(String name, boolean value) {
  585. Object result = _entries.get(name);
  586. if(result == null) {
  587. throw new NoSuchElementException("No item named \"" +
  588. name + " \" in the query box.");
  589. }
  590. if(result instanceof JComponent) {
  591. ((JComponent)result).setEnabled(value);
  592. } else if(result instanceof JRadioButton[]) {
  593. JRadioButton[] buttons = (JRadioButton[])result;
  594. for (int i = 0; i < buttons.length; i++) {
  595. buttons[i].setEnabled(value);
  596. }
  597. }
  598. }
  599. /** Set the displayed text of an item that has been added using
  600. * addLine. Notify listeners that the value has changed.
  601. * @param name The name of the entry.
  602. * @param value The string to display.
  603. * @exception NoSuchElementException If there is no item with the
  604. * specified name. Note that this is a runtime exception, so it
  605. * need not be declared explicitly.
  606. * @exception IllegalArgumentException If the entry is not a
  607. * display. This is a runtime exception, so it
  608. * need not be declared explicitly.
  609. */
  610. public void setLine(String name, String value) {
  611. Object result = _entries.get(name);
  612. if(result == null) {
  613. throw new NoSuchElementException("No item named \"" +
  614. name + " \" in the query box.");
  615. }
  616. if (result instanceof JTextField) {
  617. JTextField line = (JTextField)result;
  618. line.setText(value);
  619. } else {
  620. throw new IllegalArgumentException("Item named \"" +
  621. name + "\" is not a line, and hence cannot be set using "
  622. + "setLine().");
  623. }
  624. _notifyListeners(name);
  625. }
  626. /** Specify a message to be displayed above the query.
  627. * @param message The message to display.
  628. */
  629. public void setMessage(String message) {
  630. if (_messageArea == null) {
  631. _messageArea = new JTextArea(message);
  632. _messageArea.setFont(new Font("SansSerif", Font.PLAIN, 12));
  633. _messageArea.setEditable(false);
  634. _messageArea.setLineWrap(true);
  635. _messageArea.setWrapStyleWord(true);
  636. _messageArea.setBackground(getBackground());
  637. // Left Justify.
  638. _messageArea.setAlignmentX(0.0f);
  639. _messagePanel.setLayout(
  640. new BoxLayout(_messagePanel, BoxLayout.Y_AXIS));
  641. _messagePanel.add(_messageArea);
  642. // Add a spacer.
  643. _messagePanel.add(Box.createRigidArea(new Dimension(0,10)));
  644. } else {
  645. _messageArea.setText(message);
  646. }
  647. // In case size has changed.
  648. validate();
  649. }
  650. /** Set the position of an item that has been added using
  651. * addSlider. Notify listeners that the value has changed.
  652. * @param name The name of the entry.
  653. * @param value The value to set the slider position.
  654. * @exception NoSuchElementException If there is no item with the
  655. * specified name. Note that this is a runtime exception, so it
  656. * need not be declared explicitly.
  657. * @exception IllegalArgumentException If the entry is not a
  658. * slider. This is a runtime exception, so it
  659. * need not be declared explicitly.
  660. */
  661. public void setSlider(String name, int value) {
  662. Object result = _entries.get(name);
  663. if(result == null) {
  664. throw new NoSuchElementException("No item named \"" +
  665. name + " \" in the query box.");
  666. }
  667. if (result instanceof JSlider) {
  668. JSlider theSlider = (JSlider)result;
  669. // Set the new slider position.
  670. theSlider.setValue(value);
  671. } else {
  672. throw new IllegalArgumentException("Item named \"" +
  673. name + "\" is not a slider, and hence cannot be set using "
  674. + "setSlider().");
  675. }
  676. _notifyListeners(name);
  677. }
  678. /** Specify the preferred width to be used for entry boxes created
  679. * in using addLine(). If this is called multiple times, then
  680. * it only affects subsequent calls.
  681. *
  682. * @param characters The preferred width.
  683. */
  684. public void setTextWidth(int characters) {
  685. _width = characters;
  686. }
  687. /** Specify a tool tip to appear when the mouse lingers over the label.
  688. * @param name The name of the entry.
  689. * @param tip The text of the tool tip.
  690. */
  691. public void setToolTip(String name, String tip) {
  692. JLabel label = (JLabel)_labels.get(name);
  693. if (label != null) {
  694. label.setToolTipText(tip);
  695. }
  696. }
  697. /** Get the current value in the entry with the given name,
  698. * and return as a String. All entry types support this.
  699. * Note that this method should be called from the event dispatch
  700. * thread, since it needs to query to UI widgets for their current
  701. * values. If it is called from another thread, there is no
  702. * assurance that the value returned will be the current value.
  703. * @return The value currently in the entry as a String.
  704. * @exception NoSuchElementException If there is no item with the
  705. * specified name. Note that this is a runtime exception, so it
  706. * need not be declared explicitly.
  707. * @exception IllegalArgumentException If the entry type does not
  708. * have a string representation (this should not be thrown).
  709. */
  710. public String stringValue(String name)
  711. throws NoSuchElementException, IllegalArgumentException {
  712. Object result = _entries.get(name);
  713. if(result == null) {
  714. throw new NoSuchElementException("No item named \"" +
  715. name + " \" in the query box.");
  716. }
  717. // FIXME: Surely there is a better way to do this...
  718. // We should define a set of inner classes, one for each entry type.
  719. // Currently, this has to be updated each time a new entry type
  720. // is added.
  721. if (result instanceof JTextField) {
  722. return ((JTextField)result).getText();
  723. } else if (result instanceof JTextArea) {
  724. return ((JTextArea)result).getText();
  725. } else if (result instanceof JRadioButton) {
  726. JRadioButton radioButton = (JRadioButton)result;
  727. if (radioButton.isSelected()) {
  728. return "true";
  729. } else {
  730. return "false";
  731. }
  732. } else if (result instanceof JSlider) {
  733. return "" + ((JSlider)result).getValue();
  734. } else if (result instanceof JComboBox) {
  735. return (String)(((JComboBox)result).getSelectedItem());
  736. } else if (result instanceof JRadioButton[]) {
  737. // Regrettably, ButtonGroup gives no way to determine
  738. // which button is selected, so we have to search...
  739. JRadioButton[] buttons = (JRadioButton[])result;
  740. String toReturn = null;
  741. for (int i = 0; i < buttons.length; i++) {
  742. if (buttons[i].isSelected()) {
  743. if (toReturn == null) toReturn = buttons[i].getText();
  744. else toReturn = toReturn + ", " + buttons[i].getText();
  745. }
  746. }
  747. if (toReturn == null) toReturn = "";
  748. return toReturn;
  749. } else {
  750. throw new IllegalArgumentException("Query class cannot generate"
  751. + " a string representation for entries of type "
  752. + result.getClass());
  753. }
  754. }
  755. ///////////////////////////////////////////////////////////////////
  756. //// public variables ////
  757. /** The default width of entries created with addLine(). */
  758. public static final int DEFAULT_ENTRY_WIDTH = 20;
  759. ///////////////////////////////////////////////////////////////////
  760. //// protected methods ////
  761. /** Add a label and a widget to the panel.
  762. * @param name The name of the entry.
  763. * @param label The label.
  764. * @param widget The interactive entry to the right of the label.
  765. * @param entry The object that contains user data.
  766. */
  767. protected void _addPair(String name, JLabel label,
  768. Component widget, Object entry) {
  769. // Surely there is a better layout manager in swing...
  770. // Note that Box and BoxLayout do not work because they do not
  771. // support gridded layout.
  772. _constraints.gridwidth = 1;
  773. _grid.setConstraints(label, _constraints);
  774. _entryPanel.add(label);
  775. _constraints.gridwidth = GridBagConstraints.REMAINDER;
  776. _grid.setConstraints(widget, _constraints);
  777. _entryPanel.add(widget);
  778. _entries.put(name, entry);
  779. _labels.put(name, label);
  780. _previous.put(name, stringValue(name));
  781. }
  782. ///////////////////////////////////////////////////////////////////
  783. //// protected variables ////
  784. /** The background color as set by setBackground().
  785. * This defaults to null, which indicates that the background
  786. * is the same as the container.
  787. */
  788. protected Color _background = null;
  789. /** Layout control. */
  790. protected GridBagLayout _grid;
  791. /** Standard constraints for use with _grid. */
  792. protected GridBagConstraints _constraints;
  793. /** List of registered listeners. */
  794. protected Vector _listeners;
  795. ///////////////////////////////////////////////////////////////////
  796. //// friendly methods ////
  797. /** Notify all registered listeners that something changed for the
  798. * specified entry, if it indeed has changed. The stringValue()
  799. * method is used to check the current value against the previously
  800. * notified value, or the original value if there have been no
  801. * notifications.
  802. * @param name The entry that may have changed.
  803. */
  804. void _notifyListeners(String name) {
  805. if(_listeners != null) {
  806. String previous = (String)_previous.get(name);
  807. String newValue = stringValue(name);
  808. if (newValue.equals(previous)) {
  809. return;
  810. }
  811. // Store the new value to prevent repeated notification.
  812. // This must be done before listeners are notified, because
  813. // the notified listeners might do something that again triggers
  814. // notification, and we do not want that notification to occur
  815. // if the value has not changed.
  816. _previous.put(name, newValue);
  817. Enumeration listeners = _listeners.elements();
  818. while(listeners.hasMoreElements()) {
  819. QueryListener queryListener =
  820. (QueryListener)(listeners.nextElement());
  821. queryListener.changed(name);
  822. }
  823. }
  824. }
  825. ///////////////////////////////////////////////////////////////////
  826. //// private variables ////
  827. // The hashtable of items in the query.
  828. private Map _entries = new HashMap();
  829. // A panel within which the entries are placed.
  830. private JPanel _entryPanel = new JPanel();
  831. // The hashtable of labels in the query.
  832. private Map _labels = new HashMap();
  833. // Area for messages.
  834. private JTextArea _messageArea = null;
  835. // Panel into which messages are placed.
  836. private JPanel _messagePanel = new JPanel();
  837. // The hashtable of previous values, indexed by entry name.
  838. private Map _previous = new HashMap();
  839. // The width of the text boxes.
  840. private int _width = DEFAULT_ENTRY_WIDTH;
  841. ///////////////////////////////////////////////////////////////////
  842. //// inner classes ////
  843. /** Listener for "line" and radio button entries.
  844. */
  845. class QueryActionListener implements ActionListener {
  846. public QueryActionListener(String name) {
  847. _name = name;
  848. }
  849. /** Call all registered QueryListeners. */
  850. public void actionPerformed(ActionEvent e) {
  851. _notifyListeners(_name);
  852. }
  853. private String _name;
  854. }
  855. /** Listener for line entries, for when they lose the focus.
  856. */
  857. class QueryFocusListener implements FocusListener {
  858. public QueryFocusListener(String name) {
  859. _name = name;
  860. }
  861. public void focusGained(FocusEvent e) {
  862. // Nothing to do.
  863. }
  864. public void focusLost(FocusEvent e) {
  865. // NOTE: Java's lame AWT has no reliable way
  866. // to take action on window closing, so this focus lost
  867. // notification is the only reliable way we have of reacting
  868. // to a closing window. If the previous
  869. // notification was an erroneous one and the value has not
  870. // changed, then no further notification occurs.
  871. // This could be a problem for some users of this class.
  872. _notifyListeners(_name);
  873. }
  874. private String _name;
  875. }
  876. /** Listener for "CheckBox" and "Choice" entries.
  877. */
  878. class QueryItemListener implements ItemListener {
  879. public QueryItemListener(String name) {
  880. _name = name;
  881. }
  882. /** Call all registered QueryListeners. */
  883. public void itemStateChanged(ItemEvent e) {
  884. _notifyListeners(_name);
  885. }
  886. private String _name;
  887. }
  888. /** Listener for changes in slider.
  889. */
  890. class SliderListener implements ChangeListener {
  891. public SliderListener(String name) {
  892. _name = name;
  893. }
  894. /** Call all registered QueryListeners. */
  895. public void stateChanged(ChangeEvent event) {
  896. _notifyListeners(_name);
  897. }
  898. private String _name;
  899. }
  900. }