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