1. /*
  2. * @(#)JComboBox.java 1.82 01/02/09
  3. *
  4. * Copyright 1997-2001 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing;
  11. import java.beans.*;
  12. import java.util.*;
  13. import java.awt.*;
  14. import java.awt.event.*;
  15. import java.io.Serializable;
  16. import java.io.ObjectOutputStream;
  17. import java.io.ObjectInputStream;
  18. import java.io.IOException;
  19. import javax.swing.event.*;
  20. import javax.swing.plaf.*;
  21. import javax.swing.border.*;
  22. import javax.accessibility.*;
  23. /**
  24. * A component that combines a button or text field and a drop-down list.
  25. * The user can select a value from
  26. * the drop-down list, which appears at the user's request.
  27. * If you make the combo box editable,
  28. * then the combo box includes a text field
  29. * into which the user can type a value.
  30. * For examples and information on using combo boxes see
  31. * <a
  32. href="http://java.sun.com/docs/books/tutorial/uiswing/components/combobox.html">How to Use Combo Boxes</a>,
  33. * a section in <em>The Java Tutorial</em>.
  34. * <p>
  35. * For the keyboard keys used by this component in the standard Look and
  36. * Feel (L&F) renditions, see the
  37. * <a href="doc-files/Key-Index.html#JComboBox">JComboBox</a> key assignments.
  38. * <p>
  39. * <strong>Warning:</strong>
  40. * Serialized objects of this class will not be compatible with
  41. * future Swing releases. The current serialization support is appropriate
  42. * for short term storage or RMI between applications running the same
  43. * version of Swing. A future release of Swing will provide support for
  44. * long term persistence.
  45. *
  46. * @beaninfo
  47. * attribute: isContainer false
  48. * description: A combination of a text field and a drop-down list.
  49. *
  50. * @version 1.82 02/09/01
  51. * @author Arnaud Weber
  52. */
  53. public class JComboBox extends JComponent
  54. implements ItemSelectable,ListDataListener,ActionListener, Accessible {
  55. /**
  56. * @see #getUIClassID
  57. * @see #readObject
  58. */
  59. private static final String uiClassID = "ComboBoxUI";
  60. protected ComboBoxModel dataModel;
  61. protected ListCellRenderer renderer;
  62. protected ComboBoxEditor editor;
  63. protected int maximumRowCount = 8;
  64. protected boolean isEditable = false;
  65. protected Object selectedItemReminder = null;
  66. protected KeySelectionManager keySelectionManager = null;
  67. protected String actionCommand = "comboBoxChanged";
  68. protected boolean lightWeightPopupEnabled = JPopupMenu.getDefaultLightWeightPopupEnabled();
  69. boolean firedActionEventOnContentsChanged = false; // Flag for keeping actionEvents under control
  70. boolean firingActionEvent = false;
  71. /**
  72. * Creates a <code>JComboBox</code> that takes it's items from an
  73. * existing <code>ComboBoxModel</code>. Since the
  74. * <code>ComboBoxModel</code> is provided, a combo box created using
  75. * this constructor does not create a default combo box model and
  76. * may impact how the insert, remove and add methods behave.
  77. *
  78. * @param aModel the <code>ComboBoxModel</code> that provides the
  79. * displayed list of items
  80. * @see DefaultComboBoxModel
  81. */
  82. public JComboBox(ComboBoxModel aModel) {
  83. super();
  84. setModel(aModel);
  85. init();
  86. }
  87. /**
  88. * Creates a <code>JComboBox</code> that contains the elements
  89. * in the specified array. By default the first item in the array
  90. * (and therefore the data model) becomes selected.
  91. *
  92. * @param items an array of objects to insert into the combo box
  93. * @see DefaultComboBoxModel
  94. */
  95. public JComboBox(final Object items[]) {
  96. super();
  97. setModel(new DefaultComboBoxModel(items));
  98. init();
  99. }
  100. /**
  101. * Creates a <code>JComboBox</code> that contains the elements
  102. * in the specified Vector. By default the first item in the vector
  103. * and therefore the data model) becomes selected.
  104. *
  105. * @param items an array of vectors to insert into the combo box
  106. * @see DefaultComboBoxModel
  107. */
  108. public JComboBox(Vector items) {
  109. super();
  110. setModel(new DefaultComboBoxModel(items));
  111. init();
  112. }
  113. /**
  114. * Creates a <code>JComboBox</code> with a default data model.
  115. * The default data model is an empty list of objects.
  116. * Use <code>addItem</code> to add items. By default the first item
  117. * in the data model becomes selected.
  118. *
  119. * @see DefaultComboBoxModel
  120. */
  121. public JComboBox() {
  122. super();
  123. setModel(new DefaultComboBoxModel());
  124. init();
  125. }
  126. private void init()
  127. {
  128. installAncestorListener();
  129. setOpaque(true);
  130. updateUI();
  131. }
  132. protected void installAncestorListener() {
  133. addAncestorListener(new AncestorListener(){
  134. public void ancestorAdded(AncestorEvent event){ hidePopup();}
  135. public void ancestorRemoved(AncestorEvent event){ hidePopup();}
  136. public void ancestorMoved(AncestorEvent event){
  137. if (event.getSource() != JComboBox.this)
  138. hidePopup();
  139. }});
  140. }
  141. /**
  142. * Sets the L&F object that renders this component.
  143. *
  144. * @param ui the <code>ComboBoxUI</code> L&F object
  145. * @see UIDefaults#getUI
  146. *
  147. * @beaninfo
  148. * expert: true
  149. * description: The ComboBoxUI implementation that defines the combo box look and feel.
  150. */
  151. public void setUI(ComboBoxUI ui) {
  152. super.setUI(ui);
  153. }
  154. /**
  155. * Notification from the <code>UIFactory</code> that the L&F has changed.
  156. *
  157. * @see JComponent#updateUI
  158. */
  159. public void updateUI() {
  160. setUI((ComboBoxUI)UIManager.getUI(this));
  161. }
  162. /**
  163. * Returns the name of the L&F class that renders this component.
  164. *
  165. * @return the string "ComboBoxUI"
  166. * @see JComponent#getUIClassID
  167. * @see UIDefaults#getUI
  168. */
  169. public String getUIClassID() {
  170. return uiClassID;
  171. }
  172. /**
  173. * Returns the L&F object that renders this component.
  174. *
  175. * @return the ComboBoxUI object that renders this component
  176. */
  177. public ComboBoxUI getUI() {
  178. return(ComboBoxUI)ui;
  179. }
  180. /**
  181. * Sets the data model that the <code>JComboBox</code> uses to obtain
  182. * the list of items.
  183. *
  184. * @param aModel the <code>ComboBoxModel</code> that provides the
  185. * displayed list of items
  186. *
  187. * @beaninfo
  188. * bound: true
  189. * description: Model that the combo box uses to get data to display.
  190. */
  191. public void setModel(ComboBoxModel aModel) {
  192. ComboBoxModel oldModel = dataModel;
  193. if ( dataModel != null )
  194. dataModel.removeListDataListener(this);
  195. dataModel = aModel;
  196. firePropertyChange( "model", oldModel, dataModel);
  197. dataModel.addListDataListener(this);
  198. invalidate();
  199. }
  200. /**
  201. * Returns the data model currently used by the <code>JComboBox</code>.
  202. *
  203. * @return the <code>ComboBoxModel</code> that provides the displayed
  204. * list of items
  205. */
  206. public ComboBoxModel getModel() {
  207. return dataModel;
  208. }
  209. /*
  210. * Properties
  211. */
  212. /**
  213. * When displaying the popup, <code>JComboBox</code> choose to use
  214. * a light weight popup if it fits.
  215. * This method allows you to disable this feature. You have to do disable
  216. * it if your application mixes light weight and heavy weights components.
  217. *
  218. * @beaninfo
  219. * expert: true
  220. * description: When set, disables using light weight popups.
  221. */
  222. public void setLightWeightPopupEnabled(boolean aFlag) {
  223. lightWeightPopupEnabled = aFlag;
  224. }
  225. /**
  226. * Returns true if lightweight (all-Java) popups are in use,
  227. * or false if heavyweight (native peer) popups are being used.
  228. *
  229. * @return true if lightweight popups are in use
  230. */
  231. public boolean isLightWeightPopupEnabled() {
  232. return lightWeightPopupEnabled;
  233. }
  234. /**
  235. * Determines whether the <code>JComboBox</code> field is editable.
  236. * An editable <code>JComboBox</code> allows the user to type into the
  237. * field or selected an item from the list to initialize the field,
  238. * after which it can be edited. (The editing affects only the field,
  239. * the list item remains intact.) A non editable <code>JComboBox</code>
  240. * displays the selected item in the field,
  241. * but the selection cannot be modified.
  242. *
  243. * @param aFlag a boolean value, where true indicates that the
  244. * field is editable
  245. *
  246. * @beaninfo
  247. * bound: true
  248. * preferred: true
  249. * description: If true, the user can type a new value in the combo box.
  250. */
  251. public void setEditable(boolean aFlag) {
  252. boolean didChange = aFlag != isEditable;
  253. isEditable = aFlag;
  254. if ( didChange ) {
  255. firePropertyChange( "editable", !isEditable, isEditable );
  256. }
  257. }
  258. /**
  259. * Returns true if the <code>JComboBox</code> is editable.
  260. * By default, a combo box is not editable.
  261. *
  262. * @return true if the <code>JComboBox</code> is editable, else false
  263. */
  264. public boolean isEditable() {
  265. return isEditable;
  266. }
  267. /**
  268. * Sets the maximum number of rows the <code>JComboBox</code> displays.
  269. * If the number of objects in the model is greater than count,
  270. * the combo box uses a scrollbar.
  271. *
  272. * @param count an integer specifying the maximum number of items to
  273. * display in the list before using a scrollbar
  274. * @beaninfo
  275. * bound: true
  276. * preferred: true
  277. * description: The maximum number of rows the popup should have
  278. */
  279. public void setMaximumRowCount(int count) {
  280. int oldCount = maximumRowCount;
  281. maximumRowCount = count;
  282. firePropertyChange( "maximumRowCount", oldCount, maximumRowCount );
  283. }
  284. /**
  285. * Returns the maximum number of items the combo box can display
  286. * without a scrollbar
  287. *
  288. * @return an integer specifying the maximum number of items that are
  289. * displayed in the list before using a scrollbar
  290. */
  291. public int getMaximumRowCount() {
  292. return maximumRowCount;
  293. }
  294. /**
  295. * Sets the renderer that paints the item selected from the list in
  296. * the JComboBox field. The renderer is used if the JComboBox is not
  297. * editable. If it is editable, the editor is used to render and edit
  298. * the selected item.
  299. * <p>
  300. * The default renderer displays a string, obtained
  301. * by calling the selected object's <code>toString</code> method.
  302. * Other renderers can handle graphic images and composite items.
  303. * <p>
  304. * To display the selected item,
  305. * <code>aRenderer.getListCellRendererComponent</code>
  306. * is called, passing the list object and an index of -1.
  307. *
  308. * @param aRenderer the <code>ListCellRenderer</code> that
  309. * displays the selected item
  310. * @see #setEditor
  311. * @beaninfo
  312. * bound: true
  313. * expert: true
  314. * description: The renderer that paints the item selected in the list.
  315. */
  316. public void setRenderer(ListCellRenderer aRenderer) {
  317. ListCellRenderer oldRenderer = renderer;
  318. renderer = aRenderer;
  319. firePropertyChange( "renderer", oldRenderer, renderer );
  320. invalidate();
  321. }
  322. /**
  323. * Returns the renderer used to display the selected item in the
  324. * <code>JComboBox</code> field.
  325. *
  326. * @return the <code>ListCellRenderer</code> that displays
  327. * the selected item.
  328. */
  329. public ListCellRenderer getRenderer() {
  330. return renderer;
  331. }
  332. /**
  333. * Sets the editor used to paint and edit the selected item in the
  334. * <code>JComboBox</code> field. The editor is used only if the
  335. * receiving <code>JComboBox</code> is editable. If not editable,
  336. * the combo box uses the renderer to paint the selected item.
  337. *
  338. * @param anEditor the <code>ComboBoxEditor</code> that
  339. * displays the selected item
  340. * @see #setRenderer
  341. * @beaninfo
  342. * bound: true
  343. * expert: true
  344. * description: The editor that combo box uses to edit the current value
  345. */
  346. public void setEditor(ComboBoxEditor anEditor) {
  347. ComboBoxEditor oldEditor = editor;
  348. if ( editor != null )
  349. editor.removeActionListener(this);
  350. editor = anEditor;
  351. if ( editor != null ) {
  352. editor.addActionListener(this);
  353. }
  354. firePropertyChange( "editor", oldEditor, editor );
  355. }
  356. /**
  357. * Returns the editor used to paint and edit the selected item in the
  358. * <code>JComboBox</code> field.
  359. *
  360. * @return the <code>ComboBoxEditor</code> that displays the selected item
  361. */
  362. public ComboBoxEditor getEditor() {
  363. return editor;
  364. }
  365. /*
  366. * Selection
  367. */
  368. /**
  369. * Sets the selected item in the <code>JComboBox</code> by
  370. * specifying the object in the list.
  371. * If <code>anObject</code> is in the list, the list displays with
  372. * <code>anObject</code> selected.
  373. *
  374. * @param anObject the list object to select
  375. * @beaninfo
  376. * preferred: true
  377. * description: Sets the selected item in the JComboBox.
  378. */
  379. public void setSelectedItem(Object anObject) {
  380. firedActionEventOnContentsChanged = false;
  381. dataModel.setSelectedItem(anObject);
  382. if ( !firedActionEventOnContentsChanged ) {
  383. fireActionEvent();
  384. }
  385. else {
  386. firedActionEventOnContentsChanged = false;
  387. }
  388. }
  389. /**
  390. * Returns the currently selected item.
  391. *
  392. * @return the currently selected list object from the data model
  393. */
  394. public Object getSelectedItem() {
  395. return dataModel.getSelectedItem();
  396. }
  397. /**
  398. * Selects the item at index <code>anIndex</code>.
  399. *
  400. * @param anIndex an integer specifying the list item to select,
  401. * where 0 specifies
  402. * the first item in the list
  403. * @exception IllegalArgumentException if <code>anIndex</code> < -1 or
  404. * <code>anIndex</code> is greater than or equal to size
  405. * @beaninfo
  406. * preferred: true
  407. * description: The item at index is selected.
  408. */
  409. public void setSelectedIndex(int anIndex) {
  410. int size = dataModel.getSize();
  411. if ( anIndex == -1 ) {
  412. setSelectedItem( null );
  413. }
  414. else if ( anIndex < -1 || anIndex >= size ) {
  415. throw new IllegalArgumentException("setSelectedIndex: " + anIndex + " out of bounds");
  416. }
  417. else {
  418. setSelectedItem(dataModel.getElementAt(anIndex));
  419. }
  420. }
  421. /**
  422. * Returns the first item in the list that matches the given item.
  423. * The result is not always defined if the <code>JComboBox</code>
  424. * allows selected items that are not in the list.
  425. * Returns -1 if there is no selected item or if the user specified
  426. * an item which is not in the list.
  427. * @return an integer specifying the currently selected list item,
  428. * where 0 specifies
  429. * the first item in the list;
  430. * or -1 if no item is selected or if
  431. * the currently selected item is not in the list
  432. */
  433. public int getSelectedIndex() {
  434. Object sObject = dataModel.getSelectedItem();
  435. int i,c;
  436. Object obj;
  437. for ( i=0,c=dataModel.getSize();i<c;i++ ) {
  438. obj = dataModel.getElementAt(i);
  439. if ( obj.equals(sObject) )
  440. return i;
  441. }
  442. return -1;
  443. }
  444. /**
  445. * Adds an item to the item list.
  446. * This method works only if the <code>JComboBox</code> uses the
  447. * default data model. <code>JComboBox</code>
  448. * uses the default data model when created with the
  449. * empty constructor and no other model has been set.
  450. * <p>
  451. * <strong>Warning:</strong>
  452. * Focus and keyboard navigation problems may arise if you add duplicate
  453. * String objects. A workaround is to add new objects instead of String
  454. * objects and make sure that the toString() method is defined.
  455. * For example:
  456. * <pre>
  457. * comboBox.addItem(makeObj("Item 1"));
  458. * comboBox.addItem(makeObj("Item 1"));
  459. * ...
  460. * private Object makeObj(final String item) {
  461. * return new Object() { public String toString() { return item; } };
  462. * }
  463. * </pre>
  464. *
  465. * @param anObject the Object to add to the list
  466. */
  467. public void addItem(Object anObject) {
  468. checkMutableComboBoxModel();
  469. ((MutableComboBoxModel)dataModel).addElement(anObject);
  470. }
  471. /**
  472. * Inserts an item into the item list at a given index.
  473. * This method works only if the <code>JComboBox</code> uses the
  474. * default data model. <code>JComboBox</code>
  475. * uses the default data model when created with the
  476. * empty constructor and no other model has been set.
  477. *
  478. * @param anObject the <code>Object</code> to add to the list
  479. * @param index an integer specifying the position at which
  480. * to add the item
  481. */
  482. public void insertItemAt(Object anObject, int index) {
  483. checkMutableComboBoxModel();
  484. ((MutableComboBoxModel)dataModel).insertElementAt(anObject,index);
  485. }
  486. /**
  487. * Removes an item from the item list.
  488. * This method works only if the <code>JComboBox</code> uses the
  489. * default data model. <code>JComboBox</code>
  490. * uses the default data model when created with the empty constructor
  491. * and no other model has been set.
  492. *
  493. * @param anObject the object to remove from the item list
  494. */
  495. public void removeItem(Object anObject) {
  496. checkMutableComboBoxModel();
  497. ((MutableComboBoxModel)dataModel).removeElement(anObject);
  498. }
  499. /**
  500. * Removes the item at <code>anIndex</code>
  501. * This method works only if the <code>JComboBox</code> uses the
  502. * default data model. <code>JComboBox</code>
  503. * uses the default data model when created with the
  504. * empty constructor and no other model has been set.
  505. *
  506. * @param anIndex an int specifying the idex of the item to remove,
  507. * where 0
  508. * indicates the first item in the list
  509. */
  510. public void removeItemAt(int anIndex) {
  511. checkMutableComboBoxModel();
  512. ((MutableComboBoxModel)dataModel).removeElementAt( anIndex );
  513. }
  514. /**
  515. * Removes all items from the item list.
  516. */
  517. public void removeAllItems() {
  518. checkMutableComboBoxModel();
  519. MutableComboBoxModel model = (MutableComboBoxModel)dataModel;
  520. int size = model.getSize();
  521. if ( model instanceof DefaultComboBoxModel ) {
  522. ((DefaultComboBoxModel)model).removeAllElements();
  523. }
  524. else {
  525. for ( int i = 0; i < size; ++i ) {
  526. Object element = model.getElementAt( 0 );
  527. model.removeElement( element );
  528. }
  529. }
  530. }
  531. /**
  532. * Checks that the <code>dataModel</code> is an instance of
  533. * <code>MutableComboBoxModel</code>. If not, it throws an exception.
  534. * @exception RuntimeException if <code>dataModel</code> is not an
  535. * instance of <code>MutableComboBoxModel</code>.
  536. */
  537. void checkMutableComboBoxModel() {
  538. if ( !(dataModel instanceof MutableComboBoxModel) )
  539. throw new RuntimeException("Cannot use this method with a non-Mutable data model.");
  540. }
  541. /**
  542. * Causes the combo box to display its popup window.
  543. * @see #setPopupVisible
  544. */
  545. public void showPopup() {
  546. setPopupVisible(true);
  547. }
  548. /**
  549. * Causes the combo box to close its popup window.
  550. * @see #setPopupVisible
  551. */
  552. public void hidePopup() {
  553. setPopupVisible(false);
  554. }
  555. /**
  556. * Sets the visibility of the popup.
  557. */
  558. public void setPopupVisible(boolean v) {
  559. getUI().setPopupVisible(this, v);
  560. }
  561. /**
  562. * Determines the visibility of the popup.
  563. */
  564. public boolean isPopupVisible() {
  565. return getUI().isPopupVisible(this);
  566. }
  567. /** Selection **/
  568. /**
  569. * Adds an <code>ItemListener</code>.
  570. * <code>aListener</code> will receive an event when
  571. * the selected item changes.
  572. *
  573. * @param aListener the <code>ItemListener</code> that is to be notified
  574. */
  575. public void addItemListener(ItemListener aListener) {
  576. listenerList.add(ItemListener.class,aListener);
  577. }
  578. /** Removes an <code>ItemListener</code>.
  579. *
  580. * @param aListener the <code>ItemListener</code> to remove
  581. */
  582. public void removeItemListener(ItemListener aListener) {
  583. listenerList.remove(ItemListener.class,aListener);
  584. }
  585. /**
  586. * Adds an <code>ActionListener</code>. The listener will
  587. * receive an action event
  588. * the user finishes making a selection.
  589. *
  590. * @param l the <code>ActionListener</code> that is to be notified
  591. */
  592. public void addActionListener(ActionListener l) {
  593. listenerList.add(ActionListener.class,l);
  594. }
  595. /** Removes an <code>ActionListener</code>.
  596. *
  597. * @param l the <code>ActionListener</code> to remove
  598. */
  599. public void removeActionListener(ActionListener l) {
  600. if ((l != null) && (getAction() == l)) {
  601. setAction(null);
  602. } else {
  603. listenerList.remove(ActionListener.class, l);
  604. }
  605. }
  606. /**
  607. * Sets the action commnand that should be included in the event
  608. * sent to action listeners.
  609. *
  610. * @param aCommand a string containing the "command" that is sent
  611. * to action listeners; the same listener can then
  612. * do different things depending on the command it
  613. * receives
  614. */
  615. public void setActionCommand(String aCommand) {
  616. actionCommand = aCommand;
  617. }
  618. /**
  619. * Returns the action command that is included in the event sent to
  620. * action listeners.
  621. *
  622. * @return the string containing the "command" that is sent
  623. * to action listeners.
  624. */
  625. public String getActionCommand() {
  626. return actionCommand;
  627. }
  628. private Action action;
  629. private PropertyChangeListener actionPropertyChangeListener;
  630. /**
  631. * Sets the <code>Action</code> for the <code>ActionEvent</code> source.
  632. * The new <code>Action</code> replaces any previously set
  633. * <code>Action</code> but does not affect <code>ActionListeners</code>
  634. * independantly added with <code>addActionListener</code>.
  635. * If the <code>Action</code> is already a registered
  636. * <code>ActionListener</code> for the <code>ActionEvent</code> source,
  637. * it is not re-registered.
  638. *
  639. * <p>
  640. * A side-effect of setting the <code>Action</code> is that the
  641. * <code>ActionEvent</code> source's properties are immedately set
  642. * from the values in the <code>Action</code> (performed by the method
  643. * <code>configurePropertiesFromAction</code>) and subsequently
  644. * updated as the <code>Action</code>'s properties change (via a
  645. * <code>PropertyChangeListener</code> created by the method
  646. * <code>createActionPropertyChangeListener</code>.
  647. *
  648. * @param a the <code>Action</code> for the <code>JComboBox</code>,
  649. * or <code>null</code>.
  650. * @since 1.3
  651. * @see Action
  652. * @see #getAction
  653. * @see #configurePropertiesFromAction
  654. * @see #createActionPropertyChangeListener
  655. * @beaninfo
  656. * bound: true
  657. * attribute: visualUpdate true
  658. * description: the Action instance connected with this ActionEvent source
  659. */
  660. public void setAction(Action a) {
  661. Action oldValue = getAction();
  662. if (action==null || !action.equals(a)) {
  663. action = a;
  664. if (oldValue!=null) {
  665. removeActionListener(oldValue);
  666. oldValue.removePropertyChangeListener(actionPropertyChangeListener);
  667. actionPropertyChangeListener = null;
  668. }
  669. configurePropertiesFromAction(action);
  670. if (action!=null) {
  671. // Don't add if it is already a listener
  672. if (!isListener(ActionListener.class, action)) {
  673. addActionListener(action);
  674. }
  675. // Reverse linkage:
  676. actionPropertyChangeListener = createActionPropertyChangeListener(action);
  677. action.addPropertyChangeListener(actionPropertyChangeListener);
  678. }
  679. firePropertyChange("action", oldValue, action);
  680. revalidate();
  681. repaint();
  682. }
  683. }
  684. private boolean isListener(Class c, ActionListener a) {
  685. boolean isListener = false;
  686. Object[] listeners = listenerList.getListenerList();
  687. for (int i = listeners.length-2; i>=0; i-=2) {
  688. if (listeners[i]==c && listeners[i+1]==a) {
  689. isListener=true;
  690. }
  691. }
  692. return isListener;
  693. }
  694. /**
  695. * Returns the currently set <code>Action</code> for this
  696. * <code>ActionEvent</code> source, or <code>null</code> if no
  697. * <code>Action</code> is set.
  698. *
  699. * @return the <code>Action</code> for this <code>ActionEvent</code>
  700. * source; or <code>null</code>
  701. * @since 1.3
  702. * @see Action
  703. * @see #setAction
  704. */
  705. public Action getAction() {
  706. return action;
  707. }
  708. /**
  709. * Factory method which sets the <code>ActionEvent</code> source's
  710. * properties according to values from the <code>Action</code> instance.
  711. * The properties which are set may differ for subclasses.
  712. * By default, the properties which get set are
  713. * <code>Enabled</code> and <code>ToolTipText</code>.
  714. *
  715. * @param a the <code>Action</code> from which to get the properties,
  716. * or <code>null</code>
  717. * @since 1.3
  718. * @see Action
  719. * @see #setAction
  720. */
  721. protected void configurePropertiesFromAction(Action a) {
  722. setEnabled((a!=null?a.isEnabled():true));
  723. setToolTipText((a!=null?(String)a.getValue(Action.SHORT_DESCRIPTION):null));
  724. }
  725. /**
  726. * Factory method which creates the <code>PropertyChangeListener</code>
  727. * used to update the <code>ActionEvent</code> source as properties change
  728. * on its <code>Action</code> instance.
  729. * Subclasses may override this in order to provide their own
  730. * <code>PropertyChangeListener</code> if the set of
  731. * properties which should be kept up to date differs from the
  732. * default properties (Text, Icon, Enabled, ToolTipText).
  733. *
  734. * Note that <code>PropertyChangeListeners</code> should avoid holding
  735. * strong references to the <code>ActionEvent</code> source,
  736. * as this may hinder garbage collection of the <code>ActionEvent</code>
  737. * source and all components in its containment hierarchy.
  738. *
  739. * @since 1.3
  740. * @see Action
  741. * @see #setAction
  742. */
  743. protected PropertyChangeListener createActionPropertyChangeListener(Action a) {
  744. return new AbstractActionPropertyChangeListener(this, a) {
  745. public void propertyChange(PropertyChangeEvent e) {
  746. String propertyName = e.getPropertyName();
  747. JComboBox comboBox = (JComboBox)getTarget();
  748. if (comboBox == null) { //WeakRef GC'ed in 1.2
  749. Action action = (Action)e.getSource();
  750. action.removePropertyChangeListener(this);
  751. } else {
  752. if (e.getPropertyName().equals(Action.SHORT_DESCRIPTION)) {
  753. String text = (String) e.getNewValue();
  754. comboBox.setToolTipText(text);
  755. } else if (propertyName.equals("enabled")) {
  756. Boolean enabledState = (Boolean) e.getNewValue();
  757. comboBox.setEnabled(enabledState.booleanValue());
  758. comboBox.repaint();
  759. }
  760. }
  761. }
  762. };
  763. }
  764. /**
  765. * Notifies all listeners that have registered interest for
  766. * notification on this event type.
  767. * @param e the event of interest
  768. *
  769. * @see EventListenerList
  770. */
  771. protected void fireItemStateChanged(ItemEvent e) {
  772. // Guaranteed to return a non-null array
  773. Object[] listeners = listenerList.getListenerList();
  774. // Process the listeners last to first, notifying
  775. // those that are interested in this event
  776. for ( int i = listeners.length-2; i>=0; i-=2 ) {
  777. if ( listeners[i]==ItemListener.class ) {
  778. // Lazily create the event:
  779. // if (changeEvent == null)
  780. // changeEvent = new ChangeEvent(this);
  781. ((ItemListener)listeners[i+1]).itemStateChanged(e);
  782. }
  783. }
  784. }
  785. /**
  786. * Notifies all listeners that have registered interest for
  787. * notification on this event type.
  788. * @param e the event of interest
  789. *
  790. * @see EventListenerList
  791. */
  792. protected void fireActionEvent() {
  793. if ( !firingActionEvent ) {
  794. firingActionEvent = true;
  795. ActionEvent e = null;
  796. // Guaranteed to return a non-null array
  797. Object[] listeners = listenerList.getListenerList();
  798. // Process the listeners last to first, notifying
  799. // those that are interested in this event
  800. for ( int i = listeners.length-2; i>=0; i-=2 ) {
  801. if ( listeners[i]==ActionListener.class ) {
  802. if ( e == null )
  803. e = new ActionEvent(this,ActionEvent.ACTION_PERFORMED,getActionCommand());
  804. ((ActionListener)listeners[i+1]).actionPerformed(e);
  805. }
  806. }
  807. firingActionEvent = false;
  808. }
  809. }
  810. /**
  811. * This method is called when the selected item changes.
  812. * Its default implementation notifies the item listeners.
  813. */
  814. protected void selectedItemChanged() {
  815. if ( selectedItemReminder != null ) {
  816. fireItemStateChanged(new ItemEvent(this,ItemEvent.ITEM_STATE_CHANGED,
  817. selectedItemReminder,
  818. ItemEvent.DESELECTED));
  819. }
  820. selectedItemReminder = getModel().getSelectedItem();
  821. if ( selectedItemReminder != null )
  822. fireItemStateChanged(new ItemEvent(this,ItemEvent.ITEM_STATE_CHANGED,
  823. selectedItemReminder,
  824. ItemEvent.SELECTED));
  825. fireActionEvent();
  826. firedActionEventOnContentsChanged = true;
  827. }
  828. /**
  829. * Returns an array containing the selected item.
  830. * This method is implemented for compatibility with
  831. * <code>ItemSelectable</code>.
  832. *
  833. * @returns an array of <code>Objects</code> containing one
  834. * element -- the selected item
  835. */
  836. public Object[] getSelectedObjects() {
  837. Object selectedObject = getSelectedItem();
  838. if ( selectedObject == null )
  839. return new Object[0];
  840. else {
  841. Object result[] = new Object[1];
  842. result[0] = selectedObject;
  843. return result;
  844. }
  845. }
  846. /**
  847. * This method is public as an implementation side effect.
  848. * do not call or override.
  849. */
  850. public void actionPerformed(ActionEvent e) {
  851. Object newItem = getEditor().getItem();
  852. firedActionEventOnContentsChanged = false;
  853. getUI().setPopupVisible(this, false);
  854. getModel().setSelectedItem(newItem);
  855. if ( !firedActionEventOnContentsChanged ) {
  856. fireActionEvent();
  857. }
  858. else {
  859. firedActionEventOnContentsChanged = false;
  860. }
  861. }
  862. /**
  863. * This method is public as an implementation side effect.
  864. * do not call or override.
  865. *
  866. * @see javax.swing.event.ListDataListener
  867. */
  868. public void contentsChanged(ListDataEvent e) {
  869. ComboBoxModel mod = getModel();
  870. Object newSelectedItem = mod.getSelectedItem();
  871. if ( selectedItemReminder == null ) {
  872. if ( newSelectedItem != null )
  873. selectedItemChanged();
  874. }
  875. else {
  876. if ( !selectedItemReminder.equals(newSelectedItem) ) {
  877. selectedItemChanged();
  878. }
  879. }
  880. if ( !isEditable() && newSelectedItem != null ) {
  881. int i,c;
  882. boolean shouldResetSelectedItem = true;
  883. Object o;
  884. Object selectedItem = mod.getSelectedItem();
  885. for ( i=0,c=mod.getSize();i<c;i++ ) {
  886. o = mod.getElementAt(i);
  887. if ( o.equals(selectedItem) ) {
  888. shouldResetSelectedItem = false;
  889. break;
  890. }
  891. }
  892. if ( shouldResetSelectedItem ) {
  893. if ( mod.getSize() > 0 )
  894. setSelectedIndex(0);
  895. else
  896. setSelectedItem(null);
  897. }
  898. }
  899. }
  900. /**
  901. * Selects the list item that correponds to the specified keyboard
  902. * character and returns true, if there is an item corresponding
  903. * to that character. Otherwise, returns false.
  904. *
  905. * @param keyChar a char, typically this is a keyboard key
  906. * typed by the user
  907. */
  908. public boolean selectWithKeyChar(char keyChar) {
  909. int index;
  910. if ( keySelectionManager == null )
  911. keySelectionManager = createDefaultKeySelectionManager();
  912. index = keySelectionManager.selectionForKey(keyChar,getModel());
  913. if ( index != -1 ) {
  914. setSelectedIndex(index);
  915. return true;
  916. }
  917. else
  918. return false;
  919. }
  920. /**
  921. * Invoked items have been added to the internal data model.
  922. * The "interval" includes the first and last values added.
  923. *
  924. * @see javax.swing.event.ListDataListener
  925. */
  926. public void intervalAdded(ListDataEvent e) {
  927. contentsChanged(e);
  928. }
  929. /**
  930. * Invoked when values have been removed from the data model.
  931. * The "interval" includes the first and last values removed.
  932. *
  933. * @see javax.swing.event.ListDataListener
  934. */
  935. public void intervalRemoved(ListDataEvent e) {
  936. contentsChanged(e);
  937. }
  938. /**
  939. * Enables the combo box so that items can be selected. When the
  940. * combo box is disabled, items cannot be selected and values
  941. * cannot be typed into its field (if it is editable).
  942. *
  943. * @param b a boolean value, where true enables the component and
  944. * false disables it
  945. * @beaninfo
  946. * bound: true
  947. * preferred: true
  948. * description: Whether the combo box is enabled.
  949. */
  950. public void setEnabled(boolean b) {
  951. super.setEnabled(b);
  952. firePropertyChange( "enabled", !isEnabled(), isEnabled() );
  953. }
  954. /**
  955. * Initializes the editor with the specified item.
  956. *
  957. * @param anEditor the <code>ComboBoxEditor</code> that displays
  958. * the list item in the
  959. * combo box field and allows it to be edited
  960. * @param anItem the object to display and edit in the field
  961. */
  962. public void configureEditor(ComboBoxEditor anEditor,Object anItem) {
  963. anEditor.setItem(anItem);
  964. }
  965. /**
  966. * Handles <code>KeyEvent</code>s, looking for the Tab key.
  967. * If the Tab key is found, the popup window is closed.
  968. *
  969. * @param e the <code>KeyEvent</code> containing the keyboard
  970. * key that was pressed
  971. */
  972. public void processKeyEvent(KeyEvent e) {
  973. if ( e.getKeyCode() == KeyEvent.VK_TAB ) {
  974. hidePopup();
  975. }
  976. super.processKeyEvent(e);
  977. }
  978. /**
  979. * Returns true if the component can receive the focus. In this case,
  980. * the component returns false if it is editable, so that the
  981. * <code>Editor</code> object receives the focus,
  982. * instead of the component.
  983. *
  984. * @return true if the component can receive the focus, else false
  985. */
  986. public boolean isFocusTraversable() {
  987. return getUI().isFocusTraversable(this);
  988. }
  989. /**
  990. * Sets the object that translates a keyboard character into a list
  991. * selection. Typically, the first selection with a matching first
  992. * character becomes the selected item.
  993. *
  994. * @beaninfo
  995. * expert: true
  996. * description: The objects that changes the selection when a key is pressed.
  997. */
  998. public void setKeySelectionManager(KeySelectionManager aManager) {
  999. keySelectionManager = aManager;
  1000. }
  1001. /**
  1002. * Returns the list's key-selection manager.
  1003. *
  1004. * @return the <code>KeySelectionManager</code> currently in use
  1005. */
  1006. public KeySelectionManager getKeySelectionManager() {
  1007. return keySelectionManager;
  1008. }
  1009. /* Accessing the model */
  1010. /**
  1011. * Returns the number of items in the list.
  1012. *
  1013. * @return an integer equal to the number of items in the list
  1014. */
  1015. public int getItemCount() {
  1016. return dataModel.getSize();
  1017. }
  1018. /**
  1019. * Returns the list item at the specified index. If <code>index</code>
  1020. * is out of range (less than zero or greater than or equal to size)
  1021. * it will return <code>null</code>.
  1022. *
  1023. * @param index an integer indicating the list position, where the first
  1024. * item starts at zero
  1025. * @return the <code>Object</code> at that list position; or
  1026. * <code>null</code> if out of range
  1027. */
  1028. public Object getItemAt(int index) {
  1029. return dataModel.getElementAt(index);
  1030. }
  1031. /**
  1032. * Returns an instance of the default key-selection manager.
  1033. *
  1034. * @return the <code>KeySelectionManager</code> currently used by the list
  1035. * @see #setKeySelectionManager
  1036. */
  1037. protected KeySelectionManager createDefaultKeySelectionManager() {
  1038. return new DefaultKeySelectionManager();
  1039. }
  1040. /**
  1041. * The interface that defines a <code>KeySelectionManager</code>.
  1042. * To qualify as a <code>KeySelectionManager</code>,
  1043. * the class needs to implement the method
  1044. * that identifies the list index given a character and the
  1045. * combo box data model.
  1046. */
  1047. public interface KeySelectionManager {
  1048. /** Given <code>aKey</code> and the model, returns the row
  1049. * that should become selected. Return -1 if no match was
  1050. * found.
  1051. *
  1052. * @param aKey a char value, usually indicating a keyboard key that
  1053. * was pressed
  1054. * @param aModel a ComboBoxModel -- the component's data model, containing
  1055. * the list of selectable items
  1056. * @return an int equal to the selected row, where 0 is the
  1057. * first item and -1 is none.
  1058. */
  1059. int selectionForKey(char aKey,ComboBoxModel aModel);
  1060. }
  1061. class DefaultKeySelectionManager implements KeySelectionManager, Serializable {
  1062. public int selectionForKey(char aKey,ComboBoxModel aModel) {
  1063. int i,c;
  1064. int currentSelection = -1;
  1065. Object selectedItem = aModel.getSelectedItem();
  1066. String v;
  1067. String pattern;
  1068. if ( selectedItem != null ) {
  1069. selectedItem = selectedItem.toString();
  1070. for ( i=0,c=aModel.getSize();i<c;i++ ) {
  1071. if ( selectedItem.equals(aModel.getElementAt(i).toString()) ) {
  1072. currentSelection = i;
  1073. break;
  1074. }
  1075. }
  1076. }
  1077. pattern = ("" + aKey).toLowerCase();
  1078. aKey = pattern.charAt(0);
  1079. for ( i = ++currentSelection, c = aModel.getSize() ; i < c ; i++ ) {
  1080. v = aModel.getElementAt(i).toString().toLowerCase();
  1081. if ( v.length() > 0 && v.charAt(0) == aKey )
  1082. return i;
  1083. }
  1084. for ( i = 0 ; i < currentSelection ; i ++ ) {
  1085. v = aModel.getElementAt(i).toString().toLowerCase();
  1086. if ( v.length() > 0 && v.charAt(0) == aKey )
  1087. return i;
  1088. }
  1089. return -1;
  1090. }
  1091. }
  1092. /**
  1093. * See <code>readObject</code> and <code>writeObject</code> in
  1094. * </code>JComponent</code> for more
  1095. * information about serialization in Swing.
  1096. */
  1097. private void writeObject(ObjectOutputStream s) throws IOException {
  1098. s.defaultWriteObject();
  1099. if ((ui != null) && (getUIClassID().equals(uiClassID))) {
  1100. ui.installUI(this);
  1101. }
  1102. }
  1103. /**
  1104. * Returns a string representation of this <code>JComboBox</code>.
  1105. * This method is intended to be used only for debugging purposes,
  1106. * and the content and format of the returned string may vary between
  1107. * implementations. The returned string may be empty but may not
  1108. * be <code>null</code>.
  1109. *
  1110. * @return a string representation of this <code>JComboBox</code>
  1111. */
  1112. protected String paramString() {
  1113. String selectedItemReminderString = (selectedItemReminder != null ?
  1114. selectedItemReminder.toString() :
  1115. "");
  1116. String isEditableString = (isEditable ? "true" : "false");
  1117. String lightWeightPopupEnabledString = (lightWeightPopupEnabled ?
  1118. "true" : "false");
  1119. return super.paramString() +
  1120. ",isEditable=" + isEditableString +
  1121. ",lightWeightPopupEnabled=" + lightWeightPopupEnabledString +
  1122. ",maximumRowCount=" + maximumRowCount +
  1123. ",selectedItemReminder=" + selectedItemReminderString;
  1124. }
  1125. ///////////////////
  1126. // Accessibility support
  1127. ///////////////////
  1128. /**
  1129. * Gets the AccessibleContext associated with this JComboBox.
  1130. * For combo boxes, the AccessibleContext takes the form of an
  1131. * AccessibleJComboBox.
  1132. * A new AccessibleJComboBox instance is created if necessary.
  1133. *
  1134. * @return an AccessibleJComboBox that serves as the
  1135. * AccessibleContext of this JComboBox
  1136. */
  1137. public AccessibleContext getAccessibleContext() {
  1138. if ( accessibleContext == null ) {
  1139. accessibleContext = new AccessibleJComboBox();
  1140. }
  1141. return accessibleContext;
  1142. }
  1143. /**
  1144. * This class implements accessibility support for the
  1145. * <code>JComboBox</code> class. It provides an implementation of the
  1146. * Java Accessibility API appropriate to Combo Box user-interface elements.
  1147. * <p>
  1148. * <strong>Warning:</strong>
  1149. * Serialized objects of this class will not be compatible with
  1150. * future Swing releases. The current serialization support is appropriate
  1151. * for short term storage or RMI between applications running the same
  1152. * version of Swing. A future release of Swing will provide support for
  1153. * long term persistence.
  1154. */
  1155. protected class AccessibleJComboBox extends AccessibleJComponent
  1156. implements AccessibleAction, AccessibleSelection {
  1157. /**
  1158. * Returns the number of accessible children in the object. If all
  1159. * of the children of this object implement Accessible, than this
  1160. * method should return the number of children of this object.
  1161. *
  1162. * @return the number of accessible children in the object.
  1163. */
  1164. public int getAccessibleChildrenCount() {
  1165. // Always delegate to the UI if it exists
  1166. if (ui != null) {
  1167. return ui.getAccessibleChildrenCount(JComboBox.this);
  1168. } else {
  1169. return super.getAccessibleChildrenCount();
  1170. }
  1171. }
  1172. /**
  1173. * Returns the nth Accessible child of the object.
  1174. * The child at index zero represents the popup.
  1175. * If the combo box is editable, the child at index one
  1176. * represents the editor.
  1177. *
  1178. * @param i zero-based index of child
  1179. * @return the nth Accessible child of the object
  1180. */
  1181. public Accessible getAccessibleChild(int i) {
  1182. // Always delegate to the UI if it exists
  1183. if (ui != null) {
  1184. return ui.getAccessibleChild(JComboBox.this, i);
  1185. } else {
  1186. return super.getAccessibleChild(i);
  1187. }
  1188. }
  1189. /**
  1190. * Get the role of this object.
  1191. *
  1192. * @return an instance of AccessibleRole describing the role of the
  1193. * object
  1194. * @see AccessibleRole
  1195. */
  1196. public AccessibleRole getAccessibleRole() {
  1197. return AccessibleRole.COMBO_BOX;
  1198. }
  1199. /**
  1200. * Get the AccessibleAction associated with this object. In the
  1201. * implementation of the Java Accessibility API for this class,
  1202. * return this object, which is responsible for implementing the
  1203. * AccessibleAction interface on behalf of itself.
  1204. *
  1205. * @return this object
  1206. */
  1207. public AccessibleAction getAccessibleAction() {
  1208. return this;
  1209. }
  1210. /**
  1211. * Return a description of the specified action of the object.
  1212. *
  1213. * @param i zero-based index of the actions
  1214. */
  1215. public String getAccessibleActionDescription(int i) {
  1216. if (i == 0) {
  1217. return UIManager.getString("ComboBox.togglePopupText");
  1218. }
  1219. else {
  1220. return null;
  1221. }
  1222. }
  1223. /**
  1224. * Returns the number of Actions available in this object. The
  1225. * default behavior of a combo box is to have one action.
  1226. *
  1227. * @return 1, the number of Actions in this object
  1228. */
  1229. public int getAccessibleActionCount() {
  1230. return 1;
  1231. }
  1232. /**
  1233. * Perform the specified Action on the object
  1234. *
  1235. * @param i zero-based index of actions
  1236. * @return true if the the action was performed; else false.
  1237. */
  1238. public boolean doAccessibleAction(int i) {
  1239. if (i == 0) {
  1240. setPopupVisible(!isPopupVisible());
  1241. return true;
  1242. }
  1243. else {
  1244. return false;
  1245. }
  1246. }
  1247. /**
  1248. * Get the AccessibleSelection associated with this object. In the
  1249. * implementation of the Java Accessibility API for this class,
  1250. * return this object, which is responsible for implementing the
  1251. * AccessibleSelection interface on behalf of itself.
  1252. *
  1253. * @return this object
  1254. */
  1255. public AccessibleSelection getAccessibleSelection() {
  1256. return this;
  1257. }
  1258. /**
  1259. * Returns the number of Accessible children currently selected.
  1260. * If no children are selected, the return value will be 0.
  1261. *
  1262. * @return the number of items currently selected.
  1263. */
  1264. public int getAccessibleSelectionCount() {
  1265. Object o = JComboBox.this.getSelectedItem();
  1266. if (o != null) {
  1267. return 1;
  1268. } else {
  1269. return 0;
  1270. }
  1271. }
  1272. /**
  1273. * Returns an Accessible representing the specified selected child
  1274. * in the popup. If there isn't a selection, or there are
  1275. * fewer children selected than the integer passed in, the return
  1276. * value will be null.
  1277. * <p>Note that the index represents the i-th selected child, which
  1278. * is different from the i-th child.
  1279. *
  1280. * @param i the zero-based index of selected children
  1281. * @return the i-th selected child
  1282. * @see #getAccessibleSelectionCount
  1283. */
  1284. public Accessible getAccessibleSelection(int i) {
  1285. // Get the popup
  1286. Accessible a =
  1287. JComboBox.this.getUI().getAccessibleChild(JComboBox.this, 0);
  1288. if (a != null &&
  1289. a instanceof javax.swing.plaf.basic.ComboPopup) {
  1290. // get the popup list
  1291. JList list = ((javax.swing.plaf.basic.ComboPopup)a).getList();
  1292. // return the i-th selection in the popup list
  1293. AccessibleContext ac = list.getAccessibleContext();
  1294. if (ac != null) {
  1295. AccessibleSelection as = ac.getAccessibleSelection();
  1296. if (as != null) {
  1297. return as.getAccessibleSelection(i);
  1298. }
  1299. }
  1300. }
  1301. return null;
  1302. }
  1303. /**
  1304. * Determines if the current child of this object is selected.
  1305. *
  1306. * @return true if the current child of this object is selected;
  1307. * else false
  1308. * @param i the zero-based index of the child in this Accessible
  1309. * object.
  1310. * @see AccessibleContext#getAccessibleChild
  1311. */
  1312. public boolean isAccessibleChildSelected(int i) {
  1313. return JComboBox.this.getSelectedIndex() == i;
  1314. }
  1315. /**
  1316. * Adds the specified Accessible child of the object to the object's
  1317. * selection. If the object supports multiple selections,
  1318. * the specified child is added to any existing selection, otherwise
  1319. * it replaces any existing selection in the object. If the
  1320. * specified child is already selected, this method has no effect.
  1321. *
  1322. * @param i the zero-based index of the child
  1323. * @see AccessibleContext#getAccessibleChild
  1324. */
  1325. public void addAccessibleSelection(int i) {
  1326. JComboBox.this.setSelectedIndex(i);
  1327. }
  1328. /**
  1329. * Removes the specified child of the object from the object's
  1330. * selection. If the specified item isn't currently selected, this
  1331. * method has no effect.
  1332. *
  1333. * @param i the zero-based index of the child
  1334. * @see AccessibleContext#getAccessibleChild
  1335. */
  1336. public void removeAccessibleSelection(int i) {
  1337. if (JComboBox.this.getSelectedIndex() == i) {
  1338. clearAccessibleSelection();
  1339. }
  1340. }
  1341. /**
  1342. * Clears the selection in the object, so that no children in the
  1343. * object are selected.
  1344. */
  1345. public void clearAccessibleSelection() {
  1346. JComboBox.this.setSelectedIndex(-1);
  1347. }
  1348. /**
  1349. * Causes every child of the object to be selected
  1350. * if the object supports multiple selections.
  1351. */
  1352. public void selectAllAccessibleSelection() {
  1353. // do nothing since multiple selection is not supported
  1354. }
  1355. // public Accessible getAccessibleAt(Point p) {
  1356. // Accessible a = getAccessibleChild(1);
  1357. // if ( a != null ) {
  1358. // return a; // the editor
  1359. // }
  1360. // else {
  1361. // return getAccessibleChild(0); // the list
  1362. // }
  1363. // }
  1364. } // innerclass AccessibleJComboBox
  1365. }