1 /* JComboBox.java -- 2 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package javax.swing; 40 41 import gnu.java.lang.CPStringBuilder; 42 43 import java.awt.ItemSelectable; 44 import java.awt.event.ActionEvent; 45 import java.awt.event.ActionListener; 46 import java.awt.event.ItemEvent; 47 import java.awt.event.ItemListener; 48 import java.awt.event.KeyEvent; 49 import java.beans.PropertyChangeEvent; 50 import java.beans.PropertyChangeListener; 51 import java.util.Vector; 52 53 import javax.accessibility.Accessible; 54 import javax.accessibility.AccessibleAction; 55 import javax.accessibility.AccessibleContext; 56 import javax.accessibility.AccessibleRole; 57 import javax.accessibility.AccessibleSelection; 58 import javax.swing.event.ListDataEvent; 59 import javax.swing.event.ListDataListener; 60 import javax.swing.event.PopupMenuEvent; 61 import javax.swing.event.PopupMenuListener; 62 import javax.swing.plaf.ComboBoxUI; 63 import javax.swing.plaf.ComponentUI; 64 import javax.swing.plaf.basic.ComboPopup; 65 66 /** 67 * A component that allows a user to select any item in its list and 68 * displays the selected item to the user. JComboBox also can show/hide a 69 * popup menu containing its list of item whenever the mouse is pressed 70 * over it. 71 * 72 * @author Andrew Selkirk 73 * @author Olga Rodimina 74 * @author Robert Schuster 75 */ 76 public class JComboBox extends JComponent implements ItemSelectable, 77 ListDataListener, 78 ActionListener, 79 Accessible 80 { 81 82 private static final long serialVersionUID = 5654585963292734470L; 83 84 /** 85 * Classes implementing this interface are 86 * responsible for matching key characters typed by the user with combo 87 * box's items. 88 */ 89 public static interface KeySelectionManager 90 { selectionForKey(char aKey, ComboBoxModel aModel)91 int selectionForKey(char aKey, ComboBoxModel aModel); 92 } 93 94 /** 95 * Maximum number of rows that should be visible by default in the 96 * JComboBox's popup 97 */ 98 private static final int DEFAULT_MAXIMUM_ROW_COUNT = 8; 99 100 /** 101 * Data model used by JComboBox to keep track of its list data and currently 102 * selected element in the list. 103 */ 104 protected ComboBoxModel dataModel; 105 106 /** 107 * Renderer renders(paints) every object in the combo box list in its 108 * associated list cell. This ListCellRenderer is used only when this 109 * JComboBox is uneditable. 110 */ 111 protected ListCellRenderer renderer; 112 113 /** 114 * Editor that is responsible for editing an object in a combo box list. 115 */ 116 protected ComboBoxEditor editor; 117 118 /** 119 * Number of rows that will be visible in the JComboBox's popup. 120 */ 121 protected int maximumRowCount; 122 123 /** 124 * This field indicates if textfield of this JComboBox is editable or not. 125 */ 126 protected boolean isEditable; 127 128 /** 129 * This field is reference to the current selection of the combo box. 130 */ 131 protected Object selectedItemReminder; 132 133 /** 134 * keySelectionManager 135 */ 136 protected KeySelectionManager keySelectionManager; 137 138 /** 139 * This actionCommand is used in ActionEvent that is fired to JComboBox's 140 * ActionListeneres. 141 */ 142 protected String actionCommand; 143 144 /** 145 * This property indicates if heavyweight popup or lightweight popup will be 146 * used to diplay JComboBox's elements. 147 */ 148 protected boolean lightWeightPopupEnabled; 149 150 /** 151 * The action taken when new item is selected in the JComboBox 152 */ 153 private Action action; 154 155 /** 156 * since 1.4 If this field is set then comboBox's display area for the 157 * selected item will be set by default to this value. 158 */ 159 private Object prototypeDisplayValue; 160 161 /** 162 * Constructs JComboBox object with specified data model for it. 163 * <p>Note that the JComboBox will not change the value that 164 * is preselected by your ComboBoxModel implementation.</p> 165 * 166 * @param model Data model that will be used by this JComboBox to keep track 167 * of its list of items. 168 */ JComboBox(ComboBoxModel model)169 public JComboBox(ComboBoxModel model) 170 { 171 setEditable(false); 172 setEnabled(true); 173 setMaximumRowCount(DEFAULT_MAXIMUM_ROW_COUNT); 174 setModel(model); 175 setActionCommand("comboBoxChanged"); 176 177 lightWeightPopupEnabled = true; 178 isEditable = false; 179 180 updateUI(); 181 } 182 183 /** 184 * Constructs JComboBox with specified list of items. 185 * 186 * @param itemArray array containing list of items for this JComboBox 187 */ JComboBox(Object[] itemArray)188 public JComboBox(Object[] itemArray) 189 { 190 this(new DefaultComboBoxModel(itemArray)); 191 192 if (itemArray.length > 0) 193 setSelectedIndex(0); 194 } 195 196 /** 197 * Constructs JComboBox object with specified list of items. 198 * 199 * @param itemVector vector containing list of items for this JComboBox. 200 */ JComboBox(Vector<?> itemVector)201 public JComboBox(Vector<?> itemVector) 202 { 203 this(new DefaultComboBoxModel(itemVector)); 204 205 if (itemVector.size() > 0) 206 setSelectedIndex(0); 207 } 208 209 /** 210 * Constructor. Creates new empty JComboBox. ComboBox's data model is set to 211 * DefaultComboBoxModel. 212 */ JComboBox()213 public JComboBox() 214 { 215 this(new DefaultComboBoxModel()); 216 } 217 218 /** 219 * This method returns true JComboBox is editable and false otherwise 220 * 221 * @return boolean true if JComboBox is editable and false otherwise 222 */ isEditable()223 public boolean isEditable() 224 { 225 return isEditable; 226 } 227 228 /* 229 * This method adds ancestor listener to this JComboBox. 230 */ installAncestorListener()231 protected void installAncestorListener() 232 { 233 /* FIXME: Need to implement. 234 * 235 * Need to add ancestor listener to this JComboBox. This listener 236 * should close combo box's popup list of items whenever it 237 * receives an AncestorEvent. 238 */ 239 } 240 241 /** 242 * Set the "UI" property of the combo box, which is a look and feel class 243 * responsible for handling comboBox's input events and painting it. 244 * 245 * @param ui The new "UI" property 246 */ setUI(ComboBoxUI ui)247 public void setUI(ComboBoxUI ui) 248 { 249 super.setUI(ui); 250 } 251 252 /** 253 * This method sets this comboBox's UI to the UIManager's default for the 254 * current look and feel. 255 */ updateUI()256 public void updateUI() 257 { 258 setUI((ComboBoxUI) UIManager.getUI(this)); 259 } 260 261 /** 262 * This method returns the String identifier for the UI class to the used 263 * with the JComboBox. 264 * 265 * @return The String identifier for the UI class. 266 */ getUIClassID()267 public String getUIClassID() 268 { 269 return "ComboBoxUI"; 270 } 271 272 /** 273 * This method returns the UI used to display the JComboBox. 274 * 275 * @return The UI used to display the JComboBox. 276 */ getUI()277 public ComboBoxUI getUI() 278 { 279 return (ComboBoxUI) ui; 280 } 281 282 /** 283 * Set the data model for this JComboBox. This un-registers all listeners 284 * associated with the current model, and re-registers them with the new 285 * model. 286 * 287 * @param newDataModel The new data model for this JComboBox 288 */ setModel(ComboBoxModel newDataModel)289 public void setModel(ComboBoxModel newDataModel) 290 { 291 // dataModel is null if it this method is called from inside the constructors. 292 if (dataModel != null) 293 { 294 // Prevents unneccessary updates. 295 if (dataModel == newDataModel) 296 return; 297 298 // Removes itself (as DataListener) from the to-be-replaced model. 299 dataModel.removeListDataListener(this); 300 } 301 302 /* Adds itself as a DataListener to the new model. 303 * It is intentioned that this operation will fail with a NullPointerException if the 304 * caller delivered a null argument. 305 */ 306 newDataModel.addListDataListener(this); 307 308 // Stores old data model for event notification. 309 ComboBoxModel oldDataModel = dataModel; 310 dataModel = newDataModel; 311 selectedItemReminder = newDataModel.getSelectedItem(); 312 313 // Notifies the listeners of the model change. 314 firePropertyChange("model", oldDataModel, dataModel); 315 } 316 317 /** 318 * This method returns data model for this comboBox. 319 * 320 * @return ComboBoxModel containing items for this combo box. 321 */ getModel()322 public ComboBoxModel getModel() 323 { 324 return dataModel; 325 } 326 327 /** 328 * This method sets JComboBox's popup to be either lightweight or 329 * heavyweight. If 'enabled' is true then lightweight popup is used and 330 * heavyweight otherwise. By default lightweight popup is used to display 331 * this JComboBox's elements. 332 * 333 * @param enabled indicates if lightweight popup or heavyweight popup should 334 * be used to display JComboBox's elements. 335 */ setLightWeightPopupEnabled(boolean enabled)336 public void setLightWeightPopupEnabled(boolean enabled) 337 { 338 lightWeightPopupEnabled = enabled; 339 } 340 341 /** 342 * This method returns whether popup menu that is used to display list of 343 * combo box's item is lightWeight or not. 344 * 345 * @return boolean true if popup menu is lightweight and false otherwise. 346 */ isLightWeightPopupEnabled()347 public boolean isLightWeightPopupEnabled() 348 { 349 return lightWeightPopupEnabled; 350 } 351 352 /** 353 * This method sets editability of the combo box. If combo box is editable 354 * the user can choose component from the combo box list by typing 355 * component's name in the editor(JTextfield by default). Otherwise if not 356 * editable, the user should use the list to choose the component. This 357 * method fires PropertyChangeEvents to JComboBox's registered 358 * PropertyChangeListeners to indicate that 'editable' property of the 359 * JComboBox has changed. 360 * 361 * @param editable indicates if the JComboBox's textfield should be editable 362 * or not. 363 */ setEditable(boolean editable)364 public void setEditable(boolean editable) 365 { 366 if (isEditable != editable) 367 { 368 isEditable = editable; 369 firePropertyChange("editable", !isEditable, isEditable); 370 } 371 } 372 373 /** 374 * Sets number of rows that should be visible in this JComboBox's popup. If 375 * this JComboBox's popup has more elements that maximum number or rows 376 * then popup will have a scroll pane to allow users to view other 377 * elements. 378 * 379 * @param rowCount number of rows that will be visible in JComboBox's popup. 380 */ setMaximumRowCount(int rowCount)381 public void setMaximumRowCount(int rowCount) 382 { 383 if (maximumRowCount != rowCount) 384 { 385 int oldMaximumRowCount = maximumRowCount; 386 maximumRowCount = rowCount; 387 firePropertyChange("maximumRowCount", oldMaximumRowCount, 388 maximumRowCount); 389 } 390 } 391 392 /** 393 * This method returns number of rows visible in the JComboBox's list of 394 * items. 395 * 396 * @return int maximun number of visible rows in the JComboBox's list. 397 */ getMaximumRowCount()398 public int getMaximumRowCount() 399 { 400 return maximumRowCount; 401 } 402 403 /** 404 * This method sets cell renderer for this JComboBox that will be used to 405 * paint combo box's items. The Renderer should only be used only when 406 * JComboBox is not editable. In the case when JComboBox is editable the 407 * editor must be used. This method also fires PropertyChangeEvent when 408 * cellRendered for this JComboBox has changed. 409 * 410 * @param aRenderer cell renderer that will be used by this JComboBox to 411 * paint its elements. 412 */ setRenderer(ListCellRenderer aRenderer)413 public void setRenderer(ListCellRenderer aRenderer) 414 { 415 if (renderer != aRenderer) 416 { 417 ListCellRenderer oldRenderer = renderer; 418 renderer = aRenderer; 419 firePropertyChange("renderer", oldRenderer, renderer); 420 } 421 } 422 423 /** 424 * This method returns renderer responsible for rendering selected item in 425 * the combo box 426 * 427 * @return ListCellRenderer 428 */ getRenderer()429 public ListCellRenderer getRenderer() 430 { 431 return renderer; 432 } 433 434 /** 435 * Sets editor for this JComboBox 436 * 437 * @param newEditor ComboBoxEditor for this JComboBox. This method fires 438 * PropertyChangeEvent when 'editor' property is changed. 439 */ setEditor(ComboBoxEditor newEditor)440 public void setEditor(ComboBoxEditor newEditor) 441 { 442 if (editor == newEditor) 443 return; 444 445 if (editor != null) 446 editor.removeActionListener(this); 447 448 ComboBoxEditor oldEditor = editor; 449 editor = newEditor; 450 451 if (editor != null) 452 editor.addActionListener(this); 453 454 firePropertyChange("editor", oldEditor, editor); 455 } 456 457 /** 458 * Returns editor component that is responsible for displaying/editing 459 * selected item in the combo box. 460 * 461 * @return ComboBoxEditor 462 */ getEditor()463 public ComboBoxEditor getEditor() 464 { 465 return editor; 466 } 467 468 /** 469 * Forces combo box to select given item 470 * 471 * @param item element in the combo box to select. 472 */ setSelectedItem(Object item)473 public void setSelectedItem(Object item) 474 { 475 dataModel.setSelectedItem(item); 476 fireActionEvent(); 477 } 478 479 /** 480 * Returns currently selected item in the combo box. 481 * The result may be <code>null</code> to indicate that nothing is 482 * currently selected. 483 * 484 * @return element that is currently selected in this combo box. 485 */ getSelectedItem()486 public Object getSelectedItem() 487 { 488 return dataModel.getSelectedItem(); 489 } 490 491 /** 492 * Forces JComboBox to select component located in the given index in the 493 * combo box. 494 * <p>If the index is below -1 or exceeds the upper bound an 495 * <code>IllegalArgumentException</code> is thrown.<p/> 496 * <p>If the index is -1 then no item gets selected.</p> 497 * 498 * @param index index specifying location of the component that should be 499 * selected. 500 */ setSelectedIndex(int index)501 public void setSelectedIndex(int index) 502 { 503 if (index < -1 || index >= dataModel.getSize()) 504 // Fails because index is out of bounds. 505 throw new IllegalArgumentException("illegal index: " + index); 506 else 507 // Selects the item at the given index or clears the selection if the 508 // index value is -1. 509 setSelectedItem((index == -1) ? null : dataModel.getElementAt(index)); 510 } 511 512 /** 513 * Returns index of the item that is currently selected in the combo box. If 514 * no item is currently selected, then -1 is returned. 515 * <p> 516 * Note: For performance reasons you should minimize invocation of this 517 * method. If the data model is not an instance of 518 * <code>DefaultComboBoxModel</code> the complexity is O(n) where n is the 519 * number of elements in the combo box. 520 * </p> 521 * 522 * @return int Index specifying location of the currently selected item in the 523 * combo box or -1 if nothing is selected in the combo box. 524 */ getSelectedIndex()525 public int getSelectedIndex() 526 { 527 Object selectedItem = getSelectedItem(); 528 529 if (selectedItem != null) 530 { 531 if (dataModel instanceof DefaultComboBoxModel) 532 // Uses special method of DefaultComboBoxModel to retrieve the index. 533 return ((DefaultComboBoxModel) dataModel).getIndexOf(selectedItem); 534 else 535 { 536 // Iterates over all items to retrieve the index. 537 int size = dataModel.getSize(); 538 539 for (int i = 0; i < size; i++) 540 { 541 Object o = dataModel.getElementAt(i); 542 543 // XXX: Is special handling of ComparableS neccessary? 544 if ((selectedItem != null) ? selectedItem.equals(o) : o == null) 545 return i; 546 } 547 } 548 } 549 550 // returns that no item is currently selected 551 return -1; 552 } 553 554 /** 555 * Returns an object that is used as the display value when calculating the 556 * preferred size for the combo box. This value is, of course, never 557 * displayed anywhere. 558 * 559 * @return The prototype display value (possibly <code>null</code>). 560 * 561 * @since 1.4 562 * @see #setPrototypeDisplayValue(Object) 563 */ getPrototypeDisplayValue()564 public Object getPrototypeDisplayValue() 565 { 566 return prototypeDisplayValue; 567 } 568 569 /** 570 * Sets the object that is assumed to be the displayed item when calculating 571 * the preferred size for the combo box. A {@link PropertyChangeEvent} (with 572 * the name <code>prototypeDisplayValue</code>) is sent to all registered 573 * listeners. 574 * 575 * @param value the new value (<code>null</code> permitted). 576 * 577 * @since 1.4 578 * @see #getPrototypeDisplayValue() 579 */ setPrototypeDisplayValue(Object value)580 public void setPrototypeDisplayValue(Object value) 581 { 582 Object oldValue = prototypeDisplayValue; 583 prototypeDisplayValue = value; 584 firePropertyChange("prototypeDisplayValue", oldValue, value); 585 } 586 587 /** 588 * This method adds given element to this JComboBox. 589 * <p>A <code>RuntimeException</code> is thrown if the data model is not 590 * an instance of {@link MutableComboBoxModel}.</p> 591 * 592 * @param element element to add 593 */ addItem(Object element)594 public void addItem(Object element) 595 { 596 if (dataModel instanceof MutableComboBoxModel) 597 ((MutableComboBoxModel) dataModel).addElement(element); 598 else 599 throw new RuntimeException("Unable to add the item because the data " 600 + "model it is not an instance of " 601 + "MutableComboBoxModel."); 602 } 603 604 /** 605 * Inserts given element at the specified index to this JComboBox. 606 * <p>A <code>RuntimeException</code> is thrown if the data model is not 607 * an instance of {@link MutableComboBoxModel}.</p> 608 * 609 * @param element element to insert 610 * @param index position where to insert the element 611 */ insertItemAt(Object element, int index)612 public void insertItemAt(Object element, int index) 613 { 614 if (dataModel instanceof MutableComboBoxModel) 615 ((MutableComboBoxModel) dataModel).insertElementAt(element, index); 616 else 617 throw new RuntimeException("Unable to insert the item because the data " 618 + "model it is not an instance of " 619 + "MutableComboBoxModel."); 620 } 621 622 /** 623 * This method removes given element from this JComboBox. 624 * <p>A <code>RuntimeException</code> is thrown if the data model is not 625 * an instance of {@link MutableComboBoxModel}.</p> 626 * 627 * @param element element to remove 628 */ removeItem(Object element)629 public void removeItem(Object element) 630 { 631 if (dataModel instanceof MutableComboBoxModel) 632 ((MutableComboBoxModel) dataModel).removeElement(element); 633 else 634 throw new RuntimeException("Unable to remove the item because the data " 635 + "model it is not an instance of " 636 + "MutableComboBoxModel."); 637 } 638 639 /** 640 * This method remove element location in the specified index in the 641 * JComboBox. 642 * <p>A <code>RuntimeException</code> is thrown if the data model is not 643 * an instance of {@link MutableComboBoxModel}.</p> 644 * 645 * @param index index specifying position of the element to remove 646 */ removeItemAt(int index)647 public void removeItemAt(int index) 648 { 649 if (dataModel instanceof MutableComboBoxModel) 650 ((MutableComboBoxModel) dataModel).removeElementAt(index); 651 else 652 throw new RuntimeException("Unable to remove the item because the data " 653 + "model it is not an instance of " 654 + "MutableComboBoxModel."); 655 } 656 657 /** 658 * This method removes all elements from this JComboBox. 659 * <p> 660 * A <code>RuntimeException</code> is thrown if the data model is not an 661 * instance of {@link MutableComboBoxModel}. 662 * </p> 663 */ removeAllItems()664 public void removeAllItems() 665 { 666 if (dataModel instanceof DefaultComboBoxModel) 667 // Uses special method if we have a DefaultComboBoxModel. 668 ((DefaultComboBoxModel) dataModel).removeAllElements(); 669 else if (dataModel instanceof MutableComboBoxModel) 670 { 671 // Iterates over all items and removes each. 672 MutableComboBoxModel mcbm = (MutableComboBoxModel) dataModel; 673 674 // We intentionally remove the items backwards to support models which 675 // shift their content to the beginning (e.g. linked lists) 676 for (int i = mcbm.getSize() - 1; i >= 0; i--) 677 mcbm.removeElementAt(i); 678 } 679 else 680 throw new RuntimeException("Unable to remove the items because the data " 681 + "model it is not an instance of " 682 + "MutableComboBoxModel."); 683 } 684 685 /** 686 * This method displays popup with list of combo box's items on the screen 687 */ showPopup()688 public void showPopup() 689 { 690 setPopupVisible(true); 691 } 692 693 /** 694 * This method hides popup containing list of combo box's items 695 */ hidePopup()696 public void hidePopup() 697 { 698 setPopupVisible(false); 699 } 700 701 /** 702 * This method either displayes or hides the popup containing list of combo 703 * box's items. 704 * 705 * @param visible show popup if 'visible' is true and hide it otherwise 706 */ setPopupVisible(boolean visible)707 public void setPopupVisible(boolean visible) 708 { 709 getUI().setPopupVisible(this, visible); 710 } 711 712 /** 713 * Checks if popup is currently visible on the screen. 714 * 715 * @return boolean true if popup is visible and false otherwise 716 */ isPopupVisible()717 public boolean isPopupVisible() 718 { 719 return getUI().isPopupVisible(this); 720 } 721 722 /** 723 * This method sets actionCommand to the specified string. ActionEvent fired 724 * to this JComboBox registered ActionListeners will contain this 725 * actionCommand. 726 * 727 * @param aCommand new action command for the JComboBox's ActionEvent 728 */ setActionCommand(String aCommand)729 public void setActionCommand(String aCommand) 730 { 731 actionCommand = aCommand; 732 } 733 734 /** 735 * Returns actionCommand associated with the ActionEvent fired by the 736 * JComboBox to its registered ActionListeners. 737 * 738 * @return String actionCommand for the ActionEvent 739 */ getActionCommand()740 public String getActionCommand() 741 { 742 return actionCommand; 743 } 744 745 /** 746 * setAction 747 * 748 * @param a action to set 749 */ setAction(Action a)750 public void setAction(Action a) 751 { 752 Action old = action; 753 action = a; 754 configurePropertiesFromAction(action); 755 if (action != null) 756 // FIXME: remove from old action and add to new action 757 // PropertyChangeListener to listen to changes in the action 758 addActionListener(action); 759 } 760 761 /** 762 * This method returns Action that is invoked when selected item is changed 763 * in the JComboBox. 764 * 765 * @return Action 766 */ getAction()767 public Action getAction() 768 { 769 return action; 770 } 771 772 /** 773 * Configure properties of the JComboBox by reading properties of specified 774 * action. This method always sets the comboBox's "enabled" property to the 775 * value of the Action's "enabled" property. 776 * 777 * @param a An Action to configure the combo box from 778 */ configurePropertiesFromAction(Action a)779 protected void configurePropertiesFromAction(Action a) 780 { 781 if (a == null) 782 { 783 setEnabled(true); 784 setToolTipText(null); 785 } 786 else 787 { 788 setEnabled(a.isEnabled()); 789 setToolTipText((String) (a.getValue(Action.SHORT_DESCRIPTION))); 790 } 791 } 792 793 /** 794 * Creates PropertyChangeListener to listen for the changes in comboBox's 795 * action properties. 796 * 797 * @param action action to listen to for property changes 798 * 799 * @return a PropertyChangeListener that listens to changes in 800 * action properties. 801 */ createActionPropertyChangeListener(Action action)802 protected PropertyChangeListener createActionPropertyChangeListener(Action action) 803 { 804 return new PropertyChangeListener() 805 { 806 public void propertyChange(PropertyChangeEvent e) 807 { 808 Action act = (Action) (e.getSource()); 809 configurePropertiesFromAction(act); 810 } 811 }; 812 } 813 814 /** 815 * This method fires ItemEvent to this JComboBox's registered ItemListeners. 816 * This method is invoked when currently selected item in this combo box 817 * has changed. 818 * 819 * @param e the ItemEvent describing the change in the combo box's 820 * selection. 821 */ 822 protected void fireItemStateChanged(ItemEvent e) 823 { 824 ItemListener[] ll = getItemListeners(); 825 826 for (int i = 0; i < ll.length; i++) 827 ll[i].itemStateChanged(e); 828 } 829 830 /** 831 * This method fires ActionEvent to this JComboBox's registered 832 * ActionListeners. This method is invoked when user explicitly changes 833 * currently selected item. 834 */ 835 protected void fireActionEvent() 836 { 837 ActionListener[] ll = getActionListeners(); 838 839 for (int i = 0; i < ll.length; i++) 840 ll[i].actionPerformed(new ActionEvent(this, 841 ActionEvent.ACTION_PERFORMED, 842 actionCommand)); 843 } 844 845 /** 846 * Fires a popupMenuCanceled() event to all <code>PopupMenuListeners</code>. 847 * 848 * Note: This method is intended for use by plaf classes only. 849 */ 850 public void firePopupMenuCanceled() 851 { 852 PopupMenuListener[] listeners = getPopupMenuListeners(); 853 PopupMenuEvent e = new PopupMenuEvent(this); 854 for (int i = 0; i < listeners.length; i++) 855 listeners[i].popupMenuCanceled(e); 856 } 857 858 /** 859 * Fires a popupMenuWillBecomeInvisible() event to all 860 * <code>PopupMenuListeners</code>. 861 * 862 * Note: This method is intended for use by plaf classes only. 863 */ 864 public void firePopupMenuWillBecomeInvisible() 865 { 866 PopupMenuListener[] listeners = getPopupMenuListeners(); 867 PopupMenuEvent e = new PopupMenuEvent(this); 868 for (int i = 0; i < listeners.length; i++) 869 listeners[i].popupMenuWillBecomeInvisible(e); 870 } 871 872 /** 873 * Fires a popupMenuWillBecomeVisible() event to all 874 * <code>PopupMenuListeners</code>. 875 * 876 * Note: This method is intended for use by plaf classes only. 877 */ 878 public void firePopupMenuWillBecomeVisible() 879 { 880 PopupMenuListener[] listeners = getPopupMenuListeners(); 881 PopupMenuEvent e = new PopupMenuEvent(this); 882 for (int i = 0; i < listeners.length; i++) 883 listeners[i].popupMenuWillBecomeVisible(e); 884 } 885 886 /** 887 * This method is invoked whenever selected item changes in the combo box's 888 * data model. It fires ItemEvent and ActionEvent to all registered 889 * ComboBox's ItemListeners and ActionListeners respectively, indicating 890 * the change. 891 */ 892 protected void selectedItemChanged() 893 { 894 // Fire ItemEvent to indicated that previously selected item is now 895 // deselected 896 if (selectedItemReminder != null) 897 fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, 898 selectedItemReminder, 899 ItemEvent.DESELECTED)); 900 901 // Fire ItemEvent to indicate that new item is selected 902 Object newSelection = getSelectedItem(); 903 if (newSelection != null) 904 fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, 905 newSelection, ItemEvent.SELECTED)); 906 907 // Fire Action Event to JComboBox's registered listeners 908 fireActionEvent(); 909 910 selectedItemReminder = newSelection; 911 } 912 913 /** 914 * Returns Object array of size 1 containing currently selected element in 915 * the JComboBox. 916 * 917 * @return Object[] Object array of size 1 containing currently selected 918 * element in the JComboBox. 919 */ 920 public Object[] getSelectedObjects() 921 { 922 return new Object[] { getSelectedItem() }; 923 } 924 925 /** 926 * This method handles actionEvents fired by the ComboBoxEditor. It changes 927 * this JComboBox's selection to the new value currently in the editor and 928 * hides list of combo box items. 929 * 930 * @param e the ActionEvent 931 */ 932 public void actionPerformed(ActionEvent e) 933 { 934 setSelectedItem(getEditor().getItem()); 935 setPopupVisible(false); 936 } 937 938 /** 939 * This method selects item in this combo box that matches specified 940 * specified keyChar and returns true if such item is found. Otherwise 941 * false is returned. 942 * 943 * @param keyChar character indicating which item in the combo box should be 944 * selected. 945 * 946 * @return boolean true if item corresponding to the specified keyChar 947 * exists in the combo box. Otherwise false is returned. 948 */ 949 public boolean selectWithKeyChar(char keyChar) 950 { 951 if (keySelectionManager == null) 952 { 953 keySelectionManager = createDefaultKeySelectionManager(); 954 } 955 956 int index = keySelectionManager.selectionForKey(keyChar, getModel()); 957 boolean retVal = false; 958 if (index >= 0) 959 { 960 setSelectedIndex(index); 961 retVal = true; 962 } 963 return retVal; 964 } 965 966 /** 967 * The part of implementation of ListDataListener interface. This method is 968 * invoked when some items where added to the JComboBox's data model. 969 * 970 * @param event ListDataEvent describing the change 971 */ 972 public void intervalAdded(ListDataEvent event) 973 { 974 // FIXME: Need to implement 975 repaint(); 976 } 977 978 /** 979 * The part of implementation of ListDataListener interface. This method is 980 * invoked when some items where removed from the JComboBox's data model. 981 * 982 * @param event ListDataEvent describing the change. 983 */ 984 public void intervalRemoved(ListDataEvent event) 985 { 986 // FIXME: Need to implement 987 repaint(); 988 } 989 990 /** 991 * The part of implementation of ListDataListener interface. This method is 992 * invoked when contents of the JComboBox's data model changed. 993 * 994 * @param event ListDataEvent describing the change 995 */ 996 public void contentsChanged(ListDataEvent event) 997 { 998 // if first and last index of the given ListDataEvent are both -1, 999 // then it indicates that selected item in the combo box data model 1000 // have changed. 1001 if (event.getIndex0() == -1 && event.getIndex1() == -1) 1002 selectedItemChanged(); 1003 } 1004 1005 /** 1006 * This method disables or enables JComboBox. If the JComboBox is enabled, 1007 * then user is able to make item choice, otherwise if JComboBox is 1008 * disabled then user is not able to make a selection. 1009 * 1010 * @param enabled if 'enabled' is true then enable JComboBox and disable it 1011 */ 1012 public void setEnabled(boolean enabled) 1013 { 1014 boolean oldEnabled = super.isEnabled(); 1015 if (enabled != oldEnabled) 1016 { 1017 super.setEnabled(enabled); 1018 firePropertyChange("enabled", oldEnabled, enabled); 1019 } 1020 } 1021 1022 /** 1023 * This method initializes specified ComboBoxEditor to display given item. 1024 * 1025 * @param anEditor ComboBoxEditor to initialize 1026 * @param anItem Item that should displayed in the specified editor 1027 */ 1028 public void configureEditor(ComboBoxEditor anEditor, Object anItem) 1029 { 1030 anEditor.setItem(anItem); 1031 } 1032 1033 /** 1034 * This method is fired whenever a key is pressed with the combo box 1035 * in focus 1036 * 1037 * @param e The KeyEvent indicating which key was pressed. 1038 */ 1039 public void processKeyEvent(KeyEvent e) 1040 { 1041 if (e.getKeyCode() == KeyEvent.VK_TAB) 1042 setPopupVisible(false); 1043 else 1044 super.processKeyEvent(e); 1045 } 1046 1047 /** 1048 * setKeySelectionManager 1049 * 1050 * @param aManager 1051 */ 1052 public void setKeySelectionManager(KeySelectionManager aManager) 1053 { 1054 keySelectionManager = aManager; 1055 } 1056 1057 /** 1058 * getKeySelectionManager 1059 * 1060 * @return JComboBox.KeySelectionManager 1061 */ 1062 public KeySelectionManager getKeySelectionManager() 1063 { 1064 return keySelectionManager; 1065 } 1066 1067 /** 1068 * This method returns number of elements in this JComboBox 1069 * 1070 * @return int number of elements in this JComboBox 1071 */ 1072 public int getItemCount() 1073 { 1074 return dataModel.getSize(); 1075 } 1076 1077 /** 1078 * Returns elements located in the combo box at the given index. 1079 * 1080 * @param index index specifying location of the component to return. 1081 * 1082 * @return component in the combo box that is located in the given index. 1083 */ 1084 public Object getItemAt(int index) 1085 { 1086 return dataModel.getElementAt(index); 1087 } 1088 1089 /** 1090 * createDefaultKeySelectionManager 1091 * 1092 * @return KeySelectionManager 1093 */ 1094 protected KeySelectionManager createDefaultKeySelectionManager() 1095 { 1096 return new DefaultKeySelectionManager(); 1097 } 1098 1099 /** 1100 * Returns an implementation-dependent string describing the attributes of 1101 * this <code>JComboBox</code>. 1102 * 1103 * @return A string describing the attributes of this <code>JComboBox</code> 1104 * (never <code>null</code>). 1105 */ 1106 protected String paramString() 1107 { 1108 String superParamStr = super.paramString(); 1109 CPStringBuilder sb = new CPStringBuilder(); 1110 sb.append(",isEditable=").append(isEditable()); 1111 sb.append(",lightWeightPopupEnabled=").append(isLightWeightPopupEnabled()); 1112 sb.append(",maximumRowCount=").append(getMaximumRowCount()); 1113 1114 sb.append(",selectedItemReminder="); 1115 if (selectedItemReminder != null) 1116 sb.append(selectedItemReminder); 1117 return superParamStr + sb.toString(); 1118 } 1119 1120 /** 1121 * Returns the object that provides accessibility features for this 1122 * <code>JComboBox</code> component. 1123 * 1124 * @return The accessible context (an instance of 1125 * {@link AccessibleJComboBox}). 1126 */ 1127 public AccessibleContext getAccessibleContext() 1128 { 1129 if (accessibleContext == null) 1130 accessibleContext = new AccessibleJComboBox(); 1131 1132 return accessibleContext; 1133 } 1134 1135 /** 1136 * This methods adds specified ActionListener to this JComboBox. 1137 * 1138 * @param listener to add 1139 */ 1140 public void addActionListener(ActionListener listener) 1141 { 1142 listenerList.add(ActionListener.class, listener); 1143 } 1144 1145 /** 1146 * This method removes specified ActionListener from this JComboBox. 1147 * 1148 * @param listener ActionListener 1149 */ 1150 public void removeActionListener(ActionListener listener) 1151 { 1152 listenerList.remove(ActionListener.class, listener); 1153 } 1154 1155 /** 1156 * This method returns array of ActionListeners that are registered with 1157 * this JComboBox. 1158 * 1159 * @since 1.4 1160 */ 1161 public ActionListener[] getActionListeners() 1162 { 1163 return (ActionListener[]) getListeners(ActionListener.class); 1164 } 1165 1166 /** 1167 * This method registers given ItemListener with this JComboBox 1168 * 1169 * @param listener to remove 1170 */ 1171 public void addItemListener(ItemListener listener) 1172 { 1173 listenerList.add(ItemListener.class, listener); 1174 } 1175 1176 /** 1177 * This method unregisters given ItemListener from this JComboBox 1178 * 1179 * @param listener to remove 1180 */ 1181 public void removeItemListener(ItemListener listener) 1182 { 1183 listenerList.remove(ItemListener.class, listener); 1184 } 1185 1186 /** 1187 * This method returns array of ItemListeners that are registered with this 1188 * JComboBox. 1189 * 1190 * @since 1.4 1191 */ 1192 public ItemListener[] getItemListeners() 1193 { 1194 return (ItemListener[]) getListeners(ItemListener.class); 1195 } 1196 1197 /** 1198 * Adds PopupMenuListener to combo box to listen to the events fired by the 1199 * combo box's popup menu containing its list of items 1200 * 1201 * @param listener to add 1202 */ 1203 public void addPopupMenuListener(PopupMenuListener listener) 1204 { 1205 listenerList.add(PopupMenuListener.class, listener); 1206 } 1207 1208 /** 1209 * Removes PopupMenuListener to combo box to listen to the events fired by 1210 * the combo box's popup menu containing its list of items 1211 * 1212 * @param listener to add 1213 */ 1214 public void removePopupMenuListener(PopupMenuListener listener) 1215 { 1216 listenerList.remove(PopupMenuListener.class, listener); 1217 } 1218 1219 /** 1220 * Returns array of PopupMenuListeners that are registered with combo box. 1221 */ 1222 public PopupMenuListener[] getPopupMenuListeners() 1223 { 1224 return (PopupMenuListener[]) getListeners(PopupMenuListener.class); 1225 } 1226 1227 /** 1228 * Accessibility support for <code>JComboBox</code>. 1229 */ 1230 protected class AccessibleJComboBox extends AccessibleJComponent 1231 implements AccessibleAction, AccessibleSelection 1232 { 1233 private static final long serialVersionUID = 8217828307256675666L; 1234 1235 /** 1236 * @specnote This constructor was protected in 1.4, but made public 1237 * in 1.5. 1238 */ 1239 public AccessibleJComboBox() 1240 { 1241 // Nothing to do here. 1242 } 1243 1244 /** 1245 * Returns the number of accessible children of this object. The 1246 * implementation of AccessibleJComboBox delegates this call to the UI 1247 * of the associated JComboBox. 1248 * 1249 * @return the number of accessible children of this object 1250 * 1251 * @see ComponentUI#getAccessibleChildrenCount(JComponent) 1252 */ 1253 public int getAccessibleChildrenCount() 1254 { 1255 ComponentUI ui = getUI(); 1256 int count; 1257 if (ui != null) 1258 count = ui.getAccessibleChildrenCount(JComboBox.this); 1259 else 1260 count = super.getAccessibleChildrenCount(); 1261 return count; 1262 } 1263 1264 /** 1265 * Returns the number of accessible children of this object. The 1266 * implementation of AccessibleJComboBox delegates this call to the UI 1267 * of the associated JComboBox. 1268 * 1269 * @param index the index of the accessible child to fetch 1270 * 1271 * @return the number of accessible children of this object 1272 * 1273 * @see ComponentUI#getAccessibleChild(JComponent, int) 1274 */ 1275 public Accessible getAccessibleChild(int index) 1276 { 1277 ComponentUI ui = getUI(); 1278 Accessible child = null; 1279 if (ui != null) 1280 child = ui.getAccessibleChild(JComboBox.this, index); 1281 else 1282 child = super.getAccessibleChild(index); 1283 return child; 1284 } 1285 1286 /** 1287 * Returns the AccessibleSelection object associated with this object. 1288 * AccessibleJComboBoxes handle their selection themselves, so this 1289 * always returns <code>this</code>. 1290 * 1291 * @return the AccessibleSelection object associated with this object 1292 */ 1293 public AccessibleSelection getAccessibleSelection() 1294 { 1295 return this; 1296 } 1297 1298 /** 1299 * Returns the accessible selection from this AccssibleJComboBox. 1300 * 1301 * @param index the index of the selected child to fetch 1302 * 1303 * @return the accessible selection from this AccssibleJComboBox 1304 */ 1305 public Accessible getAccessibleSelection(int index) 1306 { 1307 // Get hold of the actual popup. 1308 Accessible popup = getUI().getAccessibleChild(JComboBox.this, 0); 1309 Accessible selected = null; 1310 if (popup != null && popup instanceof ComboPopup) 1311 { 1312 ComboPopup cPopup = (ComboPopup) popup; 1313 // Query the list for the currently selected child. 1314 JList l = cPopup.getList(); 1315 AccessibleContext listCtx = l.getAccessibleContext(); 1316 if (listCtx != null) 1317 { 1318 AccessibleSelection s = listCtx.getAccessibleSelection(); 1319 if (s != null) 1320 { 1321 selected = s.getAccessibleSelection(index); 1322 } 1323 } 1324 } 1325 return selected; 1326 } 1327 1328 /** 1329 * Returns <code>true</code> if the accessible child with the specified 1330 * <code>index</code> is selected, <code>false</code> otherwise. 1331 * 1332 * @param index the index of the accessible child 1333 * 1334 * @return <code>true</code> if the accessible child with the specified 1335 * <code>index</code> is selected, <code>false</code> otherwise 1336 */ 1337 public boolean isAccessibleChildSelected(int index) 1338 { 1339 return getSelectedIndex() == index; 1340 } 1341 1342 /** 1343 * Returns the accessible role for the <code>JComboBox</code> component. 1344 * 1345 * @return {@link AccessibleRole#COMBO_BOX}. 1346 */ 1347 public AccessibleRole getAccessibleRole() 1348 { 1349 return AccessibleRole.COMBO_BOX; 1350 } 1351 1352 /** 1353 * Returns the accessible action associated to this accessible object. 1354 * AccessibleJComboBox implements its own AccessibleAction, so this 1355 * method returns <code>this</code>. 1356 * 1357 * @return the accessible action associated to this accessible object 1358 */ 1359 public AccessibleAction getAccessibleAction() 1360 { 1361 return this; 1362 } 1363 1364 /** 1365 * Returns the description of the specified action. AccessibleJComboBox 1366 * implements 1 action (toggle the popup menu) and thus returns 1367 * <code>UIManager.getString("ComboBox.togglePopupText")</code> 1368 * 1369 * @param actionIndex the index of the action for which to return the 1370 * description 1371 * 1372 * @return the description of the specified action 1373 */ 1374 public String getAccessibleActionDescription(int actionIndex) 1375 { 1376 return UIManager.getString("ComboBox.togglePopupText"); 1377 } 1378 1379 /** 1380 * Returns the number of accessible actions that can be performed by 1381 * this object. AccessibleJComboBox implement s one accessible action 1382 * (toggle the popup menu), so this method always returns <code>1</code>. 1383 * 1384 * @return the number of accessible actions that can be performed by 1385 * this object 1386 */ 1387 public int getAccessibleActionCount() 1388 { 1389 return 1; 1390 } 1391 1392 /** 1393 * Performs the accessible action with the specified index. 1394 * AccessibleJComboBox has 1 accessible action 1395 * (<code>actionIndex == 0</code>), which is to toggle the 1396 * popup menu. All other action indices have no effect and return 1397 * <code<>false</code>. 1398 * 1399 * @param actionIndex the index of the action to perform 1400 * 1401 * @return <code>true</code> if the action has been performed, 1402 * <code>false</code> otherwise 1403 */ 1404 public boolean doAccessibleAction(int actionIndex) 1405 { 1406 boolean actionPerformed = false; 1407 if (actionIndex == 0) 1408 { 1409 setPopupVisible(! isPopupVisible()); 1410 actionPerformed = true; 1411 } 1412 return actionPerformed; 1413 } 1414 1415 /** 1416 * Returns the number of selected accessible children of this object. This 1417 * returns <code>1</code> if the combobox has a selected entry, 1418 * <code>0</code> otherwise. 1419 * 1420 * @return the number of selected accessible children of this object 1421 */ 1422 public int getAccessibleSelectionCount() 1423 { 1424 Object sel = getSelectedItem(); 1425 int count = 0; 1426 if (sel != null) 1427 count = 1; 1428 return count; 1429 } 1430 1431 /** 1432 * Sets the current selection to the specified <code>index</code>. 1433 * 1434 * @param index the index to set as selection 1435 */ 1436 public void addAccessibleSelection(int index) 1437 { 1438 setSelectedIndex(index); 1439 } 1440 1441 /** 1442 * Removes the specified index from the current selection. 1443 * 1444 * @param index the index to remove from the selection 1445 */ 1446 public void removeAccessibleSelection(int index) 1447 { 1448 if (getSelectedIndex() == index) 1449 clearAccessibleSelection(); 1450 } 1451 1452 /** 1453 * Clears the current selection. 1454 */ 1455 public void clearAccessibleSelection() 1456 { 1457 setSelectedIndex(-1); 1458 } 1459 1460 /** 1461 * Multiple selection is not supported by AccessibleJComboBox, so this 1462 * does nothing. 1463 */ 1464 public void selectAllAccessibleSelection() 1465 { 1466 // Nothing to do here. 1467 } 1468 } 1469 1470 private class DefaultKeySelectionManager 1471 implements KeySelectionManager 1472 { 1473 1474 public int selectionForKey(char aKey, ComboBoxModel aModel) 1475 { 1476 int selectedIndex = getSelectedIndex(); 1477 1478 // Start at currently selected item and iterate to end of list 1479 for (int i = selectedIndex + 1; i < aModel.getSize(); i++) 1480 { 1481 String nextItem = aModel.getElementAt(i).toString(); 1482 1483 if (nextItem.charAt(0) == aKey) 1484 return i; 1485 } 1486 1487 // Wrap to start of list if no match yet 1488 for (int i = 0; i <= selectedIndex; i++) 1489 { 1490 String nextItem = aModel.getElementAt(i).toString(); 1491 1492 if (nextItem.charAt(0) == aKey) 1493 return i; 1494 } 1495 1496 return - 1; 1497 } 1498 } 1499 } 1500