1 /*
2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
44 * The combo box is a compound component which means that it is an aggregate of
45 * many simpler components. This class creates and manages the listeners
46 * on the combo box and the combo box model. These listeners update the user
47 * interface in response to changes in the properties and state of the combo box.
48 * <p>
49 * All event handling is handled by listener classes created with the
50 * <code>createxxxListener()</code> methods and internal classes.
51 * You can change the behavior of this class by overriding the
52 * <code>createxxxListener()</code> methods and supplying your own
53 * event listeners or subclassing from the ones supplied in this class.
54 * <p>
55 * For adding specific actions,
56 * overide <code>installKeyboardActions</code> to add actions in response to
57 * KeyStroke bindings. See the article <a href="http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html">How to Use Key Bindings</a>
58 *
59 * @author Arnaud Weber
60 * @author Tom Santos
61 * @author Mark Davidson
62 */
63 public class BasicComboBoxUI extends ComboBoxUI {
64 protected JComboBox comboBox;
65 /**
66 * This protected field is implementation specific. Do not access directly
67 * or override.
68 */
69 protected boolean hasFocus = false;
70
71 // Control the selection behavior of the JComboBox when it is used
72 // in the JTable DefaultCellEditor.
73 private boolean isTableCellEditor = false;
74 private static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";
75
76 // This list is for drawing the current item in the combo box.
77 protected JList listBox;
78
79 // Used to render the currently selected item in the combo box.
80 // It doesn't have anything to do with the popup's rendering.
81 protected CellRendererPane currentValuePane = new CellRendererPane();
82
83 // The implementation of ComboPopup that is used to show the popup.
84 protected ComboPopup popup;
85
86 // The Component that the ComboBoxEditor uses for editing
87 protected Component editor;
88
89 // The arrow button that invokes the popup.
90 protected JButton arrowButton;
91
92 // Listeners that are attached to the JComboBox
93 /**
94 * This protected field is implementation specific. Do not access directly
95 * or override. Override the listener construction method instead.
96 *
97 * @see #createKeyListener
186
187 /**
188 * Indicates whether or not the combo box button should be square.
189 * If square, then the width and height are equal, and are both set to
190 * the height of the combo minus appropriate insets.
191 *
192 * @since 1.7
193 */
194 protected boolean squareButton = true;
195
196 /**
197 * If specified, these insets act as padding around the cell renderer when
198 * laying out and painting the "selected" item in the combo box. These
199 * insets add to those specified by the cell renderer.
200 *
201 * @since 1.7
202 */
203 protected Insets padding;
204
205 // Used for calculating the default size.
206 private static ListCellRenderer getDefaultListCellRenderer() {
207 ListCellRenderer renderer = (ListCellRenderer)AppContext.
208 getAppContext().get(COMBO_UI_LIST_CELL_RENDERER_KEY);
209
210 if (renderer == null) {
211 renderer = new DefaultListCellRenderer();
212 AppContext.getAppContext().put(COMBO_UI_LIST_CELL_RENDERER_KEY,
213 new DefaultListCellRenderer());
214 }
215 return renderer;
216 }
217
218 /**
219 * Populates ComboBox's actions.
220 */
221 static void loadActionMap(LazyActionMap map) {
222 map.put(new Actions(Actions.HIDE));
223 map.put(new Actions(Actions.PAGE_DOWN));
224 map.put(new Actions(Actions.PAGE_UP));
225 map.put(new Actions(Actions.HOME));
226 map.put(new Actions(Actions.END));
227 map.put(new Actions(Actions.DOWN));
228 map.put(new Actions(Actions.DOWN_2));
229 map.put(new Actions(Actions.TOGGLE));
230 map.put(new Actions(Actions.TOGGLE_2));
231 map.put(new Actions(Actions.UP));
232 map.put(new Actions(Actions.UP_2));
233 map.put(new Actions(Actions.ENTER));
234 }
235
236 //========================
237 // begin UI Initialization
238 //
239
240 public static ComponentUI createUI(JComponent c) {
241 return new BasicComboBoxUI();
242 }
243
244 @Override
245 public void installUI( JComponent c ) {
246 isMinimumSizeDirty = true;
247
248 comboBox = (JComboBox)c;
249 installDefaults();
250 popup = createPopup();
251 listBox = popup.getList();
252
253 // Is this combo box a cell editor?
254 Boolean inTable = (Boolean)c.getClientProperty(IS_TABLE_CELL_EDITOR );
255 if (inTable != null) {
256 isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
257 }
258
259 if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
260 comboBox.setRenderer( createRenderer() );
261 }
262
263 if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) {
264 comboBox.setEditor( createEditor() );
265 }
266
267 installListeners();
268 installComponents();
491 }
492
493 /**
494 * Creates a layout manager for managing the components which make up the
495 * combo box.
496 *
497 * @return an instance of a layout manager
498 */
499 protected LayoutManager createLayoutManager() {
500 return getHandler();
501 }
502
503 /**
504 * Creates the default renderer that will be used in a non-editiable combo
505 * box. A default renderer will used only if a renderer has not been
506 * explicitly set with <code>setRenderer</code>.
507 *
508 * @return a <code>ListCellRender</code> used for the combo box
509 * @see javax.swing.JComboBox#setRenderer
510 */
511 protected ListCellRenderer createRenderer() {
512 return new BasicComboBoxRenderer.UIResource();
513 }
514
515 /**
516 * Creates the default editor that will be used in editable combo boxes.
517 * A default editor will be used only if an editor has not been
518 * explicitly set with <code>setEditor</code>.
519 *
520 * @return a <code>ComboBoxEditor</code> used for the combo box
521 * @see javax.swing.JComboBox#setEditor
522 */
523 protected ComboBoxEditor createEditor() {
524 return new BasicComboBoxEditor.UIResource();
525 }
526
527 /**
528 * Returns the shared listener.
529 */
530 private Handler getHandler() {
531 if (handler == null) {
848 UIManager.getColor("ComboBox.buttonBackground"),
849 UIManager.getColor("ComboBox.buttonShadow"),
850 UIManager.getColor("ComboBox.buttonDarkShadow"),
851 UIManager.getColor("ComboBox.buttonHighlight"));
852 button.setName("ComboBox.arrowButton");
853 return button;
854 }
855
856 //
857 // end Sub-Component Management
858 //===============================
859
860
861 //================================
862 // begin ComboBoxUI Implementation
863 //
864
865 /**
866 * Tells if the popup is visible or not.
867 */
868 public boolean isPopupVisible( JComboBox c ) {
869 return popup.isVisible();
870 }
871
872 /**
873 * Hides the popup.
874 */
875 public void setPopupVisible( JComboBox c, boolean v ) {
876 if ( v ) {
877 popup.show();
878 } else {
879 popup.hide();
880 }
881 }
882
883 /**
884 * Determines if the JComboBox is focus traversable. If the JComboBox is editable
885 * this returns false, otherwise it returns true.
886 */
887 public boolean isFocusTraversable( JComboBox c ) {
888 return !comboBox.isEditable();
889 }
890
891 //
892 // end ComboBoxUI Implementation
893 //==============================
894
895
896 //=================================
897 // begin ComponentUI Implementation
898 @Override
899 public void paint( Graphics g, JComponent c ) {
900 hasFocus = comboBox.hasFocus();
901 if ( !comboBox.isEditable() ) {
902 Rectangle r = rectangleForCurrentValue();
903 paintCurrentValueBackground(g,r,hasFocus);
904 paintCurrentValue(g,r,hasFocus);
905 }
906 }
907
939 }
940
941 /**
942 * Returns the baseline.
943 *
944 * @throws NullPointerException {@inheritDoc}
945 * @throws IllegalArgumentException {@inheritDoc}
946 * @see javax.swing.JComponent#getBaseline(int, int)
947 * @since 1.6
948 */
949 @Override
950 public int getBaseline(JComponent c, int width, int height) {
951 super.getBaseline(c, width, height);
952 int baseline = -1;
953 // force sameBaseline to be updated.
954 getDisplaySize();
955 if (sameBaseline) {
956 Insets insets = c.getInsets();
957 height = height - insets.top - insets.bottom;
958 if (!comboBox.isEditable()) {
959 ListCellRenderer renderer = comboBox.getRenderer();
960 if (renderer == null) {
961 renderer = new DefaultListCellRenderer();
962 }
963 Object value = null;
964 Object prototypeValue = comboBox.getPrototypeDisplayValue();
965 if (prototypeValue != null) {
966 value = prototypeValue;
967 }
968 else if (comboBox.getModel().getSize() > 0) {
969 // Note, we're assuming the baseline is the same for all
970 // cells, if not, this needs to loop through all.
971 value = comboBox.getModel().getElementAt(0);
972 }
973 Component component = renderer.
974 getListCellRendererComponent(listBox, value, -1,
975 false, false);
976 if (component instanceof JLabel) {
977 JLabel label = (JLabel) component;
978 String text = label.getText();
979 if ((text == null) || text.isEmpty()) {
996 }
997
998 /**
999 * Returns an enum indicating how the baseline of the component
1000 * changes as the size changes.
1001 *
1002 * @throws NullPointerException {@inheritDoc}
1003 * @see javax.swing.JComponent#getBaseline(int, int)
1004 * @since 1.6
1005 */
1006 @Override
1007 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
1008 JComponent c) {
1009 super.getBaselineResizeBehavior(c);
1010 // Force sameBaseline to be updated.
1011 getDisplaySize();
1012 if (comboBox.isEditable()) {
1013 return editor.getBaselineResizeBehavior();
1014 }
1015 else if (sameBaseline) {
1016 ListCellRenderer renderer = comboBox.getRenderer();
1017 if (renderer == null) {
1018 renderer = new DefaultListCellRenderer();
1019 }
1020 Object value = null;
1021 Object prototypeValue = comboBox.getPrototypeDisplayValue();
1022 if (prototypeValue != null) {
1023 value = prototypeValue;
1024 }
1025 else if (comboBox.getModel().getSize() > 0) {
1026 // Note, we're assuming the baseline is the same for all
1027 // cells, if not, this needs to loop through all.
1028 value = comboBox.getModel().getElementAt(0);
1029 }
1030 if (value != null) {
1031 Component component = renderer.
1032 getListCellRendererComponent(listBox, value, -1,
1033 false, false);
1034 return component.getBaselineResizeBehavior();
1035 }
1036 }
1188 /**
1189 * Gets the insets from the JComboBox.
1190 */
1191 protected Insets getInsets() {
1192 return comboBox.getInsets();
1193 }
1194
1195 //
1196 // end Utility Methods
1197 //====================
1198
1199
1200 //===============================
1201 // begin Painting Utility Methods
1202 //
1203
1204 /**
1205 * Paints the currently selected item.
1206 */
1207 public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
1208 ListCellRenderer renderer = comboBox.getRenderer();
1209 Component c;
1210
1211 if ( hasFocus && !isPopupVisible(comboBox) ) {
1212 c = renderer.getListCellRendererComponent( listBox,
1213 comboBox.getSelectedItem(),
1214 -1,
1215 true,
1216 false );
1217 }
1218 else {
1219 c = renderer.getListCellRendererComponent( listBox,
1220 comboBox.getSelectedItem(),
1221 -1,
1222 false,
1223 false );
1224 c.setBackground(UIManager.getColor("ComboBox.background"));
1225 }
1226 c.setFont(comboBox.getFont());
1227 if ( hasFocus && !isPopupVisible(comboBox) ) {
1228 c.setForeground(listBox.getSelectionForeground());
1305 }
1306
1307 /**
1308 * Returns the calculated size of the display area. The display area is the
1309 * portion of the combo box in which the selected item is displayed. This
1310 * method will use the prototype display value if it has been set.
1311 * <p>
1312 * For combo boxes with a non trivial number of items, it is recommended to
1313 * use a prototype display value to significantly speed up the display
1314 * size calculation.
1315 *
1316 * @return the size of the display area calculated from the combo box items
1317 * @see javax.swing.JComboBox#setPrototypeDisplayValue
1318 */
1319 protected Dimension getDisplaySize() {
1320 if (!isDisplaySizeDirty) {
1321 return new Dimension(cachedDisplaySize);
1322 }
1323 Dimension result = new Dimension();
1324
1325 ListCellRenderer renderer = comboBox.getRenderer();
1326 if (renderer == null) {
1327 renderer = new DefaultListCellRenderer();
1328 }
1329
1330 sameBaseline = true;
1331
1332 Object prototypeValue = comboBox.getPrototypeDisplayValue();
1333 if (prototypeValue != null) {
1334 // Calculates the dimension based on the prototype value
1335 result = getSizeForComponent(renderer.getListCellRendererComponent(listBox,
1336 prototypeValue,
1337 -1, false, false));
1338 } else {
1339 // Calculate the dimension by iterating over all the elements in the combo
1340 // box list.
1341 ComboBoxModel model = comboBox.getModel();
1342 int modelSize = model.getSize();
1343 int baseline = -1;
1344 Dimension d;
1345
1346 Component cpn;
1347
1348 if (modelSize > 0 ) {
1349 for (int i = 0; i < modelSize ; i++ ) {
1350 // Calculates the maximum height and width based on the largest
1351 // element
1352 Object value = model.getElementAt(i);
1353 Component c = renderer.getListCellRendererComponent(
1354 listBox, value, -1, false, false);
1355 d = getSizeForComponent(c);
1356 if (sameBaseline && value != null &&
1357 (!(value instanceof String) || !"".equals(value))) {
1358 int newBaseline = c.getBaseline(d.width, d.height);
1359 if (newBaseline == -1) {
1360 sameBaseline = false;
1361 }
1467 private static class Actions extends UIAction {
1468 private static final String HIDE = "hidePopup";
1469 private static final String DOWN = "selectNext";
1470 private static final String DOWN_2 = "selectNext2";
1471 private static final String TOGGLE = "togglePopup";
1472 private static final String TOGGLE_2 = "spacePopup";
1473 private static final String UP = "selectPrevious";
1474 private static final String UP_2 = "selectPrevious2";
1475 private static final String ENTER = "enterPressed";
1476 private static final String PAGE_DOWN = "pageDownPassThrough";
1477 private static final String PAGE_UP = "pageUpPassThrough";
1478 private static final String HOME = "homePassThrough";
1479 private static final String END = "endPassThrough";
1480
1481 Actions(String name) {
1482 super(name);
1483 }
1484
1485 public void actionPerformed( ActionEvent e ) {
1486 String key = getName();
1487 JComboBox comboBox = (JComboBox)e.getSource();
1488 BasicComboBoxUI ui = (BasicComboBoxUI)BasicLookAndFeel.getUIOfType(
1489 comboBox.getUI(), BasicComboBoxUI.class);
1490 if (key == HIDE) {
1491 comboBox.firePopupMenuCanceled();
1492 comboBox.setPopupVisible(false);
1493 }
1494 else if (key == PAGE_DOWN || key == PAGE_UP ||
1495 key == HOME || key == END) {
1496 int index = getNextIndex(comboBox, key);
1497 if (index >= 0 && index < comboBox.getItemCount()) {
1498 if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible()) {
1499 ui.listBox.setSelectedIndex(index);
1500 ui.listBox.ensureIndexIsVisible(index);
1501 comboBox.repaint();
1502 } else {
1503 comboBox.setSelectedIndex(index);
1504 }
1505 }
1506 }
1507 else if (key == DOWN) {
1608 JRootPane root = SwingUtilities.getRootPane(comboBox);
1609 if (root != null) {
1610 InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1611 ActionMap am = root.getActionMap();
1612 if (im != null && am != null) {
1613 Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
1614 if (obj != null) {
1615 Action action = am.get(obj);
1616 if (action != null) {
1617 action.actionPerformed(new ActionEvent(
1618 root, e.getID(), e.getActionCommand(),
1619 e.getWhen(), e.getModifiers()));
1620 }
1621 }
1622 }
1623 }
1624 }
1625 }
1626 }
1627
1628 private int getNextIndex(JComboBox comboBox, String key) {
1629 int listHeight = comboBox.getMaximumRowCount();
1630
1631 int selectedIndex = comboBox.getSelectedIndex();
1632 if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation")
1633 && (comboBox.getUI() instanceof BasicComboBoxUI)) {
1634 selectedIndex = ((BasicComboBoxUI) comboBox.getUI()).listBox.getSelectedIndex();
1635 }
1636
1637 if (key == PAGE_UP) {
1638 int index = selectedIndex - listHeight;
1639 return (index < 0 ? 0: index);
1640 }
1641 else if (key == PAGE_DOWN) {
1642 int index = selectedIndex + listHeight;
1643 int max = comboBox.getItemCount();
1644 return (index < max ? index: max-1);
1645 }
1646 else if (key == HOME) {
1647 return 0;
1648 }
1668 // Shared Handler, implements all listeners
1669 //
1670 private class Handler implements ActionListener, FocusListener,
1671 KeyListener, LayoutManager,
1672 ListDataListener, PropertyChangeListener {
1673 //
1674 // PropertyChangeListener
1675 //
1676 public void propertyChange(PropertyChangeEvent e) {
1677 String propertyName = e.getPropertyName();
1678 if (e.getSource() == editor){
1679 // If the border of the editor changes then this can effect
1680 // the size of the editor which can cause the combo's size to
1681 // become invalid so we need to clear size caches
1682 if ("border".equals(propertyName)){
1683 isMinimumSizeDirty = true;
1684 isDisplaySizeDirty = true;
1685 comboBox.revalidate();
1686 }
1687 } else {
1688 JComboBox comboBox = (JComboBox)e.getSource();
1689 if ( propertyName == "model" ) {
1690 ComboBoxModel newModel = (ComboBoxModel)e.getNewValue();
1691 ComboBoxModel oldModel = (ComboBoxModel)e.getOldValue();
1692
1693 if ( oldModel != null && listDataListener != null ) {
1694 oldModel.removeListDataListener( listDataListener );
1695 }
1696
1697 if ( newModel != null && listDataListener != null ) {
1698 newModel.addListDataListener( listDataListener );
1699 }
1700
1701 if ( editor != null ) {
1702 comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
1703 }
1704 isMinimumSizeDirty = true;
1705 isDisplaySizeDirty = true;
1706 comboBox.revalidate();
1707 comboBox.repaint();
1708 }
1709 else if ( propertyName == "editor" && comboBox.isEditable() ) {
1710 addEditor();
1711 comboBox.revalidate();
1880 //
1881 // LayoutManager
1882 //
1883
1884 // This layout manager handles the 'standard' layout of combo boxes.
1885 // It puts the arrow button to the right and the editor to the left.
1886 // If there is no editor it still keeps the arrow button to the right.
1887 public void addLayoutComponent(String name, Component comp) {}
1888
1889 public void removeLayoutComponent(Component comp) {}
1890
1891 public Dimension preferredLayoutSize(Container parent) {
1892 return parent.getPreferredSize();
1893 }
1894
1895 public Dimension minimumLayoutSize(Container parent) {
1896 return parent.getMinimumSize();
1897 }
1898
1899 public void layoutContainer(Container parent) {
1900 JComboBox cb = (JComboBox)parent;
1901 int width = cb.getWidth();
1902 int height = cb.getHeight();
1903
1904 Insets insets = getInsets();
1905 int buttonHeight = height - (insets.top + insets.bottom);
1906 int buttonWidth = buttonHeight;
1907 if (arrowButton != null) {
1908 Insets arrowInsets = arrowButton.getInsets();
1909 buttonWidth = squareButton ?
1910 buttonHeight :
1911 arrowButton.getPreferredSize().width + arrowInsets.left + arrowInsets.right;
1912 }
1913 Rectangle cvb;
1914
1915 if (arrowButton != null) {
1916 if (BasicGraphicsUtils.isLeftToRight(cb)) {
1917 arrowButton.setBounds(width - (insets.right + buttonWidth),
1918 insets.top, buttonWidth, buttonHeight);
1919 } else {
1920 arrowButton.setBounds(insets.left, insets.top,
1942 if(!comboBox.isPopupVisible() && !item.equals(comboBox.getSelectedItem())) {
1943 comboBox.setSelectedItem(comboBox.getEditor().getItem());
1944 }
1945 ActionMap am = comboBox.getActionMap();
1946 if (am != null) {
1947 Action action = am.get("enterPressed");
1948 if (action != null) {
1949 action.actionPerformed(new ActionEvent(comboBox, evt.getID(),
1950 evt.getActionCommand(),
1951 evt.getModifiers()));
1952 }
1953 }
1954 }
1955 }
1956 }
1957
1958 class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource {
1959 private String prefix = "";
1960 private String typedString = "";
1961
1962 public int selectionForKey(char aKey,ComboBoxModel aModel) {
1963 if (lastTime == 0L) {
1964 prefix = "";
1965 typedString = "";
1966 }
1967 boolean startingFromSelection = true;
1968
1969 int startIndex = comboBox.getSelectedIndex();
1970 if (time - lastTime < timeFactor) {
1971 typedString += aKey;
1972 if((prefix.length() == 1) && (aKey == prefix.charAt(0))) {
1973 // Subsequent same key presses move the keyboard focus to the next
1974 // object that starts with the same letter.
1975 startIndex++;
1976 } else {
1977 prefix = typedString;
1978 }
1979 } else {
1980 startIndex++;
1981 typedString = "" + aKey;
1982 prefix = typedString;
|
1 /*
2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
44 * The combo box is a compound component which means that it is an aggregate of
45 * many simpler components. This class creates and manages the listeners
46 * on the combo box and the combo box model. These listeners update the user
47 * interface in response to changes in the properties and state of the combo box.
48 * <p>
49 * All event handling is handled by listener classes created with the
50 * <code>createxxxListener()</code> methods and internal classes.
51 * You can change the behavior of this class by overriding the
52 * <code>createxxxListener()</code> methods and supplying your own
53 * event listeners or subclassing from the ones supplied in this class.
54 * <p>
55 * For adding specific actions,
56 * overide <code>installKeyboardActions</code> to add actions in response to
57 * KeyStroke bindings. See the article <a href="http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html">How to Use Key Bindings</a>
58 *
59 * @author Arnaud Weber
60 * @author Tom Santos
61 * @author Mark Davidson
62 */
63 public class BasicComboBoxUI extends ComboBoxUI {
64 protected JComboBox<Object> comboBox;
65 /**
66 * This protected field is implementation specific. Do not access directly
67 * or override.
68 */
69 protected boolean hasFocus = false;
70
71 // Control the selection behavior of the JComboBox when it is used
72 // in the JTable DefaultCellEditor.
73 private boolean isTableCellEditor = false;
74 private static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";
75
76 // This list is for drawing the current item in the combo box.
77 protected JList<Object> listBox;
78
79 // Used to render the currently selected item in the combo box.
80 // It doesn't have anything to do with the popup's rendering.
81 protected CellRendererPane currentValuePane = new CellRendererPane();
82
83 // The implementation of ComboPopup that is used to show the popup.
84 protected ComboPopup popup;
85
86 // The Component that the ComboBoxEditor uses for editing
87 protected Component editor;
88
89 // The arrow button that invokes the popup.
90 protected JButton arrowButton;
91
92 // Listeners that are attached to the JComboBox
93 /**
94 * This protected field is implementation specific. Do not access directly
95 * or override. Override the listener construction method instead.
96 *
97 * @see #createKeyListener
186
187 /**
188 * Indicates whether or not the combo box button should be square.
189 * If square, then the width and height are equal, and are both set to
190 * the height of the combo minus appropriate insets.
191 *
192 * @since 1.7
193 */
194 protected boolean squareButton = true;
195
196 /**
197 * If specified, these insets act as padding around the cell renderer when
198 * laying out and painting the "selected" item in the combo box. These
199 * insets add to those specified by the cell renderer.
200 *
201 * @since 1.7
202 */
203 protected Insets padding;
204
205 // Used for calculating the default size.
206 private static ListCellRenderer<Object> getDefaultListCellRenderer() {
207 @SuppressWarnings("unchecked")
208 ListCellRenderer<Object> renderer = (ListCellRenderer)AppContext.
209 getAppContext().get(COMBO_UI_LIST_CELL_RENDERER_KEY);
210
211 if (renderer == null) {
212 renderer = new DefaultListCellRenderer();
213 AppContext.getAppContext().put(COMBO_UI_LIST_CELL_RENDERER_KEY,
214 new DefaultListCellRenderer());
215 }
216 return renderer;
217 }
218
219 /**
220 * Populates ComboBox's actions.
221 */
222 static void loadActionMap(LazyActionMap map) {
223 map.put(new Actions(Actions.HIDE));
224 map.put(new Actions(Actions.PAGE_DOWN));
225 map.put(new Actions(Actions.PAGE_UP));
226 map.put(new Actions(Actions.HOME));
227 map.put(new Actions(Actions.END));
228 map.put(new Actions(Actions.DOWN));
229 map.put(new Actions(Actions.DOWN_2));
230 map.put(new Actions(Actions.TOGGLE));
231 map.put(new Actions(Actions.TOGGLE_2));
232 map.put(new Actions(Actions.UP));
233 map.put(new Actions(Actions.UP_2));
234 map.put(new Actions(Actions.ENTER));
235 }
236
237 //========================
238 // begin UI Initialization
239 //
240
241 public static ComponentUI createUI(JComponent c) {
242 return new BasicComboBoxUI();
243 }
244
245 @Override
246 public void installUI( JComponent c ) {
247 isMinimumSizeDirty = true;
248
249 @SuppressWarnings("unchecked")
250 JComboBox<Object> tmp = (JComboBox)c;
251 comboBox = tmp;
252 installDefaults();
253 popup = createPopup();
254 listBox = popup.getList();
255
256 // Is this combo box a cell editor?
257 Boolean inTable = (Boolean)c.getClientProperty(IS_TABLE_CELL_EDITOR );
258 if (inTable != null) {
259 isTableCellEditor = inTable.equals(Boolean.TRUE) ? true : false;
260 }
261
262 if ( comboBox.getRenderer() == null || comboBox.getRenderer() instanceof UIResource ) {
263 comboBox.setRenderer( createRenderer() );
264 }
265
266 if ( comboBox.getEditor() == null || comboBox.getEditor() instanceof UIResource ) {
267 comboBox.setEditor( createEditor() );
268 }
269
270 installListeners();
271 installComponents();
494 }
495
496 /**
497 * Creates a layout manager for managing the components which make up the
498 * combo box.
499 *
500 * @return an instance of a layout manager
501 */
502 protected LayoutManager createLayoutManager() {
503 return getHandler();
504 }
505
506 /**
507 * Creates the default renderer that will be used in a non-editiable combo
508 * box. A default renderer will used only if a renderer has not been
509 * explicitly set with <code>setRenderer</code>.
510 *
511 * @return a <code>ListCellRender</code> used for the combo box
512 * @see javax.swing.JComboBox#setRenderer
513 */
514 protected ListCellRenderer<Object> createRenderer() {
515 return new BasicComboBoxRenderer.UIResource();
516 }
517
518 /**
519 * Creates the default editor that will be used in editable combo boxes.
520 * A default editor will be used only if an editor has not been
521 * explicitly set with <code>setEditor</code>.
522 *
523 * @return a <code>ComboBoxEditor</code> used for the combo box
524 * @see javax.swing.JComboBox#setEditor
525 */
526 protected ComboBoxEditor createEditor() {
527 return new BasicComboBoxEditor.UIResource();
528 }
529
530 /**
531 * Returns the shared listener.
532 */
533 private Handler getHandler() {
534 if (handler == null) {
851 UIManager.getColor("ComboBox.buttonBackground"),
852 UIManager.getColor("ComboBox.buttonShadow"),
853 UIManager.getColor("ComboBox.buttonDarkShadow"),
854 UIManager.getColor("ComboBox.buttonHighlight"));
855 button.setName("ComboBox.arrowButton");
856 return button;
857 }
858
859 //
860 // end Sub-Component Management
861 //===============================
862
863
864 //================================
865 // begin ComboBoxUI Implementation
866 //
867
868 /**
869 * Tells if the popup is visible or not.
870 */
871 public boolean isPopupVisible( JComboBox<?> c ) {
872 return popup.isVisible();
873 }
874
875 /**
876 * Hides the popup.
877 */
878 public void setPopupVisible( JComboBox<?> c, boolean v ) {
879 if ( v ) {
880 popup.show();
881 } else {
882 popup.hide();
883 }
884 }
885
886 /**
887 * Determines if the JComboBox is focus traversable. If the JComboBox is editable
888 * this returns false, otherwise it returns true.
889 */
890 public boolean isFocusTraversable( JComboBox<?> c ) {
891 return !comboBox.isEditable();
892 }
893
894 //
895 // end ComboBoxUI Implementation
896 //==============================
897
898
899 //=================================
900 // begin ComponentUI Implementation
901 @Override
902 public void paint( Graphics g, JComponent c ) {
903 hasFocus = comboBox.hasFocus();
904 if ( !comboBox.isEditable() ) {
905 Rectangle r = rectangleForCurrentValue();
906 paintCurrentValueBackground(g,r,hasFocus);
907 paintCurrentValue(g,r,hasFocus);
908 }
909 }
910
942 }
943
944 /**
945 * Returns the baseline.
946 *
947 * @throws NullPointerException {@inheritDoc}
948 * @throws IllegalArgumentException {@inheritDoc}
949 * @see javax.swing.JComponent#getBaseline(int, int)
950 * @since 1.6
951 */
952 @Override
953 public int getBaseline(JComponent c, int width, int height) {
954 super.getBaseline(c, width, height);
955 int baseline = -1;
956 // force sameBaseline to be updated.
957 getDisplaySize();
958 if (sameBaseline) {
959 Insets insets = c.getInsets();
960 height = height - insets.top - insets.bottom;
961 if (!comboBox.isEditable()) {
962 ListCellRenderer<Object> renderer = comboBox.getRenderer();
963 if (renderer == null) {
964 renderer = new DefaultListCellRenderer();
965 }
966 Object value = null;
967 Object prototypeValue = comboBox.getPrototypeDisplayValue();
968 if (prototypeValue != null) {
969 value = prototypeValue;
970 }
971 else if (comboBox.getModel().getSize() > 0) {
972 // Note, we're assuming the baseline is the same for all
973 // cells, if not, this needs to loop through all.
974 value = comboBox.getModel().getElementAt(0);
975 }
976 Component component = renderer.
977 getListCellRendererComponent(listBox, value, -1,
978 false, false);
979 if (component instanceof JLabel) {
980 JLabel label = (JLabel) component;
981 String text = label.getText();
982 if ((text == null) || text.isEmpty()) {
999 }
1000
1001 /**
1002 * Returns an enum indicating how the baseline of the component
1003 * changes as the size changes.
1004 *
1005 * @throws NullPointerException {@inheritDoc}
1006 * @see javax.swing.JComponent#getBaseline(int, int)
1007 * @since 1.6
1008 */
1009 @Override
1010 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
1011 JComponent c) {
1012 super.getBaselineResizeBehavior(c);
1013 // Force sameBaseline to be updated.
1014 getDisplaySize();
1015 if (comboBox.isEditable()) {
1016 return editor.getBaselineResizeBehavior();
1017 }
1018 else if (sameBaseline) {
1019 ListCellRenderer<Object> renderer = comboBox.getRenderer();
1020 if (renderer == null) {
1021 renderer = new DefaultListCellRenderer();
1022 }
1023 Object value = null;
1024 Object prototypeValue = comboBox.getPrototypeDisplayValue();
1025 if (prototypeValue != null) {
1026 value = prototypeValue;
1027 }
1028 else if (comboBox.getModel().getSize() > 0) {
1029 // Note, we're assuming the baseline is the same for all
1030 // cells, if not, this needs to loop through all.
1031 value = comboBox.getModel().getElementAt(0);
1032 }
1033 if (value != null) {
1034 Component component = renderer.
1035 getListCellRendererComponent(listBox, value, -1,
1036 false, false);
1037 return component.getBaselineResizeBehavior();
1038 }
1039 }
1191 /**
1192 * Gets the insets from the JComboBox.
1193 */
1194 protected Insets getInsets() {
1195 return comboBox.getInsets();
1196 }
1197
1198 //
1199 // end Utility Methods
1200 //====================
1201
1202
1203 //===============================
1204 // begin Painting Utility Methods
1205 //
1206
1207 /**
1208 * Paints the currently selected item.
1209 */
1210 public void paintCurrentValue(Graphics g,Rectangle bounds,boolean hasFocus) {
1211 ListCellRenderer<Object> renderer = comboBox.getRenderer();
1212 Component c;
1213
1214 if ( hasFocus && !isPopupVisible(comboBox) ) {
1215 c = renderer.getListCellRendererComponent( listBox,
1216 comboBox.getSelectedItem(),
1217 -1,
1218 true,
1219 false );
1220 }
1221 else {
1222 c = renderer.getListCellRendererComponent( listBox,
1223 comboBox.getSelectedItem(),
1224 -1,
1225 false,
1226 false );
1227 c.setBackground(UIManager.getColor("ComboBox.background"));
1228 }
1229 c.setFont(comboBox.getFont());
1230 if ( hasFocus && !isPopupVisible(comboBox) ) {
1231 c.setForeground(listBox.getSelectionForeground());
1308 }
1309
1310 /**
1311 * Returns the calculated size of the display area. The display area is the
1312 * portion of the combo box in which the selected item is displayed. This
1313 * method will use the prototype display value if it has been set.
1314 * <p>
1315 * For combo boxes with a non trivial number of items, it is recommended to
1316 * use a prototype display value to significantly speed up the display
1317 * size calculation.
1318 *
1319 * @return the size of the display area calculated from the combo box items
1320 * @see javax.swing.JComboBox#setPrototypeDisplayValue
1321 */
1322 protected Dimension getDisplaySize() {
1323 if (!isDisplaySizeDirty) {
1324 return new Dimension(cachedDisplaySize);
1325 }
1326 Dimension result = new Dimension();
1327
1328 ListCellRenderer<Object> renderer = comboBox.getRenderer();
1329 if (renderer == null) {
1330 renderer = new DefaultListCellRenderer();
1331 }
1332
1333 sameBaseline = true;
1334
1335 Object prototypeValue = comboBox.getPrototypeDisplayValue();
1336 if (prototypeValue != null) {
1337 // Calculates the dimension based on the prototype value
1338 result = getSizeForComponent(renderer.getListCellRendererComponent(listBox,
1339 prototypeValue,
1340 -1, false, false));
1341 } else {
1342 // Calculate the dimension by iterating over all the elements in the combo
1343 // box list.
1344 ComboBoxModel<Object> model = comboBox.getModel();
1345 int modelSize = model.getSize();
1346 int baseline = -1;
1347 Dimension d;
1348
1349 Component cpn;
1350
1351 if (modelSize > 0 ) {
1352 for (int i = 0; i < modelSize ; i++ ) {
1353 // Calculates the maximum height and width based on the largest
1354 // element
1355 Object value = model.getElementAt(i);
1356 Component c = renderer.getListCellRendererComponent(
1357 listBox, value, -1, false, false);
1358 d = getSizeForComponent(c);
1359 if (sameBaseline && value != null &&
1360 (!(value instanceof String) || !"".equals(value))) {
1361 int newBaseline = c.getBaseline(d.width, d.height);
1362 if (newBaseline == -1) {
1363 sameBaseline = false;
1364 }
1470 private static class Actions extends UIAction {
1471 private static final String HIDE = "hidePopup";
1472 private static final String DOWN = "selectNext";
1473 private static final String DOWN_2 = "selectNext2";
1474 private static final String TOGGLE = "togglePopup";
1475 private static final String TOGGLE_2 = "spacePopup";
1476 private static final String UP = "selectPrevious";
1477 private static final String UP_2 = "selectPrevious2";
1478 private static final String ENTER = "enterPressed";
1479 private static final String PAGE_DOWN = "pageDownPassThrough";
1480 private static final String PAGE_UP = "pageUpPassThrough";
1481 private static final String HOME = "homePassThrough";
1482 private static final String END = "endPassThrough";
1483
1484 Actions(String name) {
1485 super(name);
1486 }
1487
1488 public void actionPerformed( ActionEvent e ) {
1489 String key = getName();
1490 @SuppressWarnings("unchecked")
1491 JComboBox<Object> comboBox = (JComboBox)e.getSource();
1492 BasicComboBoxUI ui = (BasicComboBoxUI)BasicLookAndFeel.getUIOfType(
1493 comboBox.getUI(), BasicComboBoxUI.class);
1494 if (key == HIDE) {
1495 comboBox.firePopupMenuCanceled();
1496 comboBox.setPopupVisible(false);
1497 }
1498 else if (key == PAGE_DOWN || key == PAGE_UP ||
1499 key == HOME || key == END) {
1500 int index = getNextIndex(comboBox, key);
1501 if (index >= 0 && index < comboBox.getItemCount()) {
1502 if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation") && comboBox.isPopupVisible()) {
1503 ui.listBox.setSelectedIndex(index);
1504 ui.listBox.ensureIndexIsVisible(index);
1505 comboBox.repaint();
1506 } else {
1507 comboBox.setSelectedIndex(index);
1508 }
1509 }
1510 }
1511 else if (key == DOWN) {
1612 JRootPane root = SwingUtilities.getRootPane(comboBox);
1613 if (root != null) {
1614 InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
1615 ActionMap am = root.getActionMap();
1616 if (im != null && am != null) {
1617 Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0));
1618 if (obj != null) {
1619 Action action = am.get(obj);
1620 if (action != null) {
1621 action.actionPerformed(new ActionEvent(
1622 root, e.getID(), e.getActionCommand(),
1623 e.getWhen(), e.getModifiers()));
1624 }
1625 }
1626 }
1627 }
1628 }
1629 }
1630 }
1631
1632 private int getNextIndex(JComboBox<?> comboBox, String key) {
1633 int listHeight = comboBox.getMaximumRowCount();
1634
1635 int selectedIndex = comboBox.getSelectedIndex();
1636 if (UIManager.getBoolean("ComboBox.noActionOnKeyNavigation")
1637 && (comboBox.getUI() instanceof BasicComboBoxUI)) {
1638 selectedIndex = ((BasicComboBoxUI) comboBox.getUI()).listBox.getSelectedIndex();
1639 }
1640
1641 if (key == PAGE_UP) {
1642 int index = selectedIndex - listHeight;
1643 return (index < 0 ? 0: index);
1644 }
1645 else if (key == PAGE_DOWN) {
1646 int index = selectedIndex + listHeight;
1647 int max = comboBox.getItemCount();
1648 return (index < max ? index: max-1);
1649 }
1650 else if (key == HOME) {
1651 return 0;
1652 }
1672 // Shared Handler, implements all listeners
1673 //
1674 private class Handler implements ActionListener, FocusListener,
1675 KeyListener, LayoutManager,
1676 ListDataListener, PropertyChangeListener {
1677 //
1678 // PropertyChangeListener
1679 //
1680 public void propertyChange(PropertyChangeEvent e) {
1681 String propertyName = e.getPropertyName();
1682 if (e.getSource() == editor){
1683 // If the border of the editor changes then this can effect
1684 // the size of the editor which can cause the combo's size to
1685 // become invalid so we need to clear size caches
1686 if ("border".equals(propertyName)){
1687 isMinimumSizeDirty = true;
1688 isDisplaySizeDirty = true;
1689 comboBox.revalidate();
1690 }
1691 } else {
1692 @SuppressWarnings("unchecked")
1693 JComboBox<?> comboBox = (JComboBox)e.getSource();
1694 if ( propertyName == "model" ) {
1695 @SuppressWarnings("unchecked")
1696 ComboBoxModel<?> newModel = (ComboBoxModel)e.getNewValue();
1697 @SuppressWarnings("unchecked")
1698 ComboBoxModel<?> oldModel = (ComboBoxModel)e.getOldValue();
1699
1700 if ( oldModel != null && listDataListener != null ) {
1701 oldModel.removeListDataListener( listDataListener );
1702 }
1703
1704 if ( newModel != null && listDataListener != null ) {
1705 newModel.addListDataListener( listDataListener );
1706 }
1707
1708 if ( editor != null ) {
1709 comboBox.configureEditor( comboBox.getEditor(), comboBox.getSelectedItem() );
1710 }
1711 isMinimumSizeDirty = true;
1712 isDisplaySizeDirty = true;
1713 comboBox.revalidate();
1714 comboBox.repaint();
1715 }
1716 else if ( propertyName == "editor" && comboBox.isEditable() ) {
1717 addEditor();
1718 comboBox.revalidate();
1887 //
1888 // LayoutManager
1889 //
1890
1891 // This layout manager handles the 'standard' layout of combo boxes.
1892 // It puts the arrow button to the right and the editor to the left.
1893 // If there is no editor it still keeps the arrow button to the right.
1894 public void addLayoutComponent(String name, Component comp) {}
1895
1896 public void removeLayoutComponent(Component comp) {}
1897
1898 public Dimension preferredLayoutSize(Container parent) {
1899 return parent.getPreferredSize();
1900 }
1901
1902 public Dimension minimumLayoutSize(Container parent) {
1903 return parent.getMinimumSize();
1904 }
1905
1906 public void layoutContainer(Container parent) {
1907 @SuppressWarnings("unchecked")
1908 JComboBox<?> cb = (JComboBox)parent;
1909 int width = cb.getWidth();
1910 int height = cb.getHeight();
1911
1912 Insets insets = getInsets();
1913 int buttonHeight = height - (insets.top + insets.bottom);
1914 int buttonWidth = buttonHeight;
1915 if (arrowButton != null) {
1916 Insets arrowInsets = arrowButton.getInsets();
1917 buttonWidth = squareButton ?
1918 buttonHeight :
1919 arrowButton.getPreferredSize().width + arrowInsets.left + arrowInsets.right;
1920 }
1921 Rectangle cvb;
1922
1923 if (arrowButton != null) {
1924 if (BasicGraphicsUtils.isLeftToRight(cb)) {
1925 arrowButton.setBounds(width - (insets.right + buttonWidth),
1926 insets.top, buttonWidth, buttonHeight);
1927 } else {
1928 arrowButton.setBounds(insets.left, insets.top,
1950 if(!comboBox.isPopupVisible() && !item.equals(comboBox.getSelectedItem())) {
1951 comboBox.setSelectedItem(comboBox.getEditor().getItem());
1952 }
1953 ActionMap am = comboBox.getActionMap();
1954 if (am != null) {
1955 Action action = am.get("enterPressed");
1956 if (action != null) {
1957 action.actionPerformed(new ActionEvent(comboBox, evt.getID(),
1958 evt.getActionCommand(),
1959 evt.getModifiers()));
1960 }
1961 }
1962 }
1963 }
1964 }
1965
1966 class DefaultKeySelectionManager implements JComboBox.KeySelectionManager, UIResource {
1967 private String prefix = "";
1968 private String typedString = "";
1969
1970 public int selectionForKey(char aKey,ComboBoxModel<?> aModel) {
1971 if (lastTime == 0L) {
1972 prefix = "";
1973 typedString = "";
1974 }
1975 boolean startingFromSelection = true;
1976
1977 int startIndex = comboBox.getSelectedIndex();
1978 if (time - lastTime < timeFactor) {
1979 typedString += aKey;
1980 if((prefix.length() == 1) && (aKey == prefix.charAt(0))) {
1981 // Subsequent same key presses move the keyboard focus to the next
1982 // object that starts with the same letter.
1983 startIndex++;
1984 } else {
1985 prefix = typedString;
1986 }
1987 } else {
1988 startIndex++;
1989 typedString = "" + aKey;
1990 prefix = typedString;
|