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