1. /*
  2. * @(#)BasicComboBoxUI.java 1.170 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import javax.swing.*;
  11. import javax.accessibility.*;
  12. import javax.swing.FocusManager;
  13. import javax.swing.plaf.*;
  14. import javax.swing.border.*;
  15. import javax.swing.text.*;
  16. import javax.swing.event.*;
  17. import java.beans.PropertyChangeListener;
  18. import java.beans.PropertyChangeEvent;
  19. import sun.awt.AppContext;
  20. import sun.swing.DefaultLookup;
  21. import sun.swing.UIAction;
  22. /**
  23. * Basic UI implementation for JComboBox.
  24. * <p>
  25. * The combo box is a compound component which means that it is an agregate of
  26. * many simpler components. This class creates and manages the listeners
  27. * on the combo box and the combo box model. These listeners update the user
  28. * interface in response to changes in the properties and state of the combo box.
  29. * <p>
  30. * All event handling is handled by listener classes created with the
  31. * <code>createxxxListener()</code> methods and internal classes.
  32. * You can change the behavior of this class by overriding the
  33. * <code>createxxxListener()</code> methods and supplying your own
  34. * event listeners or subclassing from the ones supplied in this class.
  35. * <p>
  36. * For adding specific actions,
  37. * overide <code>installKeyboardActions</code> to add actions in response to
  38. * KeyStroke bindings. See the article <a href="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html">Keyboard Bindings in Swing</a>
  39. * at <a href="http://java.sun.com/products/jfc/tsc"><em>The Swing Connection</em></a>.
  40. *
  41. * @version 1.170 12/19/03
  42. * @author Arnaud Weber
  43. * @author Tom Santos
  44. * @author Mark Davidson
  45. */
  46. public class BasicComboBoxUI extends ComboBoxUI {
  47. protected JComboBox comboBox;
  48. /**
  49. * This protected field is implementation specific. Do not access directly
  50. * or override.
  51. */
  52. protected boolean hasFocus = false;
  53. // Control the selection behavior of the JComboBox when it is used
  54. // in the JTable DefaultCellEditor.
  55. private boolean isTableCellEditor = false;
  56. private static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";
  57. // This list is for drawing the current item in the combo box.
  58. protected JList listBox;
  59. // Used to render the currently selected item in the combo box.
  60. // It doesn't have anything to do with the popup's rendering.
  61. protected CellRendererPane currentValuePane = new CellRendererPane();
  62. // The implementation of ComboPopup that is used to show the popup.
  63. protected ComboPopup popup;
  64. // The Component that the ComboBoxEditor uses for editing
  65. protected Component editor;
  66. // The arrow button that invokes the popup.
  67. protected JButton arrowButton;
  68. // Listeners that are attached to the JComboBox
  69. /**
  70. * This protected field is implementation specific. Do not access directly
  71. * or override. Override the listener construction method instead.
  72. *
  73. * @see #createKeyListener
  74. */
  75. protected KeyListener keyListener;
  76. /**
  77. * This protected field is implementation specific. Do not access directly
  78. * or override. Override the listener construction method instead.
  79. *
  80. * @see #createFocusListener
  81. */
  82. protected FocusListener focusListener;
  83. /**
  84. * This protected field is implementation specific. Do not access directly
  85. * or override. Override the listener construction method instead.
  86. *
  87. * @see #createPropertyChangeListener
  88. */
  89. protected PropertyChangeListener propertyChangeListener;
  90. /**
  91. * This protected field is implementation specific. Do not access directly
  92. * or override. Override the listener construction method instead.
  93. *
  94. * @see #createItemListener
  95. */
  96. protected ItemListener itemListener;
  97. // Listeners that the ComboPopup produces.
  98. protected MouseListener popupMouseListener;
  99. protected MouseMotionListener popupMouseMotionListener;
  100. protected KeyListener popupKeyListener;
  101. // This is used for knowing when to cache the minimum preferred size.
  102. // If the data in the list changes, the cached value get marked for recalc.
  103. // Added to the current JComboBox model
  104. /**
  105. * This protected field is implementation specific. Do not access directly
  106. * or override. Override the listener construction method instead.
  107. *
  108. * @see #createListDataListener
  109. */
  110. protected ListDataListener listDataListener;
  111. /**
  112. * Implements all the Listeners needed by this class, all existing
  113. * listeners redirect to it.
  114. */
  115. private Handler handler;
  116. /**
  117. * The time factor to treate the series of typed alphanumeric key
  118. * as prefix for first letter navigation.
  119. */
  120. private long timeFactor = 1000L;
  121. /**
  122. * This is tricky, this variables is needed for DefaultKeySelectionManager
  123. * to take into account time factor.
  124. */
  125. private long lastTime = 0L;
  126. private long time = 0L;
  127. /**
  128. * The default key selection manager
  129. */
  130. JComboBox.KeySelectionManager keySelectionManager;
  131. // Flag for recalculating the minimum preferred size.
  132. protected boolean isMinimumSizeDirty = true;
  133. // Cached minimum preferred size.
  134. protected Dimension cachedMinimumSize = new Dimension( 0, 0 );
  135. // Flag for calculating the display size
  136. private boolean isDisplaySizeDirty = true;
  137. // Cached the size that the display needs to render the largest item
  138. private Dimension cachedDisplaySize = new Dimension( 0, 0 );
  139. // Key used for lookup of the DefaultListCellRenderer in the AppContext.
  140. private static final Object COMBO_UI_LIST_CELL_RENDERER_KEY =
  141. new StringBuffer("DefaultListCellRendererKey");
  142. static final StringBuffer HIDE_POPUP_KEY
  143. = new StringBuffer("HidePopupKey");
  144. // Used for calculating the default size.
  145. private static ListCellRenderer getDefaultListCellRenderer() {
  146. ListCellRenderer renderer = (ListCellRenderer)AppContext.
  147. getAppContext().get(COMBO_UI_LIST_CELL_RENDERER_KEY);
  148. if (renderer == null) {
  149. renderer = new DefaultListCellRenderer();
  150. AppContext.getAppContext().put(COMBO_UI_LIST_CELL_RENDERER_KEY,
  151. new DefaultListCellRenderer());
  152. }
  153. return renderer;
  154. }
  155. /**
  156. * Populates ComboBox's actions.
  157. */
  158. static void loadActionMap(LazyActionMap map) {
  159. map.put(new Actions(Actions.HIDE));
  160. map.put(new Actions(Actions.PAGE_DOWN));
  161. map.put(new Actions(Actions.PAGE_UP));
  162. map.put(new Actions(Actions.HOME));
  163. map.put(new Actions(Actions.END));
  164. map.put(new Actions(Actions.DOWN));
  165. map.put(new Actions(Actions.DOWN_2));
  166. map.put(new Actions(Actions.TOGGLE));
  167. map.put(new Actions(Actions.TOGGLE_2));
  168. map.put(new Actions(Actions.UP));
  169. map.put(new Actions(Actions.UP_2));
  170. map.put(new Actions(Actions.ENTER));
  171. }
  172. //========================
  173. // begin UI Initialization
  174. //
  175. public static ComponentUI createUI(JComponent c) {
  176. return new BasicComboBoxUI();
  177. }
  178. public void installUI( JComponent c ) {
  179. isMinimumSizeDirty = true;
  180. comboBox = (JComboBox)c;
  181. installDefaults();
  182. popup = createPopup();
  183. listBox = popup.getList();
  184. // Is this combo box a cell editor?
  185. Boolean inTable = (Boolean)c.getClientProperty(IS_TABLE_CELL_EDITOR );
  186. if (inTable != null) {
  187. isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
  188. }
  189. if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
  190. comboBox.setRenderer( createRenderer() );
  191. }
  192. if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) {
  193. comboBox.setEditor( createEditor() );
  194. }
  195. installListeners();
  196. installComponents();
  197. comboBox.setLayout( createLayoutManager() );
  198. comboBox.setRequestFocusEnabled( true );
  199. installKeyboardActions();
  200. comboBox.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);
  201. if (keySelectionManager == null || keySelectionManager instanceof UIResource) {
  202. keySelectionManager = new DefaultKeySelectionManager();
  203. }
  204. comboBox.setKeySelectionManager(keySelectionManager);
  205. }
  206. public void uninstallUI( JComponent c ) {
  207. setPopupVisible( comboBox, false);
  208. popup.uninstallingUI();
  209. uninstallKeyboardActions();
  210. comboBox.setLayout( null );
  211. uninstallComponents();
  212. uninstallListeners();
  213. uninstallDefaults();
  214. if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
  215. comboBox.setRenderer( null );
  216. }
  217. if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) {
  218. if(comboBox.getEditor().getEditorComponent().hasFocus()) {
  219. // Leave focus in JComboBox.
  220. comboBox.requestFocusInWindow();
  221. }
  222. comboBox.setEditor( null );
  223. }
  224. if (keySelectionManager instanceof UIResource) {
  225. comboBox.setKeySelectionManager(null);
  226. }
  227. handler = null;
  228. keyListener = null;
  229. focusListener = null;
  230. listDataListener = null;
  231. propertyChangeListener = null;
  232. popup = null;
  233. listBox = null;
  234. comboBox = null;
  235. }
  236. /**
  237. * Installs the default colors, default font, default renderer, and default
  238. * editor into the JComboBox.
  239. */
  240. protected void installDefaults() {
  241. LookAndFeel.installColorsAndFont( comboBox,
  242. "ComboBox.background",
  243. "ComboBox.foreground",
  244. "ComboBox.font" );
  245. LookAndFeel.installBorder( comboBox, "ComboBox.border" );
  246. LookAndFeel.installProperty( comboBox, "opaque", Boolean.TRUE);
  247. Long l = (Long)UIManager.get("ComboBox.timeFactor");
  248. timeFactor = (l!=null) ? l.longValue() : 1000L;
  249. }
  250. /**
  251. * Create and install the listeners for the combo box and its model.
  252. * This method is called when the UI is installed.
  253. */
  254. protected void installListeners() {
  255. if ( (itemListener = createItemListener()) != null) {
  256. comboBox.addItemListener( itemListener );
  257. }
  258. if ( (propertyChangeListener = createPropertyChangeListener()) != null ) {
  259. comboBox.addPropertyChangeListener( propertyChangeListener );
  260. }
  261. if ( (keyListener = createKeyListener()) != null ) {
  262. comboBox.addKeyListener( keyListener );
  263. }
  264. if ( (focusListener = createFocusListener()) != null ) {
  265. comboBox.addFocusListener( focusListener );
  266. }
  267. if ((popupMouseListener = popup.getMouseListener()) != null) {
  268. comboBox.addMouseListener( popupMouseListener );
  269. }
  270. if ((popupMouseMotionListener = popup.getMouseMotionListener()) != null) {
  271. comboBox.addMouseMotionListener( popupMouseMotionListener );
  272. }
  273. if ((popupKeyListener = popup.getKeyListener()) != null) {
  274. comboBox.addKeyListener(popupKeyListener);
  275. }
  276. if ( comboBox.getModel() != null ) {
  277. if ( (listDataListener = createListDataListener()) != null ) {
  278. comboBox.getModel().addListDataListener( listDataListener );
  279. }
  280. }
  281. }
  282. /**
  283. * Uninstalls the default colors, default font, default renderer, and default
  284. * editor into the JComboBox.
  285. */
  286. protected void uninstallDefaults() {
  287. LookAndFeel.installColorsAndFont( comboBox,
  288. "ComboBox.background",
  289. "ComboBox.foreground",
  290. "ComboBox.font" );
  291. LookAndFeel.uninstallBorder( comboBox );
  292. }
  293. /**
  294. * Remove the installed listeners from the combo box and its model.
  295. * The number and types of listeners removed and in this method should be
  296. * the same that was added in <code>installListeners</code>
  297. */
  298. protected void uninstallListeners() {
  299. if ( keyListener != null ) {
  300. comboBox.removeKeyListener( keyListener );
  301. }
  302. if ( itemListener != null) {
  303. comboBox.removeItemListener( itemListener );
  304. }
  305. if ( propertyChangeListener != null ) {
  306. comboBox.removePropertyChangeListener( propertyChangeListener );
  307. }
  308. if ( focusListener != null) {
  309. comboBox.removeFocusListener( focusListener );
  310. }
  311. if ( popupMouseListener != null) {
  312. comboBox.removeMouseListener( popupMouseListener );
  313. }
  314. if ( popupMouseMotionListener != null) {
  315. comboBox.removeMouseMotionListener( popupMouseMotionListener );
  316. }
  317. if (popupKeyListener != null) {
  318. comboBox.removeKeyListener(popupKeyListener);
  319. }
  320. if ( comboBox.getModel() != null ) {
  321. if ( listDataListener != null ) {
  322. comboBox.getModel().removeListDataListener( listDataListener );
  323. }
  324. }
  325. }
  326. /**
  327. * Creates the popup portion of the combo box.
  328. *
  329. * @return an instance of <code>ComboPopup</code>
  330. * @see ComboPopup
  331. */
  332. protected ComboPopup createPopup() {
  333. return new BasicComboPopup( comboBox );
  334. }
  335. /**
  336. * Creates a <code>KeyListener</code> which will be added to the
  337. * combo box. If this method returns null then it will not be added
  338. * to the combo box.
  339. *
  340. * @return an instance <code>KeyListener</code> or null
  341. */
  342. protected KeyListener createKeyListener() {
  343. return getHandler();
  344. }
  345. /**
  346. * Creates a <code>FocusListener</code> which will be added to the combo box.
  347. * If this method returns null then it will not be added to the combo box.
  348. *
  349. * @return an instance of a <code>FocusListener</code> or null
  350. */
  351. protected FocusListener createFocusListener() {
  352. return getHandler();
  353. }
  354. /**
  355. * Creates a list data listener which will be added to the
  356. * <code>ComboBoxModel</code>. If this method returns null then
  357. * it will not be added to the combo box model.
  358. *
  359. * @return an instance of a <code>ListDataListener</code> or null
  360. */
  361. protected ListDataListener createListDataListener() {
  362. return getHandler();
  363. }
  364. /**
  365. * Creates an <code>ItemListener</code> which will be added to the
  366. * combo box. If this method returns null then it will not
  367. * be added to the combo box.
  368. * <p>
  369. * Subclasses may override this method to return instances of their own
  370. * ItemEvent handlers.
  371. *
  372. * @return an instance of an <code>ItemListener</code> or null
  373. */
  374. protected ItemListener createItemListener() {
  375. return null;
  376. }
  377. /**
  378. * Creates a <code>PropertyChangeListener</code> which will be added to
  379. * the combo box. If this method returns null then it will not
  380. * be added to the combo box.
  381. *
  382. * @return an instance of a <code>PropertyChangeListener</code> or null
  383. */
  384. protected PropertyChangeListener createPropertyChangeListener() {
  385. return getHandler();
  386. }
  387. /**
  388. * Creates a layout manager for managing the components which make up the
  389. * combo box.
  390. *
  391. * @return an instance of a layout manager
  392. */
  393. protected LayoutManager createLayoutManager() {
  394. return getHandler();
  395. }
  396. /**
  397. * Creates the default renderer that will be used in a non-editiable combo
  398. * box. A default renderer will used only if a renderer has not been
  399. * explicitly set with <code>setRenderer</code>.
  400. *
  401. * @return a <code>ListCellRender</code> used for the combo box
  402. * @see javax.swing.JComboBox#setRenderer
  403. */
  404. protected ListCellRenderer createRenderer() {
  405. return new BasicComboBoxRenderer.UIResource();
  406. }
  407. /**
  408. * Creates the default editor that will be used in editable combo boxes.
  409. * A default editor will be used only if an editor has not been
  410. * explicitly set with <code>setEditor</code>.
  411. *
  412. * @return a <code>ComboBoxEditor</code> used for the combo box
  413. * @see javax.swing.JComboBox#setEditor
  414. */
  415. protected ComboBoxEditor createEditor() {
  416. return new BasicComboBoxEditor.UIResource();
  417. }
  418. /**
  419. * Returns the shared listener.
  420. */
  421. private Handler getHandler() {
  422. if (handler == null) {
  423. handler = new Handler();
  424. }
  425. return handler;
  426. }
  427. //
  428. // end UI Initialization
  429. //======================
  430. //======================
  431. // begin Inner classes
  432. //
  433. /**
  434. * This listener checks to see if the key event isn't a navigation key. If
  435. * it finds a key event that wasn't a navigation key it dispatches it to
  436. * JComboBox.selectWithKeyChar() so that it can do type-ahead.
  437. *
  438. * This public inner class should be treated as protected.
  439. * Instantiate it only within subclasses of
  440. * <code>BasicComboBoxUI</code>.
  441. */
  442. public class KeyHandler extends KeyAdapter {
  443. public void keyPressed( KeyEvent e ) {
  444. getHandler().keyPressed(e);
  445. }
  446. }
  447. /**
  448. * This listener hides the popup when the focus is lost. It also repaints
  449. * when focus is gained or lost.
  450. *
  451. * This public inner class should be treated as protected.
  452. * Instantiate it only within subclasses of
  453. * <code>BasicComboBoxUI</code>.
  454. */
  455. public class FocusHandler implements FocusListener {
  456. public void focusGained( FocusEvent e ) {
  457. getHandler().focusGained(e);
  458. }
  459. public void focusLost( FocusEvent e ) {
  460. getHandler().focusLost(e);
  461. }
  462. }
  463. /**
  464. * This listener watches for changes in the
  465. * <code>ComboBoxModel</code>.
  466. * <p>
  467. * This public inner class should be treated as protected.
  468. * Instantiate it only within subclasses of
  469. * <code>BasicComboBoxUI</code>.
  470. *
  471. * @see #createListDataListener
  472. */
  473. public class ListDataHandler implements ListDataListener {
  474. public void contentsChanged( ListDataEvent e ) {
  475. getHandler().contentsChanged(e);
  476. }
  477. public void intervalAdded( ListDataEvent e ) {
  478. getHandler().intervalAdded(e);
  479. }
  480. public void intervalRemoved( ListDataEvent e ) {
  481. getHandler().intervalRemoved(e);
  482. }
  483. }
  484. /**
  485. * This listener watches for changes to the selection in the
  486. * combo box.
  487. * <p>
  488. * This public inner class should be treated as protected.
  489. * Instantiate it only within subclasses of
  490. * <code>BasicComboBoxUI</code>.
  491. *
  492. * @see #createItemListener
  493. */
  494. public class ItemHandler implements ItemListener {
  495. // This class used to implement behavior which is now redundant.
  496. public void itemStateChanged(ItemEvent e) {}
  497. }
  498. /**
  499. * This listener watches for bound properties that have changed in the
  500. * combo box.
  501. * <p>
  502. * Subclasses which wish to listen to combo box property changes should
  503. * call the superclass methods to ensure that the combo box ui correctly
  504. * handles property changes.
  505. * <p>
  506. * This public inner class should be treated as protected.
  507. * Instantiate it only within subclasses of
  508. * <code>BasicComboBoxUI</code>.
  509. *
  510. * @see #createPropertyChangeListener
  511. */
  512. public class PropertyChangeHandler implements PropertyChangeListener {
  513. public void propertyChange(PropertyChangeEvent e) {
  514. getHandler().propertyChange(e);
  515. }
  516. }
  517. // Syncronizes the ToolTip text for the components within the combo box to be the
  518. // same value as the combo box ToolTip text.
  519. private void updateToolTipTextForChildren() {
  520. Component[] children = comboBox.getComponents();
  521. for ( int i = 0; i < children.length; ++i ) {
  522. if ( children[i] instanceof JComponent ) {
  523. ((JComponent)children[i]).setToolTipText( comboBox.getToolTipText() );
  524. }
  525. }
  526. }
  527. /**
  528. * This layout manager handles the 'standard' layout of combo boxes. It puts
  529. * the arrow button to the right and the editor to the left. If there is no
  530. * editor it still keeps the arrow button to the right.
  531. *
  532. * This public inner class should be treated as protected.
  533. * Instantiate it only within subclasses of
  534. * <code>BasicComboBoxUI</code>.
  535. */
  536. public class ComboBoxLayoutManager implements LayoutManager {
  537. public void addLayoutComponent(String name, Component comp) {}
  538. public void removeLayoutComponent(Component comp) {}
  539. public Dimension preferredLayoutSize(Container parent) {
  540. return getHandler().preferredLayoutSize(parent);
  541. }
  542. public Dimension minimumLayoutSize(Container parent) {
  543. return getHandler().minimumLayoutSize(parent);
  544. }
  545. public void layoutContainer(Container parent) {
  546. getHandler().layoutContainer(parent);
  547. }
  548. }
  549. //
  550. // end Inner classes
  551. //====================
  552. //===============================
  553. // begin Sub-Component Management
  554. //
  555. /**
  556. * Creates and initializes the components which make up the
  557. * aggregate combo box. This method is called as part of the UI
  558. * installation process.
  559. */
  560. protected void installComponents() {
  561. arrowButton = createArrowButton();
  562. comboBox.add( arrowButton );
  563. if (arrowButton != null) {
  564. configureArrowButton();
  565. }
  566. if ( comboBox.isEditable() ) {
  567. addEditor();
  568. }
  569. comboBox.add( currentValuePane );
  570. }
  571. /**
  572. * The aggregate components which compise the combo box are
  573. * unregistered and uninitialized. This method is called as part of the
  574. * UI uninstallation process.
  575. */
  576. protected void uninstallComponents() {
  577. if ( arrowButton != null ) {
  578. unconfigureArrowButton();
  579. }
  580. if ( editor != null ) {
  581. unconfigureEditor();
  582. }
  583. comboBox.removeAll(); // Just to be safe.
  584. arrowButton = null;
  585. }
  586. /**
  587. * This public method is implementation specific and should be private.
  588. * do not call or override. To implement a specific editor create a
  589. * custom <code>ComboBoxEditor</code>
  590. *
  591. * @see #createEditor
  592. * @see javax.swing.JComboBox#setEditor
  593. * @see javax.swing.ComboBoxEditor
  594. */
  595. public void addEditor() {
  596. removeEditor();
  597. editor = comboBox.getEditor().getEditorComponent();
  598. if ( editor != null ) {
  599. configureEditor();
  600. comboBox.add(editor);
  601. if(comboBox.isFocusOwner()) {
  602. // Switch focus to the editor component
  603. editor.requestFocusInWindow();
  604. }
  605. }
  606. }
  607. /**
  608. * This public method is implementation specific and should be private.
  609. * do not call or override.
  610. *
  611. * @see #addEditor
  612. */
  613. public void removeEditor() {
  614. if ( editor != null ) {
  615. unconfigureEditor();
  616. comboBox.remove( editor );
  617. editor = null;
  618. }
  619. }
  620. /**
  621. * This protected method is implementation specific and should be private.
  622. * do not call or override.
  623. *
  624. * @see #addEditor
  625. */
  626. protected void configureEditor() {
  627. // Should be in the same state as the combobox
  628. editor.setEnabled(comboBox.isEnabled());
  629. editor.setFont( comboBox.getFont() );
  630. if (focusListener != null) {
  631. editor.addFocusListener(focusListener);
  632. }
  633. editor.addFocusListener( getHandler() );
  634. comboBox.getEditor().addActionListener(getHandler());
  635. if(editor instanceof JComponent) {
  636. ((JComponent)editor).putClientProperty("doNotCancelPopup",
  637. HIDE_POPUP_KEY);
  638. ((JComponent)editor).setInheritsPopupMenu(true);
  639. }
  640. comboBox.configureEditor(comboBox.getEditor(),comboBox.getSelectedItem());
  641. }
  642. /**
  643. * This protected method is implementation specific and should be private.
  644. * Do not call or override.
  645. *
  646. * @see #addEditor
  647. */
  648. protected void unconfigureEditor() {
  649. if (focusListener != null) {
  650. editor.removeFocusListener(focusListener);
  651. }
  652. editor.removeFocusListener(getHandler());
  653. comboBox.getEditor().removeActionListener(getHandler());
  654. }
  655. /**
  656. * This public method is implementation specific and should be private. Do
  657. * not call or override.
  658. *
  659. * @see #createArrowButton
  660. */
  661. public void configureArrowButton() {
  662. if ( arrowButton != null ) {
  663. arrowButton.setEnabled( comboBox.isEnabled() );
  664. arrowButton.setRequestFocusEnabled(false);
  665. arrowButton.addMouseListener( popup.getMouseListener() );
  666. arrowButton.addMouseMotionListener( popup.getMouseMotionListener() );
  667. arrowButton.resetKeyboardActions();
  668. arrowButton.putClientProperty("doNotCancelPopup", HIDE_POPUP_KEY);
  669. arrowButton.setInheritsPopupMenu(true);
  670. }
  671. }
  672. /**
  673. * This public method is implementation specific and should be private. Do
  674. * not call or override.
  675. *
  676. * @see #createArrowButton
  677. */
  678. public void unconfigureArrowButton() {
  679. if ( arrowButton != null ) {
  680. arrowButton.removeMouseListener( popup.getMouseListener() );
  681. arrowButton.removeMouseMotionListener( popup.getMouseMotionListener() );
  682. }
  683. }
  684. /**
  685. * Creates an button which will be used as the control to show or hide
  686. * the popup portion of the combo box.
  687. *
  688. * @return a button which represents the popup control
  689. */
  690. protected JButton createArrowButton() {
  691. JButton button = new BasicArrowButton(BasicArrowButton.SOUTH,
  692. UIManager.getColor("ComboBox.buttonBackground"),
  693. UIManager.getColor("ComboBox.buttonShadow"),
  694. UIManager.getColor("ComboBox.buttonDarkShadow"),
  695. UIManager.getColor("ComboBox.buttonHighlight"));
  696. button.setName("ComboBox.arrowButton");
  697. return button;
  698. }
  699. //
  700. // end Sub-Component Management
  701. //===============================
  702. //================================
  703. // begin ComboBoxUI Implementation
  704. //
  705. /**
  706. * Tells if the popup is visible or not.
  707. */
  708. public boolean isPopupVisible( JComboBox c ) {
  709. return popup.isVisible();
  710. }
  711. /**
  712. * Hides the popup.
  713. */
  714. public void setPopupVisible( JComboBox c, boolean v ) {
  715. if ( v ) {
  716. popup.show();
  717. } else {
  718. popup.hide();
  719. }
  720. }
  721. /**
  722. * Determines if the JComboBox is focus traversable. If the JComboBox is editable
  723. * this returns false, otherwise it returns true.
  724. */
  725. public boolean isFocusTraversable( JComboBox c ) {
  726. return !comboBox.isEditable();
  727. }
  728. //
  729. // end ComboBoxUI Implementation
  730. //==============================
  731. //=================================
  732. // begin ComponentUI Implementation
  733. public void paint( Graphics g, JComponent c ) {
  734. hasFocus = comboBox.hasFocus();
  735. if ( !comboBox.isEditable() ) {
  736. Rectangle r = rectangleForCurrentValue();
  737. paintCurrentValueBackground(g,r,hasFocus);
  738. paintCurrentValue(g,r,hasFocus);
  739. }
  740. }
  741. public Dimension getPreferredSize( JComponent c ) {
  742. return getMinimumSize(c);
  743. }
  744. /**
  745. * The minumum size is the size of the display area plus insets plus the button.
  746. */
  747. public Dimension getMinimumSize( JComponent c ) {
  748. if ( !isMinimumSizeDirty ) {
  749. return new Dimension(cachedMinimumSize);
  750. }
  751. Dimension size = getDisplaySize();
  752. Insets insets = getInsets();
  753. size.height += insets.top + insets.bottom;
  754. int buttonSize = size.height - (insets.top + insets.bottom);
  755. size.width += insets.left + insets.right + buttonSize;
  756. cachedMinimumSize.setSize( size.width, size.height );
  757. isMinimumSizeDirty = false;
  758. return new Dimension(size);
  759. }
  760. public Dimension getMaximumSize( JComponent c ) {
  761. return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
  762. }
  763. // This is currently hacky...
  764. public int getAccessibleChildrenCount(JComponent c) {
  765. if ( comboBox.isEditable() ) {
  766. return 2;
  767. }
  768. else {
  769. return 1;
  770. }
  771. }
  772. // This is currently hacky...
  773. public Accessible getAccessibleChild(JComponent c, int i) {
  774. // 0 = the popup
  775. // 1 = the editor
  776. switch ( i ) {
  777. case 0:
  778. if ( popup instanceof Accessible ) {
  779. AccessibleContext ac = ((Accessible) popup).getAccessibleContext();
  780. ac.setAccessibleParent(comboBox);
  781. return(Accessible) popup;
  782. }
  783. break;
  784. case 1:
  785. if ( comboBox.isEditable()
  786. && (editor instanceof Accessible) ) {
  787. AccessibleContext ac = ((Accessible) editor).getAccessibleContext();
  788. ac.setAccessibleParent(comboBox);
  789. return(Accessible) editor;
  790. }
  791. break;
  792. }
  793. return null;
  794. }
  795. //
  796. // end ComponentUI Implementation
  797. //===============================
  798. //======================
  799. // begin Utility Methods
  800. //
  801. /**
  802. * Returns whether or not the supplied keyCode maps to a key that is used for
  803. * navigation. This is used for optimizing key input by only passing non-
  804. * navigation keys to the type-ahead mechanism. Subclasses should override this
  805. * if they change the navigation keys.
  806. */
  807. protected boolean isNavigationKey( int keyCode ) {
  808. return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN ||
  809. keyCode == KeyEvent.VK_KP_UP || keyCode == KeyEvent.VK_KP_DOWN;
  810. }
  811. private boolean isNavigationKey(int keyCode, int modifiers) {
  812. InputMap inputMap = comboBox.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  813. KeyStroke key = KeyStroke.getKeyStroke(keyCode, modifiers);
  814. if (inputMap != null && inputMap.get(key) != null) {
  815. return true;
  816. }
  817. return false;
  818. }
  819. /**
  820. * Selects the next item in the list. It won't change the selection if the
  821. * currently selected item is already the last item.
  822. */
  823. protected void selectNextPossibleValue() {
  824. int si;
  825. if ( isTableCellEditor ) {
  826. si = listBox.getSelectedIndex();
  827. }
  828. else {
  829. si = comboBox.getSelectedIndex();
  830. }
  831. if ( si < comboBox.getModel().getSize() - 1 ) {
  832. if ( isTableCellEditor ) {
  833. listBox.setSelectedIndex( si + 1 );
  834. listBox.ensureIndexIsVisible( si + 1 );
  835. }
  836. else {
  837. comboBox.setSelectedIndex(si+1);
  838. }
  839. comboBox.repaint();
  840. }
  841. }
  842. /**
  843. * Selects the previous item in the list. It won't change the selection if the
  844. * currently selected item is already the first item.
  845. */
  846. protected void selectPreviousPossibleValue() {
  847. int si;
  848. if ( isTableCellEditor ) {
  849. si = listBox.getSelectedIndex();
  850. }
  851. else {
  852. si = comboBox.getSelectedIndex();
  853. }
  854. if ( si > 0 ) {
  855. if ( isTableCellEditor ) {
  856. listBox.setSelectedIndex( si - 1 );
  857. listBox.ensureIndexIsVisible( si - 1 );
  858. }
  859. else {
  860. comboBox.setSelectedIndex(si-1);
  861. }
  862. comboBox.repaint();
  863. }
  864. }
  865. /**
  866. * Hides the popup if it is showing and shows the popup if it is hidden.
  867. */
  868. protected void toggleOpenClose() {
  869. setPopupVisible(comboBox, !isPopupVisible(comboBox));
  870. }
  871. /**
  872. * Returns the area that is reserved for drawing the currently selected item.
  873. */
  874. protected Rectangle rectangleForCurrentValue() {
  875. int width = comboBox.getWidth();
  876. int height = comboBox.getHeight();
  877. Insets insets = getInsets();
  878. int buttonSize = height - (insets.top + insets.bottom);
  879. if ( arrowButton != null ) {
  880. buttonSize = arrowButton.getWidth();
  881. }
  882. if(BasicGraphicsUtils.isLeftToRight(comboBox)) {
  883. return new Rectangle(insets.left, insets.top,
  884. width - (insets.left + insets.right + buttonSize),
  885. height - (insets.top + insets.bottom));
  886. }
  887. else {
  888. return new Rectangle(insets.left + buttonSize, insets.top,
  889. width - (insets.left + insets.right + buttonSize),
  890. height - (insets.top + insets.bottom));
  891. }
  892. }
  893. /**
  894. * Gets the insets from the JComboBox.
  895. */
  896. protected Insets getInsets() {
  897. return comboBox.getInsets();
  898. }
  899. //
  900. // end Utility Methods
  901. //====================
  902. //===============================
  903. // begin Painting Utility Methods
  904. //
  905. /**
  906. * Paints the currently selected item.
  907. */
  908. public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
  909. ListCellRenderer renderer = comboBox.getRenderer();
  910. Component c;
  911. if ( hasFocus && !isPopupVisible(comboBox) ) {
  912. c = renderer.getListCellRendererComponent( listBox,
  913. comboBox.getSelectedItem(),
  914. -1,
  915. true,
  916. false );
  917. }
  918. else {
  919. c = renderer.getListCellRendererComponent( listBox,
  920. comboBox.getSelectedItem(),
  921. -1,
  922. false,
  923. false );
  924. c.setBackground(UIManager.getColor("ComboBox.background"));
  925. }
  926. c.setFont(comboBox.getFont());
  927. if ( hasFocus && !isPopupVisible(comboBox) ) {
  928. c.setForeground(listBox.getSelectionForeground());
  929. c.setBackground(listBox.getSelectionBackground());
  930. }
  931. else {
  932. if ( comboBox.isEnabled() ) {
  933. c.setForeground(comboBox.getForeground());
  934. c.setBackground(comboBox.getBackground());
  935. }
  936. else {
  937. c.setForeground(DefaultLookup.getColor(
  938. comboBox, this, "ComboBox.disabledForeground", null));
  939. c.setBackground(DefaultLookup.getColor(
  940. comboBox, this, "ComboBox.disabledBackground", null));
  941. }
  942. }
  943. // Fix for 4238829: should lay out the JPanel.
  944. boolean shouldValidate = false;
  945. if (c instanceof JPanel) {
  946. shouldValidate = true;
  947. }
  948. currentValuePane.paintComponent(g,c,comboBox,bounds.x,bounds.y,
  949. bounds.width,bounds.height, shouldValidate);
  950. }
  951. /**
  952. * Paints the background of the currently selected item.
  953. */
  954. public void paintCurrentValueBackground(Graphics g,Rectangle bounds,boolean hasFocus) {
  955. Color t = g.getColor();
  956. if ( comboBox.isEnabled() )
  957. g.setColor(DefaultLookup.getColor(comboBox, this,
  958. "ComboBox.background", null));
  959. else
  960. g.setColor(DefaultLookup.getColor(comboBox, this,
  961. "ComboBox.disabledBackground", null));
  962. g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height);
  963. g.setColor(t);
  964. }
  965. /**
  966. * Repaint the currently selected item.
  967. */
  968. void repaintCurrentValue() {
  969. Rectangle r = rectangleForCurrentValue();
  970. comboBox.repaint(r.x,r.y,r.width,r.height);
  971. }
  972. //
  973. // end Painting Utility Methods
  974. //=============================
  975. //===============================
  976. // begin Size Utility Methods
  977. //
  978. /**
  979. * Return the default size of an empty display area of the combo box using
  980. * the current renderer and font.
  981. *
  982. * @return the size of an empty display area
  983. * @see #getDisplaySize
  984. */
  985. protected Dimension getDefaultSize() {
  986. // Calculates the height and width using the default text renderer
  987. Dimension d = getSizeForComponent(getDefaultListCellRenderer().getListCellRendererComponent(listBox, " ", -1, false, false));
  988. return new Dimension(d.width, d.height);
  989. }
  990. /**
  991. * Returns the calculated size of the display area. The display area is the
  992. * portion of the combo box in which the selected item is displayed. This
  993. * method will use the prototype display value if it has been set.
  994. * <p>
  995. * For combo boxes with a non trivial number of items, it is recommended to
  996. * use a prototype display value to significantly speed up the display
  997. * size calculation.
  998. *
  999. * @return the size of the display area calculated from the combo box items
  1000. * @see javax.swing.JComboBox#setPrototypeDisplayValue
  1001. */
  1002. protected Dimension getDisplaySize() {
  1003. if (!isDisplaySizeDirty) {
  1004. return new Dimension(cachedDisplaySize);
  1005. }
  1006. Dimension result = new Dimension();
  1007. ListCellRenderer renderer = comboBox.getRenderer();
  1008. if (renderer == null) {
  1009. renderer = new DefaultListCellRenderer();
  1010. }
  1011. Object prototypeValue = comboBox.getPrototypeDisplayValue();
  1012. if (prototypeValue != null) {
  1013. // Calculates the dimension based on the prototype value
  1014. result = getSizeForComponent(renderer.getListCellRendererComponent(listBox,
  1015. prototypeValue,
  1016. -1, false, false));
  1017. } else {
  1018. // Calculate the dimension by iterating over all the elements in the combo
  1019. // box list.
  1020. ComboBoxModel model = comboBox.getModel();
  1021. int modelSize = model.getSize();
  1022. Dimension d;
  1023. Component cpn;
  1024. if (modelSize > 0 ) {
  1025. for (int i = 0; i < modelSize ; i++ ) {
  1026. // Calculates the maximum height and width based on the largest
  1027. // element
  1028. d = getSizeForComponent(renderer.getListCellRendererComponent(listBox,
  1029. model.getElementAt(i),
  1030. -1, false, false));
  1031. result.width = Math.max(result.width,d.width);
  1032. result.height = Math.max(result.height,d.height);
  1033. }
  1034. } else {
  1035. result = getDefaultSize();
  1036. if (comboBox.isEditable()) {
  1037. result.width = 100;
  1038. }
  1039. }
  1040. }
  1041. if ( comboBox.isEditable() ) {
  1042. Dimension d = editor.getPreferredSize();
  1043. result.width = Math.max(result.width,d.width);
  1044. result.height = Math.max(result.height,d.height);
  1045. }
  1046. // Set the cached value
  1047. cachedDisplaySize.setSize(result.width, result.height);
  1048. isDisplaySizeDirty = false;
  1049. return result;
  1050. }
  1051. /**
  1052. * This has been refactored out in hopes that it may be investigated and
  1053. * simplified for the next major release. adding/removing
  1054. * the component to the currentValuePane and changing the font may be
  1055. * redundant operations.
  1056. */
  1057. private Dimension getSizeForComponent(Component comp) {
  1058. currentValuePane.add(comp);
  1059. comp.setFont(comboBox.getFont());
  1060. Dimension d = comp.getPreferredSize();
  1061. currentValuePane.remove(comp);
  1062. return d;
  1063. }
  1064. //
  1065. // end Size Utility Methods
  1066. //=============================
  1067. //=================================
  1068. // begin Keyboard Action Management
  1069. //
  1070. /**
  1071. * Adds keyboard actions to the JComboBox. Actions on enter and esc are already
  1072. * supplied. Add more actions as you need them.
  1073. */
  1074. protected void installKeyboardActions() {
  1075. InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  1076. SwingUtilities.replaceUIInputMap(comboBox, JComponent.
  1077. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
  1078. LazyActionMap.installLazyActionMap(comboBox, BasicComboBoxUI.class,
  1079. "ComboBox.actionMap");
  1080. }
  1081. InputMap getInputMap(int condition) {
  1082. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  1083. return (InputMap)DefaultLookup.get(comboBox, this,
  1084. "ComboBox.ancestorInputMap");
  1085. }
  1086. return null;
  1087. }
  1088. boolean isTableCellEditor() {
  1089. return isTableCellEditor;
  1090. }
  1091. /**
  1092. * Removes the focus InputMap and ActionMap.
  1093. */
  1094. protected void uninstallKeyboardActions() {
  1095. SwingUtilities.replaceUIInputMap(comboBox, JComponent.
  1096. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
  1097. SwingUtilities.replaceUIActionMap(comboBox, null);
  1098. }
  1099. //
  1100. // Actions
  1101. //
  1102. private static class Actions extends UIAction {
  1103. private static final String HIDE = "hidePopup";
  1104. private static final String DOWN = "selectNext";
  1105. private static final String DOWN_2 = "selectNext2";
  1106. private static final String TOGGLE = "togglePopup";
  1107. private static final String TOGGLE_2 = "spacePopup";
  1108. private static final String UP = "selectPrevious";
  1109. private static final String UP_2 = "selectPrevious2";
  1110. private static final String ENTER = "enterPressed";
  1111. private static final String PAGE_DOWN = "pageDownPassThrough";
  1112. private static final String PAGE_UP = "pageUpPassThrough";
  1113. private static final String HOME = "homePassThrough";
  1114. private static final String END = "endPassThrough";
  1115. Actions(String name) {
  1116. super(name);
  1117. }
  1118. public void actionPerformed( ActionEvent e ) {
  1119. String key = getName();
  1120. JComboBox comboBox = (JComboBox)e.getSource();
  1121. BasicComboBoxUI ui = (BasicComboBoxUI)BasicLookAndFeel.getUIOfType(
  1122. comboBox.getUI(), BasicComboBoxUI.class);
  1123. if (key == HIDE) {
  1124. comboBox.firePopupMenuCanceled();
  1125. comboBox.setPopupVisible(false);
  1126. }
  1127. else if (key == PAGE_DOWN || key == PAGE_UP ||
  1128. key == HOME || key == END) {
  1129. int index = getNextIndex(comboBox, key);
  1130. if (index >= 0 && index < comboBox.getItemCount()) {
  1131. comboBox.setSelectedIndex(index);
  1132. }
  1133. }
  1134. else if (key == DOWN) {
  1135. if (comboBox.isShowing() ) {
  1136. if ( comboBox.isPopupVisible() ) {
  1137. if (ui != null) {
  1138. ui.selectNextPossibleValue();
  1139. }
  1140. } else {
  1141. comboBox.setPopupVisible(true);
  1142. }
  1143. }
  1144. }
  1145. else if (key == DOWN_2) {
  1146. // Special case in which pressing the arrow keys will not
  1147. // make the popup appear - except for editable combo boxes.
  1148. if (comboBox.isShowing() ) {
  1149. if ( comboBox.isEditable() && !comboBox.isPopupVisible() ) {
  1150. comboBox.setPopupVisible(true);
  1151. } else {
  1152. if (ui != null) {
  1153. ui.selectNextPossibleValue();
  1154. }
  1155. }
  1156. }
  1157. }
  1158. else if (key == TOGGLE || key == TOGGLE_2) {
  1159. if (ui != null && (key == TOGGLE || !comboBox.isEditable())) {
  1160. if ( ui.isTableCellEditor() ) {
  1161. // Forces the selection of the list item if the
  1162. // combo box is in a JTable.
  1163. comboBox.setSelectedIndex(ui.popup.getList().
  1164. getSelectedIndex());
  1165. }
  1166. else {
  1167. comboBox.setPopupVisible(!comboBox.isPopupVisible());
  1168. }
  1169. }
  1170. }
  1171. else if (key == UP) {
  1172. if (ui != null) {
  1173. if (ui.isPopupVisible(comboBox)) {
  1174. ui.selectPreviousPossibleValue();
  1175. }
  1176. else if (DefaultLookup.getBoolean(comboBox, ui,
  1177. "ComboBox.showPopupOnNavigation", false)) {
  1178. ui.setPopupVisible(comboBox, true);
  1179. }
  1180. }
  1181. }
  1182. else if (key == UP_2) {
  1183. // Special case in which pressing the arrow keys will not
  1184. // make the popup appear - except for editable combo boxes.
  1185. if (comboBox.isShowing() && ui != null) {
  1186. if ( comboBox.isEditable() && !comboBox.isPopupVisible()) {
  1187. comboBox.setPopupVisible(true);
  1188. } else {
  1189. ui.selectPreviousPossibleValue();
  1190. }
  1191. }
  1192. }
  1193. else if (key == ENTER) {
  1194. if ( ui != null && ui.isTableCellEditor() ) {
  1195. // Forces the selection of the list item if the
  1196. // combo box is in a JTable.
  1197. comboBox.setSelectedIndex(ui.popup.getList().
  1198. getSelectedIndex());
  1199. }
  1200. else {
  1201. if (comboBox.isPopupVisible()) {
  1202. comboBox.setPopupVisible(false);
  1203. } else {
  1204. // Call the default button binding.
  1205. // This is a pretty messy way of passing an event through
  1206. // to the root pane.
  1207. JRootPane root = SwingUtilities.getRootPane(comboBox);
  1208. if (root != null) {
  1209. InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  1210. ActionMap am = root.getActionMap();
  1211. if (im != null && am != null) {
  1212. Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
  1213. if (obj != null) {
  1214. Action action = am.get(obj);
  1215. if (action != null) {
  1216. action.actionPerformed(new ActionEvent(
  1217. root, e.getID(), e.getActionCommand(),
  1218. e.getWhen(), e.getModifiers()));
  1219. }
  1220. }
  1221. }
  1222. }
  1223. }
  1224. }
  1225. }
  1226. }
  1227. private int getNextIndex(JComboBox comboBox, String key) {
  1228. if (key == PAGE_UP) {
  1229. int listHeight = comboBox.getMaximumRowCount();
  1230. int index = comboBox.getSelectedIndex() - listHeight;
  1231. return (index < 0 ? 0: index);
  1232. }
  1233. else if (key == PAGE_DOWN) {
  1234. int listHeight = comboBox.getMaximumRowCount();
  1235. int index = comboBox.getSelectedIndex() + listHeight;
  1236. int max = comboBox.getItemCount();
  1237. return (index < max ? index: max-1);
  1238. }
  1239. else if (key == HOME) {
  1240. return 0;
  1241. }
  1242. else if (key == END) {
  1243. return comboBox.getItemCount() - 1;
  1244. }
  1245. return comboBox.getSelectedIndex();
  1246. }
  1247. public boolean isEnabled(Object c) {
  1248. if (getName() == HIDE) {
  1249. return (c != null && ((JComboBox)c).isPopupVisible());
  1250. }
  1251. return true;
  1252. }
  1253. }
  1254. //
  1255. // end Keyboard Action Management
  1256. //===============================
  1257. //
  1258. // Shared Handler, implements all listeners
  1259. //
  1260. private class Handler implements ActionListener, FocusListener,
  1261. KeyListener, LayoutManager,
  1262. ListDataListener, PropertyChangeListener {
  1263. //
  1264. // PropertyChangeListener
  1265. //
  1266. public void propertyChange(PropertyChangeEvent e) {
  1267. String propertyName = e.getPropertyName();
  1268. JComboBox comboBox = (JComboBox)e.getSource();
  1269. if ( propertyName == "model" ) {
  1270. ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
  1271. ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
  1272. if ( oldModel != null && listDataListener != null ) {
  1273. oldModel.removeListDataListener( listDataListener );
  1274. }
  1275. if ( newModel != null && listDataListener != null ) {
  1276. newModel.addListDataListener( listDataListener );
  1277. }
  1278. if ( editor != null ) {
  1279. comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
  1280. }
  1281. isMinimumSizeDirty = true;
  1282. isDisplaySizeDirty = true;
  1283. comboBox.revalidate();
  1284. comboBox.repaint();
  1285. }
  1286. else if ( propertyName == "editor" && comboBox.isEditable() ) {
  1287. addEditor();
  1288. comboBox.revalidate();
  1289. }
  1290. else if ( propertyName == "editable" ) {
  1291. if ( comboBox.isEditable() ) {
  1292. comboBox.setRequestFocusEnabled( false );
  1293. addEditor();
  1294. } else {
  1295. comboBox.setRequestFocusEnabled( true );
  1296. removeEditor();
  1297. }
  1298. updateToolTipTextForChildren();
  1299. comboBox.revalidate();
  1300. }
  1301. else if ( propertyName == "enabled" ) {
  1302. boolean enabled = comboBox.isEnabled();
  1303. if ( editor != null )
  1304. editor.setEnabled(enabled);
  1305. if ( arrowButton != null )
  1306. arrowButton.setEnabled(enabled);
  1307. comboBox.repaint();
  1308. }
  1309. else if ( propertyName == "maximumRowCount" ) {
  1310. if ( isPopupVisible( comboBox ) ) {
  1311. setPopupVisible(comboBox, false);
  1312. setPopupVisible(comboBox, true);
  1313. }
  1314. }
  1315. else if ( propertyName == "font" ) {
  1316. listBox.setFont( comboBox.getFont() );
  1317. if ( editor != null ) {
  1318. editor.setFont( comboBox.getFont() );
  1319. }
  1320. isMinimumSizeDirty = true;
  1321. comboBox.validate();
  1322. }
  1323. else if ( propertyName == JComponent.TOOL_TIP_TEXT_KEY ) {
  1324. updateToolTipTextForChildren();
  1325. }
  1326. else if ( propertyName == BasicComboBoxUI.IS_TABLE_CELL_EDITOR ) {
  1327. Boolean inTable = (Boolean)e.getNewValue();
  1328. isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
  1329. }
  1330. else if (propertyName == "prototypeDisplayValue") {
  1331. isMinimumSizeDirty = true;
  1332. isDisplaySizeDirty = true;
  1333. comboBox.revalidate();
  1334. }
  1335. else if (propertyName == "renderer") {
  1336. isMinimumSizeDirty = true;
  1337. isDisplaySizeDirty = true;
  1338. comboBox.revalidate();
  1339. }
  1340. }
  1341. //
  1342. // KeyListener
  1343. //
  1344. // This listener checks to see if the key event isn't a navigation
  1345. // key. If it finds a key event that wasn't a navigation key it
  1346. // dispatches it to JComboBox.selectWithKeyChar() so that it can do
  1347. // type-ahead.
  1348. public void keyPressed( KeyEvent e ) {
  1349. if ( isNavigationKey(e.getKeyCode(), e.getModifiers()) ) {
  1350. lastTime = 0L;
  1351. } else if ( comboBox.isEnabled() && comboBox.getModel().getSize()!=0 &&
  1352. isTypeAheadKey( e ) ) {
  1353. time = e.getWhen();
  1354. if ( comboBox.selectWithKeyChar(e.getKeyChar()) ) {
  1355. e.consume();
  1356. }
  1357. }
  1358. }
  1359. public void keyTyped(KeyEvent e) {
  1360. }
  1361. public void keyReleased(KeyEvent e) {
  1362. }
  1363. private boolean isTypeAheadKey( KeyEvent e ) {
  1364. return !e.isAltDown() && !e.isControlDown() && !e.isMetaDown();
  1365. }
  1366. //
  1367. // FocusListener
  1368. //
  1369. // NOTE: The class is added to both the Editor and ComboBox.
  1370. // The combo box listener hides the popup when the focus is lost.
  1371. // It also repaints when focus is gained or lost.
  1372. public void focusGained( FocusEvent e ) {
  1373. if (e.getSource() == comboBox.getEditor().getEditorComponent()) {
  1374. return;
  1375. }
  1376. hasFocus = true;
  1377. comboBox.repaint();
  1378. if (comboBox.isEditable() && editor != null) {
  1379. editor.requestFocus();
  1380. }
  1381. }
  1382. public void focusLost( FocusEvent e ) {
  1383. if (e.getSource() == comboBox.getEditor().getEditorComponent()) {
  1384. ComboBoxEditor editor = comboBox.getEditor();
  1385. Object item = editor.getItem();
  1386. if (!e.isTemporary() && item != null &&
  1387. !item.equals( comboBox.getSelectedItem())) {
  1388. comboBox.actionPerformed
  1389. (new ActionEvent(editor, 0, "",
  1390. EventQueue.getMostRecentEventTime(), 0));
  1391. }
  1392. return;
  1393. }
  1394. hasFocus = false;
  1395. // GES, 980818:
  1396. // Note that the second check here is a workaround to bug
  1397. // 4168483. There is a bogus focusLost sent to the
  1398. // ComboBox with isTemporary false when a mediumweight menu
  1399. // is popped up. Until this is fixed in AWT, we make the
  1400. // tradeoff of not popping down mediumweight popups when
  1401. // the combobox loses focus. Although this means that the
  1402. // combobox does not remove such menus when you tab out,
  1403. // it is seen as more desirable than the alternative which
  1404. // is that mediumweight combobox menus dissappear immediately
  1405. // on popup, rendering them completely unusable.
  1406. if ( !e.isTemporary() && comboBox.isLightWeightPopupEnabled()) {
  1407. setPopupVisible(comboBox, false);
  1408. }
  1409. comboBox.repaint();
  1410. }
  1411. //
  1412. // ListDataListener
  1413. //
  1414. // This listener watches for changes in the ComboBoxModel
  1415. public void contentsChanged( ListDataEvent e ) {
  1416. if ( !(e.getIndex0() == -1 && e.getIndex1() == -1) ) {
  1417. isMinimumSizeDirty = true;
  1418. comboBox.revalidate();
  1419. }
  1420. // set the editor with the selected item since this
  1421. // is the event handler for a selected item change.
  1422. if (comboBox.isEditable() && editor != null) {
  1423. comboBox.configureEditor( comboBox.getEditor(),
  1424. comboBox.getSelectedItem() );
  1425. }
  1426. comboBox.repaint();
  1427. }
  1428. public void intervalAdded( ListDataEvent e ) {
  1429. isDisplaySizeDirty = true;
  1430. contentsChanged( e );
  1431. }
  1432. public void intervalRemoved( ListDataEvent e ) {
  1433. isDisplaySizeDirty = true;
  1434. contentsChanged( e );
  1435. }
  1436. //
  1437. // LayoutManager
  1438. //
  1439. // This layout manager handles the 'standard' layout of combo boxes.
  1440. // It puts the arrow button to the right and the editor to the left.
  1441. // If there is no editor it still keeps the arrow button to the right.
  1442. public void addLayoutComponent(String name, Component comp) {}
  1443. public void removeLayoutComponent(Component comp) {}
  1444. public Dimension preferredLayoutSize(Container parent) {
  1445. return parent.getPreferredSize();
  1446. }
  1447. public Dimension minimumLayoutSize(Container parent) {
  1448. return parent.getMinimumSize();
  1449. }
  1450. public void layoutContainer(Container parent) {
  1451. JComboBox cb = (JComboBox)parent;
  1452. int width = cb.getWidth();
  1453. int height = cb.getHeight();
  1454. Insets insets = getInsets();
  1455. int buttonSize = height - (insets.top + insets.bottom);
  1456. Rectangle cvb;
  1457. if ( arrowButton != null ) {
  1458. if(BasicGraphicsUtils.isLeftToRight(cb)) {
  1459. arrowButton.setBounds( width - (insets.right + buttonSize),
  1460. insets.top,
  1461. buttonSize, buttonSize);
  1462. }
  1463. else {
  1464. arrowButton.setBounds( insets.left, insets.top,
  1465. buttonSize, buttonSize);
  1466. }
  1467. }
  1468. if ( editor != null ) {
  1469. cvb = rectangleForCurrentValue();
  1470. editor.setBounds(cvb);
  1471. }
  1472. }
  1473. //
  1474. // ActionListener
  1475. //
  1476. // Fix for 4515752: Forward the Enter pressed on the
  1477. // editable combo box to the default button if the item has
  1478. // not changed.
  1479. // Note: This could depend on event ordering. The first ActionEvent
  1480. // from the editor may be handled by the JComboBox in which case, the
  1481. // enterPressed action will always be invoked.
  1482. public void actionPerformed(ActionEvent evt) {
  1483. Object item = comboBox.getEditor().getItem();
  1484. if (item != null && item.equals(comboBox.getSelectedItem())) {
  1485. ActionMap am = comboBox.getActionMap();
  1486. if (am != null) {
  1487. Action action = am.get("enterPressed");
  1488. if (action != null) {
  1489. action.actionPerformed(new ActionEvent(comboBox, evt.getID(),
  1490. evt.getActionCommand(),
  1491. evt.getModifiers()));
  1492. }
  1493. }
  1494. }
  1495. }
  1496. }
  1497. class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource {
  1498. private String prefix = "";
  1499. private String typedString = "";
  1500. public int selectionForKey(char aKey,ComboBoxModel aModel) {
  1501. if (lastTime == 0L) {
  1502. prefix = "";
  1503. typedString = "";
  1504. }
  1505. boolean startingFromSelection = true;
  1506. int startIndex = comboBox.getSelectedIndex();
  1507. if (time - lastTime < timeFactor) {
  1508. typedString += aKey;
  1509. if((prefix.length() == 1) && (aKey == prefix.charAt(0))) {
  1510. // Subsequent same key presses move the keyboard focus to the next
  1511. // object that starts with the same letter.
  1512. startIndex++;
  1513. } else {
  1514. prefix = typedString;
  1515. }
  1516. } else {
  1517. startIndex++;
  1518. typedString = "" + aKey;
  1519. prefix = typedString;
  1520. }
  1521. lastTime = time;
  1522. if (startIndex < 0 || startIndex >= aModel.getSize()) {
  1523. startingFromSelection = false;
  1524. startIndex = 0;
  1525. }
  1526. int index = listBox.getNextMatch(prefix, startIndex,
  1527. Position.Bias.Forward);
  1528. if (index < 0 && startingFromSelection) { // wrap
  1529. index = listBox.getNextMatch(prefix, 0,
  1530. Position.Bias.Forward);
  1531. }
  1532. return index;
  1533. }
  1534. }
  1535. }