1. /*
  2. * @(#)BasicComboBoxUI.java 1.113 01/02/09
  3. *
  4. * Copyright 1997-2001 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.plaf.basic;
  11. import java.awt.*;
  12. import java.awt.event.*;
  13. import javax.swing.*;
  14. import javax.accessibility.*;
  15. import javax.swing.FocusManager;
  16. import javax.swing.plaf.*;
  17. import javax.swing.border.*;
  18. import javax.swing.text.*;
  19. import javax.swing.event.*;
  20. import java.beans.PropertyChangeListener;
  21. import java.beans.PropertyChangeEvent;
  22. /**
  23. * Basic UI for JComboBox. This class adds and removes components from the
  24. * JComboBox. The arrow button and the editor are managed by this object.
  25. * The popup menu is handled by BasicComboPopup. BasicComboPopup supplies
  26. * this class with a MouseListener, MouseMotionListener, and a KeyListener.
  27. * These listeners are added to the arrow button and the JComboBox by default.
  28. * Subclasses of BasicComboBoxUI should attach the listeners to whichever
  29. * components they like.
  30. *
  31. * installListeners() is where listeners get added to the JComboBox (and model).
  32. * configureEditor() is where listeners get added to the editor.
  33. * configureArrowButton() is where listeners get added to the arrow button.
  34. *
  35. * Inner classes for handling events:
  36. * FocusHandler
  37. * ItemHandler
  38. * ListDataHandler
  39. * PropertyChangeHandler
  40. * KeyHandler
  41. *
  42. *
  43. * @version 1.103 07/10/99
  44. * @author Arnaud Weber
  45. * @author Tom Santos
  46. */
  47. public class BasicComboBoxUI extends ComboBoxUI {
  48. protected JComboBox comboBox;
  49. protected boolean hasFocus = false;
  50. private boolean lightNav = false;
  51. private static final String LIGHTWEIGHT_KEYBOARD_NAVIGATION = "JComboBox.lightweightKeyboardNavigation";
  52. private static final String LIGHTWEIGHT_KEYBOARD_NAVIGATION_ON = "Lightweight";
  53. private static final String LIGHTWEIGHT_KEYBOARD_NAVIGATION_OFF = "Heavyweight";
  54. // This list is for drawing the current item in the combo box.
  55. protected JList listBox;
  56. // Used to render the currently selected item in the combo box.
  57. // It doesn't have anything to do with the popup's rendering.
  58. protected CellRendererPane currentValuePane = new CellRendererPane();
  59. // The implementation of ComboPopup that is used to show the popup.
  60. protected ComboPopup popup;
  61. // The Component that the ComboBoxEditor uses for editing
  62. protected Component editor;
  63. // The arrow button that invokes the popup.
  64. protected JButton arrowButton;
  65. // Listeners that are attached to the JComboBox
  66. protected KeyListener keyListener;
  67. protected FocusListener focusListener;
  68. FocusListener editorFocusListener;
  69. protected ItemListener itemListener;
  70. protected PropertyChangeListener propertyChangeListener;
  71. // Listeners that the ComboPopup produces.
  72. // These get attached to any component that wishes to invoke the popup.
  73. protected MouseListener popupMouseListener;
  74. protected MouseMotionListener popupMouseMotionListener;
  75. protected KeyListener popupKeyListener;
  76. // This is used for knowing when to cache the minimum preferred size.
  77. // If the data in the list changes, the cached value get marked for recalc.
  78. protected ListDataListener listDataListener;
  79. // Flag for recalculating the minimum preferred size.
  80. protected boolean isMinimumSizeDirty = true;
  81. // Cached minimum preferred size.
  82. protected Dimension cachedMinimumSize = new Dimension( 0, 0 );
  83. // Cached the size that the display needs to render the largest item
  84. Dimension cachedDisplaySize = new Dimension( 0, 0 );
  85. // Used for calculating the default size.
  86. static DefaultListCellRenderer textRenderer = new DefaultListCellRenderer();
  87. //========================
  88. // begin UI Initialization
  89. //
  90. public static ComponentUI createUI(JComponent c) {
  91. return new BasicComboBoxUI();
  92. }
  93. public void installUI( JComponent c ) {
  94. isMinimumSizeDirty = true;
  95. comboBox = (JComboBox)c;
  96. installDefaults();
  97. popup = createPopup();
  98. listBox = popup.getList();
  99. Object keyNav = c.getClientProperty( LIGHTWEIGHT_KEYBOARD_NAVIGATION );
  100. if ( keyNav != null ) {
  101. if ( keyNav.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_ON ) ) {
  102. lightNav = true;
  103. }
  104. else if ( keyNav.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_OFF ) ) {
  105. lightNav = false;
  106. }
  107. }
  108. if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
  109. comboBox.setRenderer( createRenderer() );
  110. }
  111. if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) {
  112. comboBox.setEditor( createEditor() );
  113. }
  114. installComponents();
  115. installListeners();
  116. if ( arrowButton != null ) {
  117. configureArrowButton();
  118. }
  119. comboBox.setLayout( createLayoutManager() );
  120. comboBox.setRequestFocusEnabled( true );
  121. // An invokeLater() was used here because updateComponentTree() resets
  122. // our sub-components after this method is completed. By delaying, we
  123. // can set what we need after updateComponentTree() has set all of the
  124. // values to defaults.
  125. Runnable initializer = new Runnable() {
  126. public void run(){
  127. // This test for comboBox being null is required because it's possible for the UI
  128. // to become uninstalled before this block of code is executed.
  129. if ( comboBox != null ) {
  130. if ( editor != null ) {
  131. editor.setFont( comboBox.getFont() );
  132. }
  133. installKeyboardActions();
  134. }
  135. }
  136. };
  137. SwingUtilities.invokeLater( initializer );
  138. }
  139. public void uninstallUI( JComponent c ) {
  140. setPopupVisible( comboBox, false);
  141. popup.uninstallingUI();
  142. uninstallKeyboardActions();
  143. comboBox.setLayout( null );
  144. uninstallComponents();
  145. uninstallListeners();
  146. uninstallDefaults();
  147. if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
  148. comboBox.setRenderer( null );
  149. }
  150. if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) {
  151. comboBox.setEditor( null );
  152. }
  153. keyListener = null;
  154. focusListener = null;
  155. listDataListener = null;
  156. popupKeyListener = null;
  157. popupMouseListener = null;
  158. popupMouseMotionListener = null;
  159. propertyChangeListener = null;
  160. editorFocusListener = null;
  161. popup = null;
  162. listBox = null;
  163. comboBox = null;
  164. }
  165. /**
  166. * Installs the default colors, default font, default renderer, and default
  167. * editor into the JComboBox.
  168. */
  169. protected void installDefaults() {
  170. LookAndFeel.installColorsAndFont( comboBox,
  171. "ComboBox.background",
  172. "ComboBox.foreground",
  173. "ComboBox.font" );
  174. LookAndFeel.installBorder( comboBox, "ComboBox.border" );
  175. }
  176. /**
  177. * Attaches listeners to the JComboBox and JComboBoxModel.
  178. */
  179. protected void installListeners() {
  180. if ( (itemListener = createItemListener()) != null ) {
  181. comboBox.addItemListener( itemListener );
  182. }
  183. if ( (propertyChangeListener = createPropertyChangeListener()) != null ) {
  184. comboBox.addPropertyChangeListener( propertyChangeListener );
  185. }
  186. if ( (keyListener = createKeyListener()) != null ) {
  187. comboBox.addKeyListener( keyListener );
  188. }
  189. if ( (focusListener = createFocusListener()) != null ) {
  190. comboBox.addFocusListener( focusListener );
  191. }
  192. if ( (popupMouseListener = popup.getMouseListener()) != null ) {
  193. comboBox.addMouseListener( popupMouseListener );
  194. }
  195. if ( (popupMouseMotionListener = popup.getMouseMotionListener()) != null ) {
  196. comboBox.addMouseMotionListener( popupMouseMotionListener );
  197. }
  198. if ( (popupKeyListener = popup.getKeyListener()) != null ) {
  199. comboBox.addKeyListener( popupKeyListener );
  200. }
  201. if ( comboBox.getModel() != null ) {
  202. if ( (listDataListener = createListDataListener()) != null ) {
  203. comboBox.getModel().addListDataListener( listDataListener );
  204. }
  205. }
  206. }
  207. /**
  208. * Uninstalls the default colors, default font, default renderer, and default
  209. * editor into the JComboBox.
  210. */
  211. protected void uninstallDefaults() {
  212. LookAndFeel.installColorsAndFont( comboBox,
  213. "ComboBox.background",
  214. "ComboBox.foreground",
  215. "ComboBox.font" );
  216. LookAndFeel.uninstallBorder( comboBox );
  217. }
  218. /**
  219. * Removes listeners from the JComboBox and JComboBoxModel.
  220. */
  221. protected void uninstallListeners() {
  222. if ( keyListener != null ) {
  223. comboBox.removeKeyListener( keyListener );
  224. }
  225. if ( itemListener != null ) {
  226. comboBox.removeItemListener( itemListener );
  227. }
  228. if ( propertyChangeListener != null ) {
  229. comboBox.removePropertyChangeListener( propertyChangeListener );
  230. }
  231. if ( focusListener != null ) {
  232. comboBox.removeFocusListener( focusListener );
  233. }
  234. if ( popupMouseListener != null ) {
  235. comboBox.removeMouseListener( popupMouseListener );
  236. }
  237. if ( popupMouseMotionListener != null ) {
  238. comboBox.removeMouseMotionListener( popupMouseMotionListener );
  239. }
  240. if ( popupKeyListener != null ) {
  241. comboBox.removeKeyListener( popupKeyListener );
  242. }
  243. if ( comboBox.getModel() != null ) {
  244. if ( listDataListener != null ) {
  245. comboBox.getModel().removeListDataListener( listDataListener );
  246. }
  247. }
  248. }
  249. /**
  250. * Creates an implementation of the ComboPopup interface.
  251. * Returns an instance of BasicComboPopup.
  252. */
  253. protected ComboPopup createPopup() {
  254. BasicComboPopup popup = new BasicComboPopup( comboBox );
  255. popup.getAccessibleContext().setAccessibleParent(comboBox);
  256. return popup;
  257. }
  258. /**
  259. * Creates the key listener for handling type-ahead.
  260. * Returns an instance of BasicComboBoxUI$KeyHandler.
  261. */
  262. protected KeyListener createKeyListener() {
  263. return new KeyHandler();
  264. }
  265. /**
  266. * Creates the focus listener that hides the popup when the focus is lost.
  267. * Returns an instance of BasicComboBoxUI$FocusHandler.
  268. */
  269. protected FocusListener createFocusListener() {
  270. return new FocusHandler();
  271. }
  272. /**
  273. * Creates the list data listener that is used for caching the preferred sizes.
  274. * Returns an instance of BasicComboBoxUI$ListDataHandler.
  275. */
  276. protected ListDataListener createListDataListener() {
  277. return new ListDataHandler();
  278. }
  279. /**
  280. * Creates the item listener that watches for updates in the current selection
  281. * so that it can update the display.
  282. * Returns an instance of BasicComboBoxUI$ItemHandler.
  283. */
  284. protected ItemListener createItemListener() {
  285. return new ItemHandler();
  286. }
  287. /**
  288. * Creates the list data listener that is used for caching the preferred sizes.
  289. * Returns an instance of BasicComboBoxUI$PropertyChangeHandler.
  290. */
  291. protected PropertyChangeListener createPropertyChangeListener() {
  292. return new PropertyChangeHandler();
  293. }
  294. /**
  295. * Creates the standard combo box layout manager that has the arrow button to
  296. * the right and the editor to the left.
  297. * Returns an instance of BasicComboBoxUI$ComboBoxLayoutManager.
  298. */
  299. protected LayoutManager createLayoutManager() {
  300. return new ComboBoxLayoutManager();
  301. }
  302. /**
  303. * Creates the renderer that is to be used in the combo box. This method only gets called if
  304. * a custom renderer has nto already been installed in the JComboBox.
  305. */
  306. protected ListCellRenderer createRenderer() {
  307. return new BasicComboBoxRenderer.UIResource();
  308. }
  309. /**
  310. * Creates the editor that is to be used in editable combo boxes. This method only gets called if
  311. * a custom editor has not already been installed in the JComboBox.
  312. */
  313. protected ComboBoxEditor createEditor() {
  314. return new BasicComboBoxEditor.UIResource();
  315. }
  316. //
  317. // end UI Initialization
  318. //======================
  319. //======================
  320. // begin Inner classes
  321. //
  322. /**
  323. * This listener checks to see if the key event isn't a navigation key. If
  324. * it finds a key event that wasn't a navigation key it dispatches it to
  325. * JComboBox.selectWithKeyChar() so that it can do type-ahead.
  326. *
  327. * This inner class is marked "public" due to a compiler bug.
  328. * This class should be treated as a "protected" inner class.
  329. * Instantiate it only within subclasses of <FooUI>.
  330. */
  331. public class KeyHandler extends KeyAdapter {
  332. public void keyPressed( KeyEvent e ) {
  333. if ( comboBox.isEnabled() &&
  334. !isNavigationKey( e.getKeyCode() ) &&
  335. isTypeAheadKey( e ) ) {
  336. if ( comboBox.selectWithKeyChar(e.getKeyChar()) ) {
  337. e.consume();
  338. }
  339. }
  340. }
  341. boolean isTypeAheadKey( KeyEvent e ) {
  342. return !e.isAltDown() && !e.isControlDown() && !e.isMetaDown();
  343. }
  344. }
  345. /**
  346. * This listener hides the popup when the focus is lost. It also repaints
  347. * when focus is gained or lost.
  348. *
  349. * This inner class is marked "public" due to a compiler bug.
  350. * This class should be treated as a "protected" inner class.
  351. * Instantiate it only within subclasses of <FooUI>.
  352. */
  353. public class FocusHandler implements FocusListener {
  354. public void focusGained( FocusEvent e ) {
  355. hasFocus = true;
  356. comboBox.repaint();
  357. // Notify assistive technologies that the combo box
  358. // gained focus.
  359. if (comboBox instanceof Accessible) {
  360. AccessibleContext ac =
  361. ((Accessible)comboBox).getAccessibleContext();
  362. if (ac != null) {
  363. ac.firePropertyChange(
  364. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  365. null, AccessibleState.FOCUSED);
  366. }
  367. }
  368. }
  369. public void focusLost( FocusEvent e ) {
  370. hasFocus = false;
  371. // GES, 980818:
  372. // Note that the second check here is a workaround to bug
  373. // 4168483. There is a bogus focusLost sent to the
  374. // ComboBox with isTemporary false when a mediumweight menu
  375. // is popped up. Until this is fixed in AWT, we make the
  376. // tradeoff of not popping down mediumweight popups when
  377. // the combobox loses focus. Although this means that the
  378. // combobox does not remove such menus when you tab out,
  379. // it is seen as more desirable than the alternative which
  380. // is that mediumweight combobox menus dissappear immediately
  381. // on popup, rendering them completely unusable.
  382. if ( !e.isTemporary() && comboBox.isLightWeightPopupEnabled()) {
  383. setPopupVisible(comboBox, false);
  384. }
  385. comboBox.repaint();
  386. // Notify assistive technologies that the combo box
  387. // lost focus.
  388. if (comboBox instanceof Accessible) {
  389. AccessibleContext ac =
  390. ((Accessible)comboBox).getAccessibleContext();
  391. if (ac != null) {
  392. ac.firePropertyChange(
  393. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  394. AccessibleState.FOCUSED, null);
  395. }
  396. }
  397. }
  398. }
  399. /**
  400. * This listener watches for changes in the data and revalidates.
  401. *
  402. * This inner class is marked "public" due to a compiler bug.
  403. * This class should be treated as a "protected" inner class.
  404. * Instantiate it only within subclasses of <FooUI>.
  405. */
  406. public class ListDataHandler implements ListDataListener {
  407. public void contentsChanged( ListDataEvent e ) {
  408. if ( !(e.getIndex0() == -1 && e.getIndex1() == -1) ) {
  409. isMinimumSizeDirty = true;
  410. comboBox.revalidate();
  411. }
  412. if ( editor != null ) {
  413. comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
  414. }
  415. comboBox.repaint();
  416. }
  417. public void intervalAdded( ListDataEvent e ) {
  418. int index0 = e.getIndex0();
  419. int index1 = e.getIndex1();
  420. if ( index0 == 0 && comboBox.getItemCount() - ((index1 - index0) + 1) == 0 ) {
  421. contentsChanged( e );
  422. }
  423. else if ( !(index0 == -1 && index1 == -1) ) {
  424. ListCellRenderer renderer = comboBox.getRenderer();
  425. ComboBoxModel model = comboBox.getModel();
  426. Component c;
  427. Dimension size;
  428. int widestWidth = cachedDisplaySize.width;
  429. int tallestHeight = cachedDisplaySize.height;
  430. for ( int i = index0; i <= index1; ++i ) {
  431. c = renderer.getListCellRendererComponent( listBox, model.getElementAt( i ), -1, false, false );
  432. currentValuePane.add( c );
  433. c.setFont( comboBox.getFont() );
  434. size = c.getPreferredSize();
  435. widestWidth = Math.max( widestWidth, size.width );
  436. tallestHeight = Math.max( tallestHeight, size.height );
  437. currentValuePane.remove( c );
  438. }
  439. if ( cachedDisplaySize.width < widestWidth || cachedDisplaySize.height < tallestHeight ) {
  440. if ( cachedDisplaySize.width < widestWidth ) {
  441. cachedMinimumSize.width += widestWidth - cachedDisplaySize.width;
  442. }
  443. if ( cachedDisplaySize.height < tallestHeight ) {
  444. cachedMinimumSize.height += tallestHeight - cachedDisplaySize.height;
  445. }
  446. cachedDisplaySize.setSize( widestWidth, tallestHeight );
  447. comboBox.revalidate();
  448. if ( editor != null ) {
  449. comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
  450. }
  451. }
  452. }
  453. }
  454. public void intervalRemoved( ListDataEvent e ) {
  455. contentsChanged( e );
  456. }
  457. }
  458. /**
  459. * This listener watches for changes to the selection in the combo box and
  460. * updates the display of the currently selected item.
  461. *
  462. * This inner class is marked "public" due to a compiler bug.
  463. * This class should be treated as a "protected" inner class.
  464. * Instantiate it only within subclasses of <FooUI>.
  465. */
  466. public class ItemHandler implements ItemListener {
  467. public void itemStateChanged(ItemEvent e) {
  468. ComboBoxModel model = comboBox.getModel();
  469. Object v = model.getSelectedItem();
  470. if ( editor != null ) {
  471. comboBox.configureEditor(comboBox.getEditor(),v);
  472. }
  473. comboBox.repaint();
  474. }
  475. }
  476. /**
  477. * This listener watches for bound properties that have changed in the JComboBox.
  478. * It looks for the model and editor being swapped-out and updates appropriately.
  479. * It also looks for changes in the editable, enabled, and maximumRowCount properties.
  480. *
  481. * This inner class is marked "public" due to a compiler bug.
  482. * This class should be treated as a "protected" inner class.
  483. * Instantiate it only within subclasses of <FooUI>.
  484. */
  485. public class PropertyChangeHandler implements PropertyChangeListener {
  486. public void propertyChange(PropertyChangeEvent e) {
  487. String propertyName = e.getPropertyName();
  488. if ( propertyName.equals( "model" ) ) {
  489. ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
  490. ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
  491. if ( oldModel != null && listDataListener != null ) {
  492. oldModel.removeListDataListener( listDataListener );
  493. }
  494. if ( newModel != null && listDataListener != null ) {
  495. newModel.addListDataListener( listDataListener );
  496. }
  497. if ( editor != null ) {
  498. comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
  499. }
  500. isMinimumSizeDirty = true;
  501. comboBox.revalidate();
  502. comboBox.repaint();
  503. }
  504. else if ( propertyName.equals( "editor" ) && comboBox.isEditable() ) {
  505. removeEditor();
  506. addEditor();
  507. if (editor != null) {
  508. // Should be in the same state as the combobox
  509. editor.setEnabled(comboBox.isEnabled());
  510. }
  511. }
  512. else if ( propertyName.equals( "editable" ) ) {
  513. if ( comboBox.isEditable() ) {
  514. comboBox.setRequestFocusEnabled( false );
  515. if ( popupKeyListener != null ) {
  516. comboBox.removeKeyListener( popupKeyListener );
  517. }
  518. addEditor();
  519. }
  520. else {
  521. comboBox.setRequestFocusEnabled( true );
  522. if ( popupKeyListener != null ) {
  523. comboBox.addKeyListener( popupKeyListener );
  524. }
  525. removeEditor();
  526. }
  527. updateToolTipTextForChildren();
  528. // Double revalidation seems to be necessary here. Switching to editable can cause
  529. // strange layout problems otherwise.
  530. comboBox.revalidate();
  531. comboBox.validate();
  532. comboBox.revalidate();
  533. comboBox.repaint();
  534. }
  535. else if ( propertyName.equals( "enabled" ) ) {
  536. boolean cbIsEnabled = comboBox.isEnabled();
  537. if ( cbIsEnabled ) {
  538. if ( editor != null )
  539. editor.setEnabled(true);
  540. if ( arrowButton != null )
  541. arrowButton.setEnabled(true);
  542. }
  543. else {
  544. if ( editor != null )
  545. editor.setEnabled(false);
  546. if ( arrowButton != null )
  547. arrowButton.setEnabled(false);
  548. }
  549. comboBox.repaint();
  550. }
  551. else if ( propertyName.equals( "maximumRowCount" ) ) {
  552. if ( isPopupVisible( comboBox ) ) {
  553. setPopupVisible(comboBox, false);
  554. setPopupVisible(comboBox, true);
  555. }
  556. }
  557. else if ( propertyName.equals( "font" ) ) {
  558. listBox.setFont( comboBox.getFont() );
  559. if ( editor != null ) {
  560. editor.setFont( comboBox.getFont() );
  561. }
  562. isMinimumSizeDirty = true;
  563. comboBox.validate();
  564. }
  565. else if ( propertyName.equals( JComponent.TOOL_TIP_TEXT_KEY ) ) {
  566. updateToolTipTextForChildren();
  567. }
  568. else if ( propertyName.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION ) ) {
  569. Object newValue = e.getNewValue();
  570. if ( newValue.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_ON ) ) {
  571. lightNav = true;
  572. }
  573. else if ( newValue.equals( LIGHTWEIGHT_KEYBOARD_NAVIGATION_OFF ) ) {
  574. lightNav = false;
  575. }
  576. } else if ( propertyName.equals( "background" ) ) {
  577. Color color = (Color)e.getNewValue();
  578. if ( arrowButton != null )
  579. arrowButton.setBackground(color);
  580. listBox.setBackground(color);
  581. } else if ( propertyName.equals( "foreground" ) ) {
  582. Color color = (Color)e.getNewValue();
  583. if ( arrowButton != null )
  584. arrowButton.setForeground(color);
  585. listBox.setForeground(color);
  586. }
  587. }
  588. }
  589. void updateToolTipTextForChildren() {
  590. Component[] children = comboBox.getComponents();
  591. for ( int i = 0; i < children.length; ++i ) {
  592. if ( children[i] instanceof JComponent ) {
  593. ((JComponent)children[i]).setToolTipText( comboBox.getToolTipText() );
  594. }
  595. }
  596. }
  597. /**
  598. * This layout manager handles the 'standard' layout of combo boxes. It puts
  599. * the arrow button to the right and the editor to the left. If there is no
  600. * editor it still keeps the arrow button to the right.
  601. *
  602. * This inner class is marked "public" due to a compiler bug.
  603. * This class should be treated as a "protected" inner class.
  604. * Instantiate it only within subclasses of <FooUI>.
  605. */
  606. public class ComboBoxLayoutManager implements LayoutManager {
  607. public void addLayoutComponent(String name, Component comp) {}
  608. public void removeLayoutComponent(Component comp) {}
  609. public Dimension preferredLayoutSize(Container parent) {
  610. JComboBox cb = (JComboBox)parent;
  611. return parent.getPreferredSize();
  612. }
  613. public Dimension minimumLayoutSize(Container parent) {
  614. JComboBox cb = (JComboBox)parent;
  615. return parent.getMinimumSize();
  616. }
  617. public void layoutContainer(Container parent) {
  618. JComboBox cb = (JComboBox)parent;
  619. int width = cb.getWidth();
  620. int height = cb.getHeight();
  621. Insets insets = getInsets();
  622. int buttonSize = height - (insets.top + insets.bottom);
  623. Rectangle cvb;
  624. if ( arrowButton != null ) {
  625. if(BasicGraphicsUtils.isLeftToRight(cb)) {
  626. arrowButton.setBounds( width - (insets.right + buttonSize),
  627. insets.top,
  628. buttonSize, buttonSize);
  629. }
  630. else {
  631. arrowButton.setBounds( insets.left, insets.top,
  632. buttonSize, buttonSize);
  633. }
  634. }
  635. if ( editor != null ) {
  636. cvb = rectangleForCurrentValue();
  637. editor.setBounds(cvb);
  638. }
  639. }
  640. }
  641. //
  642. // end Inner classes
  643. //====================
  644. //===============================
  645. // begin Sub-Component Management
  646. //
  647. /**
  648. * The editor and arrow button are added to the JComboBox here.
  649. */
  650. protected void installComponents() {
  651. arrowButton = createArrowButton();
  652. comboBox.add( arrowButton );
  653. if ( comboBox.isEditable() ) {
  654. addEditor();
  655. }
  656. comboBox.add( currentValuePane );
  657. }
  658. /**
  659. * The editor and/or arrow button are removed from the JComboBox here.
  660. * This method calls removeAll() on the JComboBox just to make sure that
  661. * everything gets removed.
  662. */
  663. protected void uninstallComponents() {
  664. if ( arrowButton != null ) {
  665. unconfigureArrowButton();
  666. }
  667. if ( editor != null ) {
  668. unconfigureEditor();
  669. }
  670. comboBox.removeAll(); // Just to be safe.
  671. arrowButton = null;
  672. }
  673. /**
  674. * Adds the editor to the JComboBox.
  675. */
  676. public void addEditor() {
  677. removeEditor();
  678. editor = comboBox.getEditor().getEditorComponent();
  679. if ( editor != null ) {
  680. configureEditor();
  681. comboBox.add(editor);
  682. }
  683. }
  684. /**
  685. * Removes the editor from the JComboBox. It also calls unconfigureEditor()
  686. */
  687. public void removeEditor() {
  688. if ( editor != null ) {
  689. unconfigureEditor();
  690. comboBox.remove( editor );
  691. }
  692. }
  693. /**
  694. * Configures the editor by setting its font and adding listeners.
  695. */
  696. protected void configureEditor() {
  697. editor.setFont( comboBox.getFont() );
  698. if ( popupKeyListener != null ) {
  699. editor.addKeyListener( popupKeyListener );
  700. }
  701. if ( editor instanceof Accessible ) {
  702. AccessibleContext ac = ((Accessible) editor).getAccessibleContext();
  703. if ( ac != null ) {
  704. ac.setAccessibleParent(comboBox);
  705. }
  706. }
  707. if (editorFocusListener == null) {
  708. editorFocusListener = new EditorFocusListener();
  709. editor.addFocusListener( editorFocusListener );
  710. }
  711. comboBox.configureEditor(comboBox.getEditor(),comboBox.getSelectedItem());
  712. }
  713. /**
  714. * Unconfigures the editor by removing listeners.
  715. */
  716. protected void unconfigureEditor() {
  717. if ( popupKeyListener != null ) {
  718. editor.removeKeyListener( popupKeyListener );
  719. }
  720. if ( editorFocusListener != null ) {
  721. editor.removeFocusListener( editorFocusListener );
  722. editorFocusListener = null;
  723. }
  724. }
  725. /**
  726. * Configures the arrow button by adding listeners.
  727. */
  728. public void configureArrowButton() {
  729. if ( arrowButton != null ) {
  730. arrowButton.setEnabled( comboBox.isEnabled() );
  731. arrowButton.setRequestFocusEnabled(false);
  732. if ( popupMouseListener != null ) {
  733. arrowButton.addMouseListener( popupMouseListener );
  734. }
  735. if ( popupMouseMotionListener != null ) {
  736. arrowButton.addMouseMotionListener( popupMouseMotionListener );
  737. }
  738. arrowButton.resetKeyboardActions();
  739. }
  740. }
  741. /**
  742. * Unconfigures the arrow button by removing listeners.
  743. */
  744. public void unconfigureArrowButton() {
  745. if ( arrowButton != null ) {
  746. if ( popupMouseListener != null ) {
  747. arrowButton.removeMouseListener( popupMouseListener );
  748. }
  749. if ( popupMouseMotionListener != null ) {
  750. arrowButton.removeMouseMotionListener( popupMouseMotionListener );
  751. }
  752. }
  753. }
  754. /**
  755. * Creates the arrow button. Subclasses can create any button they like.
  756. * The default behavior of this class is to attach various listeners to the
  757. * button returned by this method.
  758. * Returns an instance of BasicArrowButton.
  759. */
  760. protected JButton createArrowButton() {
  761. return new BasicArrowButton(BasicArrowButton.SOUTH);
  762. }
  763. //
  764. // end Sub-Component Management
  765. //===============================
  766. //================================
  767. // begin ComboBoxUI Implementation
  768. //
  769. /**
  770. * Tells if the popup is visible or not.
  771. */
  772. public boolean isPopupVisible( JComboBox c ) {
  773. return popup.isVisible();
  774. }
  775. /**
  776. * Hides the popup.
  777. */
  778. public void setPopupVisible( JComboBox c, boolean v ) {
  779. if ( v ) {
  780. popup.show();
  781. }
  782. else {
  783. popup.hide();
  784. }
  785. }
  786. /**
  787. * Determines if the JComboBox is focus traversable. If the JComboBox is editable
  788. * this returns false, otherwise it returns true.
  789. */
  790. public boolean isFocusTraversable( JComboBox c ) {
  791. return !comboBox.isEditable();
  792. }
  793. //
  794. // end ComboBoxUI Implementation
  795. //==============================
  796. //=================================
  797. // begin ComponentUI Implementation
  798. public void paint( Graphics g, JComponent c ) {
  799. hasFocus = comboBox.hasFocus();
  800. if ( !comboBox.isEditable() ) {
  801. Rectangle r = rectangleForCurrentValue();
  802. paintCurrentValueBackground(g,r,hasFocus);
  803. paintCurrentValue(g,r,hasFocus);
  804. }
  805. }
  806. public Dimension getPreferredSize( JComponent c ) {
  807. Dimension size = getMinimumSize( c );
  808. size.width += 4; // Added for a little 'elbow room'.
  809. return size;
  810. }
  811. public Dimension getMinimumSize( JComponent c ) {
  812. if ( !isMinimumSizeDirty ) {
  813. return new Dimension( cachedMinimumSize );
  814. }
  815. Dimension size;
  816. Insets insets = getInsets();
  817. size = getDisplaySize();
  818. size.height += insets.top + insets.bottom;
  819. int buttonSize = size.height - (insets.top + insets.bottom);
  820. size.width += insets.left + insets.right + buttonSize;
  821. cachedMinimumSize.setSize( size.width, size.height );
  822. isMinimumSizeDirty = false;
  823. return size;
  824. }
  825. public Dimension getMaximumSize( JComponent c ) {
  826. Dimension size = getPreferredSize( c );
  827. size.width = Short.MAX_VALUE;
  828. return size;
  829. }
  830. // This is currently hacky...
  831. public int getAccessibleChildrenCount(JComponent c) {
  832. if ( comboBox.isEditable() ) {
  833. return 2;
  834. }
  835. else {
  836. return 1;
  837. }
  838. }
  839. // This is currently hacky...
  840. public Accessible getAccessibleChild(JComponent c, int i) {
  841. // 0 = the popup
  842. // 1 = the editor
  843. switch ( i ) {
  844. case 0:
  845. if ( popup instanceof Accessible ) {
  846. AccessibleContext ac = ((Accessible) popup).getAccessibleContext();
  847. ac.setAccessibleParent(comboBox);
  848. return(Accessible) popup;
  849. }
  850. break;
  851. case 1:
  852. if ( comboBox.isEditable()
  853. && (editor instanceof Accessible) ) {
  854. AccessibleContext ac = ((Accessible) editor).getAccessibleContext();
  855. ac.setAccessibleParent(comboBox);
  856. return(Accessible) editor;
  857. }
  858. break;
  859. }
  860. return null;
  861. }
  862. //
  863. // end ComponentUI Implementation
  864. //===============================
  865. //======================
  866. // begin Utility Methods
  867. //
  868. /**
  869. * Returns whether or not the supplied keyCode maps to a key that is used for
  870. * navigation. This is used for optimizing key input by only passing non-
  871. * navigation keys to the type-ahead mechanism. Subclasses should override this
  872. * if they change the navigation keys.
  873. */
  874. protected boolean isNavigationKey( int keyCode ) {
  875. return keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN ||
  876. // This is horrible, but necessary since these aren't
  877. // supported until JDK 1.2
  878. keyCode == KeyStroke.getKeyStroke("KP_UP").getKeyCode() ||
  879. keyCode == KeyStroke.getKeyStroke("KP_DOWN").getKeyCode();
  880. }
  881. /**
  882. * Selects the next item in the list. It won't change the selection if the
  883. * currently selected item is already the last item.
  884. */
  885. protected void selectNextPossibleValue() {
  886. int si;
  887. if ( lightNav ) {
  888. si = listBox.getSelectedIndex();
  889. }
  890. else {
  891. si = comboBox.getSelectedIndex();
  892. }
  893. if ( si < comboBox.getModel().getSize() - 1 ) {
  894. if ( lightNav ) {
  895. listBox.setSelectedIndex( si + 1 );
  896. listBox.ensureIndexIsVisible( si + 1 );
  897. }
  898. else {
  899. comboBox.setSelectedIndex(si+1);
  900. }
  901. comboBox.repaint();
  902. }
  903. }
  904. /**
  905. * Selects the previous item in the list. It won't change the selection if the
  906. * currently selected item is already the first item.
  907. */
  908. protected void selectPreviousPossibleValue() {
  909. int si;
  910. if ( lightNav ) {
  911. si = listBox.getSelectedIndex();
  912. }
  913. else {
  914. si = comboBox.getSelectedIndex();
  915. }
  916. if ( si > 0 ) {
  917. if ( lightNav ) {
  918. listBox.setSelectedIndex( si - 1 );
  919. listBox.ensureIndexIsVisible( si - 1 );
  920. }
  921. else {
  922. comboBox.setSelectedIndex(si-1);
  923. }
  924. comboBox.repaint();
  925. }
  926. }
  927. /**
  928. * Hides the popup if it is showing and shows the popup if it is hidden.
  929. */
  930. protected void toggleOpenClose() {
  931. setPopupVisible(comboBox, !isPopupVisible(comboBox));
  932. }
  933. /**
  934. * Returns the area that is reserved for drawing the currently selected item.
  935. */
  936. protected Rectangle rectangleForCurrentValue() {
  937. int width = comboBox.getWidth();
  938. int height = comboBox.getHeight();
  939. Insets insets = getInsets();
  940. int buttonSize = height - (insets.top + insets.bottom);
  941. if ( arrowButton != null ) {
  942. buttonSize = arrowButton.getWidth();
  943. }
  944. if(BasicGraphicsUtils.isLeftToRight(comboBox)) {
  945. return new Rectangle(insets.left, insets.top,
  946. width - (insets.left + insets.right + buttonSize),
  947. height - (insets.top + insets.bottom));
  948. }
  949. else {
  950. return new Rectangle(insets.left + buttonSize, insets.top,
  951. width - (insets.left + insets.right + buttonSize),
  952. height - (insets.top + insets.bottom));
  953. }
  954. }
  955. /**
  956. * Gets the insets from the JComboBox.
  957. */
  958. protected Insets getInsets() {
  959. return comboBox.getInsets();
  960. }
  961. //
  962. // end Utility Methods
  963. //====================
  964. //===============================
  965. // begin Painting Utility Methods
  966. //
  967. /**
  968. * Paints the currently selected item.
  969. */
  970. public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
  971. ListCellRenderer renderer = comboBox.getRenderer();
  972. Component c;
  973. if ( comboBox.getSelectedIndex() == -1 ) {
  974. return;
  975. }
  976. if ( hasFocus && !isPopupVisible(comboBox) ) {
  977. c = renderer.getListCellRendererComponent( listBox,
  978. comboBox.getSelectedItem(),
  979. -1,
  980. true,
  981. false );
  982. }
  983. else {
  984. c = renderer.getListCellRendererComponent( listBox,
  985. comboBox.getSelectedItem(),
  986. -1,
  987. false,
  988. false );
  989. c.setBackground(UIManager.getColor("ComboBox.background"));
  990. }
  991. c.setFont(comboBox.getFont());
  992. if ( hasFocus && !isPopupVisible(comboBox) ) {
  993. c.setForeground(listBox.getSelectionForeground());
  994. c.setBackground(listBox.getSelectionBackground());
  995. }
  996. else {
  997. if ( comboBox.isEnabled() ) {
  998. c.setForeground(comboBox.getForeground());
  999. c.setBackground(comboBox.getBackground());
  1000. }
  1001. else {
  1002. c.setForeground(UIManager.getColor("ComboBox.disabledForeground"));
  1003. c.setBackground(UIManager.getColor("ComboBox.disabledBackground"));
  1004. }
  1005. }
  1006. currentValuePane.paintComponent(g,c,comboBox,bounds.x,bounds.y,
  1007. bounds.width,bounds.height);
  1008. }
  1009. /**
  1010. * Paints the background of the currently selected item.
  1011. */
  1012. public void paintCurrentValueBackground(Graphics g,Rectangle bounds,boolean hasFocus) {
  1013. Color t = g.getColor();
  1014. if ( comboBox.isEnabled() )
  1015. g.setColor(UIManager.getColor("ComboBox.background"));
  1016. else
  1017. g.setColor(UIManager.getColor("ComboBox.disabledBackground"));
  1018. g.fillRect(bounds.x,bounds.y,bounds.width,bounds.height);
  1019. g.setColor(t);
  1020. }
  1021. /**
  1022. * Repaint the currently selected item.
  1023. */
  1024. void repaintCurrentValue() {
  1025. Rectangle r = rectangleForCurrentValue();
  1026. comboBox.repaint(r.x,r.y,r.width,r.height);
  1027. }
  1028. //
  1029. // end Painting Utility Methods
  1030. //=============================
  1031. //===============================
  1032. // begin Size Utility Methods
  1033. //
  1034. /**
  1035. * Return the default size of an empty combo box.
  1036. */
  1037. protected Dimension getDefaultSize() {
  1038. ListCellRenderer renderer = comboBox.getRenderer();
  1039. Component c = textRenderer.getListCellRendererComponent( listBox, " ", -1, false, false );
  1040. currentValuePane.add( c );
  1041. c.setFont( comboBox.getFont() );
  1042. int height = c.getPreferredSize().height;
  1043. currentValuePane.remove( c );
  1044. return new Dimension( 100, height );
  1045. }
  1046. protected Dimension getDisplaySize() {
  1047. // This is special code for empty editable combo boxes.
  1048. if ( comboBox.isEditable() ) {
  1049. if ( comboBox.getModel().getSize() == 0 ) {
  1050. return new Dimension( 100, editor.getPreferredSize().height );
  1051. }
  1052. }
  1053. int i,c;
  1054. Dimension result = new Dimension();
  1055. ListCellRenderer renderer = comboBox.getRenderer();
  1056. ComboBoxModel model = comboBox.getModel();
  1057. Component cpn;
  1058. Dimension d;
  1059. if ( renderer != null && model.getSize() > 0 ) {
  1060. for ( i=0,c=model.getSize();i<c;i++ ) {
  1061. cpn = renderer.getListCellRendererComponent(listBox, model.getElementAt(i),-1, false, false);
  1062. currentValuePane.add(cpn);
  1063. cpn.setFont(comboBox.getFont());
  1064. d = cpn.getPreferredSize();
  1065. currentValuePane.remove(cpn);
  1066. result.width = Math.max(result.width,d.width);
  1067. result.height = Math.max(result.height,d.height);
  1068. }
  1069. if ( comboBox.isEditable() ) {
  1070. d = editor.getPreferredSize();
  1071. result.width = Math.max(result.width,d.width);
  1072. result.height = Math.max(result.height,d.height);
  1073. }
  1074. cachedDisplaySize.setSize( result.width, result.height );
  1075. return result;
  1076. }
  1077. else {
  1078. return getDefaultSize();
  1079. }
  1080. }
  1081. //
  1082. // end Size Utility Methods
  1083. //=============================
  1084. //=================================
  1085. // begin Keyboard Action Management
  1086. //
  1087. /**
  1088. * Adds keyboard actions to the JComboBox. Actions on enter and esc are already
  1089. * supplied. Add more actions as you need them.
  1090. */
  1091. protected void installKeyboardActions() {
  1092. InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
  1093. SwingUtilities.replaceUIInputMap(comboBox, JComponent.
  1094. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
  1095. ActionMap am = getActionMap();
  1096. if (am != null) {
  1097. SwingUtilities.replaceUIActionMap(comboBox, am);
  1098. }
  1099. }
  1100. InputMap getInputMap(int condition) {
  1101. if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
  1102. return (InputMap)UIManager.get("ComboBox.ancestorInputMap");
  1103. }
  1104. return null;
  1105. }
  1106. ActionMap getActionMap() {
  1107. ActionMap map = (ActionMap)UIManager.get("ComboBox.actionMap");
  1108. if (map == null) {
  1109. map = createActionMap();
  1110. if (map != null) {
  1111. UIManager.put("ComboBox.actionMap", map);
  1112. }
  1113. }
  1114. return map;
  1115. }
  1116. ActionMap createActionMap() {
  1117. ActionMap map = new ActionMapUIResource();
  1118. map.put("hidePopup", new HidePopupAction());
  1119. map.put("pageDownPassThrough", new KeyToListDispatcher
  1120. (KeyEvent.VK_PAGE_DOWN));
  1121. map.put("pageUpPassThrough", new KeyToListDispatcher
  1122. (KeyEvent.VK_PAGE_UP));
  1123. map.put("homePassThrough", new KeyToListDispatcher
  1124. (KeyEvent.VK_HOME));
  1125. map.put("endPassThrough", new KeyToListDispatcher
  1126. (KeyEvent.VK_END));
  1127. map.put("selectNext", new DownAction());
  1128. map.put("togglePopup", new AltAction());
  1129. map.put("selectPrevious", new UpAction());
  1130. return map;
  1131. }
  1132. /**
  1133. * Removes the focus InputMap and ActionMap.
  1134. */
  1135. protected void uninstallKeyboardActions() {
  1136. SwingUtilities.replaceUIInputMap(comboBox, JComponent.
  1137. WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
  1138. SwingUtilities.replaceUIActionMap(comboBox, null);
  1139. }
  1140. //
  1141. // Actions
  1142. //
  1143. static class HidePopupAction extends AbstractAction {
  1144. public void actionPerformed( ActionEvent e ) {
  1145. JComboBox comboBox = (JComboBox)e.getSource();
  1146. BasicComboBoxUI ui = (BasicComboBoxUI)comboBox.getUI();
  1147. if ( comboBox.isEnabled() ) {
  1148. ui.setPopupVisible( comboBox, false );
  1149. }
  1150. }
  1151. }
  1152. static class KeyToListDispatcher extends AbstractAction {
  1153. int keyCode;
  1154. public KeyToListDispatcher( int keyCode ) {
  1155. this.keyCode = keyCode;
  1156. }
  1157. public void actionPerformed( ActionEvent e ) {
  1158. JComboBox comboBox = (JComboBox)e.getSource();
  1159. BasicComboBoxUI ui = (BasicComboBoxUI)comboBox.getUI();
  1160. if ( ui.isPopupVisible( comboBox ) ) {
  1161. KeyEvent keyEvent = new KeyEvent( ui.popup.getList(),
  1162. KeyEvent.KEY_PRESSED,
  1163. 0,
  1164. 0,
  1165. keyCode );
  1166. ui.popup.getList().dispatchEvent( keyEvent );
  1167. }
  1168. }
  1169. }
  1170. static class DownAction extends AbstractAction {
  1171. public void actionPerformed(ActionEvent e) {
  1172. JComboBox comboBox = (JComboBox)e.getSource();
  1173. if ( comboBox.isEnabled() ) {
  1174. BasicComboBoxUI ui = (BasicComboBoxUI)comboBox.getUI();
  1175. if ( ui.isPopupVisible(comboBox) ) {
  1176. ui.selectNextPossibleValue();
  1177. }
  1178. else {
  1179. ui.setPopupVisible( comboBox, true );
  1180. }
  1181. }
  1182. }
  1183. }
  1184. static class AltAction extends AbstractAction {
  1185. public void actionPerformed(ActionEvent e) {
  1186. JComboBox comboBox = (JComboBox)e.getSource();
  1187. if ( comboBox.isEnabled() ) {
  1188. BasicComboBoxUI ui = (BasicComboBoxUI)comboBox.getUI();
  1189. ui.toggleOpenClose();
  1190. }
  1191. }
  1192. }
  1193. static class UpAction extends AbstractAction {
  1194. public void actionPerformed(ActionEvent e) {
  1195. JComboBox comboBox = (JComboBox)e.getSource();
  1196. if ( comboBox.isEnabled() ) {
  1197. BasicComboBoxUI ui = (BasicComboBoxUI)comboBox.getUI();
  1198. if (ui.isPopupVisible(comboBox)) {
  1199. ui.selectPreviousPossibleValue();
  1200. }
  1201. }
  1202. }
  1203. }
  1204. //
  1205. // end Keyboard Action Management
  1206. //===============================
  1207. // This will make the comboBox fire an ActionEvent if the currently
  1208. // edited editor has a value in it that is different from the
  1209. // selected item in the model. This allows people to enter data in
  1210. // a combo box and tab-out without allowing an inconsistency between
  1211. // what's displayed and what the current value of the combo box is.
  1212. class EditorFocusListener extends FocusAdapter {
  1213. public void focusLost( FocusEvent e ) {
  1214. Object item = comboBox.getEditor().getItem();
  1215. if ( !e.isTemporary() &&
  1216. item != null &&
  1217. !item.equals( comboBox.getSelectedItem() ) ) {
  1218. comboBox.actionPerformed( new ActionEvent( editor, 0, "" ) );
  1219. }
  1220. }
  1221. }
  1222. }