| /* | |
| * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package com.apple.laf; | |
| import java.awt.Color; | |
| import java.awt.Container; | |
| import java.awt.Dimension; | |
| import java.awt.Graphics; | |
| import java.awt.Insets; | |
| import java.awt.LayoutManager; | |
| import java.awt.Rectangle; | |
| import java.awt.event.ActionEvent; | |
| import java.awt.event.FocusEvent; | |
| import java.awt.event.FocusListener; | |
| import java.awt.event.ItemEvent; | |
| import java.awt.event.ItemListener; | |
| import java.awt.event.KeyEvent; | |
| import javax.accessibility.Accessible; | |
| import javax.accessibility.AccessibleContext; | |
| import javax.accessibility.AccessibleState; | |
| import javax.swing.AbstractAction; | |
| import javax.swing.Action; | |
| import javax.swing.ActionMap; | |
| import javax.swing.ComboBoxEditor; | |
| import javax.swing.InputMap; | |
| import javax.swing.JButton; | |
| import javax.swing.JComboBox; | |
| import javax.swing.JComponent; | |
| import javax.swing.JList; | |
| import javax.swing.JRootPane; | |
| import javax.swing.JTextField; | |
| import javax.swing.KeyStroke; | |
| import javax.swing.ListCellRenderer; | |
| import javax.swing.ListModel; | |
| import javax.swing.LookAndFeel; | |
| import javax.swing.SwingUtilities; | |
| import javax.swing.border.Border; | |
| import javax.swing.event.DocumentEvent; | |
| import javax.swing.event.DocumentListener; | |
| import javax.swing.plaf.ActionMapUIResource; | |
| import javax.swing.plaf.ComboBoxUI; | |
| import javax.swing.plaf.ComponentUI; | |
| import javax.swing.plaf.ListUI; | |
| import javax.swing.plaf.UIResource; | |
| import javax.swing.plaf.basic.BasicComboBoxEditor; | |
| import javax.swing.plaf.basic.BasicComboBoxUI; | |
| import javax.swing.plaf.basic.ComboPopup; | |
| import apple.laf.JRSUIConstants.Size; | |
| import com.apple.laf.AquaUtilControlSize.Sizeable; | |
| import com.apple.laf.AquaUtils.RecyclableSingleton; | |
| import com.apple.laf.ClientPropertyApplicator.Property; | |
| // Inspired by MetalComboBoxUI, which also has a combined text-and-arrow button for noneditables | |
| public class AquaComboBoxUI extends BasicComboBoxUI implements Sizeable { | |
| static final String POPDOWN_CLIENT_PROPERTY_KEY = "JComboBox.isPopDown"; | |
| static final String ISSQUARE_CLIENT_PROPERTY_KEY = "JComboBox.isSquare"; | |
| public static ComponentUI createUI(final JComponent c) { | |
| return new AquaComboBoxUI(); | |
| } | |
| private boolean wasOpaque; | |
| public void installUI(final JComponent c) { | |
| super.installUI(c); | |
| // this doesn't work right now, because the JComboBox.init() method calls | |
| // .setOpaque(false) directly, and doesn't allow the LaF to decided. Bad Sun! | |
| LookAndFeel.installProperty(c, "opaque", Boolean.FALSE); | |
| wasOpaque = c.isOpaque(); | |
| c.setOpaque(false); | |
| } | |
| public void uninstallUI(final JComponent c) { | |
| c.setOpaque(wasOpaque); | |
| super.uninstallUI(c); | |
| } | |
| protected void installListeners() { | |
| super.installListeners(); | |
| AquaUtilControlSize.addSizePropertyListener(comboBox); | |
| } | |
| protected void uninstallListeners() { | |
| AquaUtilControlSize.removeSizePropertyListener(comboBox); | |
| super.uninstallListeners(); | |
| } | |
| protected void installComponents() { | |
| super.installComponents(); | |
| // client properties must be applied after the components have been installed, | |
| // because isSquare and isPopdown are applied to the installed button | |
| getApplicator().attachAndApplyClientProperties(comboBox); | |
| } | |
| protected void uninstallComponents() { | |
| getApplicator().removeFrom(comboBox); | |
| // AquaButtonUI install some listeners to all parents, which means that | |
| // we need to uninstall UI here to remove those listeners, because after | |
| // we remove them from ComboBox we lost the latest reference to them, | |
| // and our standard uninstallUI machinery will not call them. | |
| arrowButton.getUI().uninstallUI(arrowButton); | |
| super.uninstallComponents(); | |
| } | |
| protected ItemListener createItemListener() { | |
| return new ItemListener() { | |
| long lastBlink = 0L; | |
| public void itemStateChanged(final ItemEvent e) { | |
| if (e.getStateChange() != ItemEvent.SELECTED) return; | |
| if (!popup.isVisible()) return; | |
| // sometimes, multiple selection changes can occur while the popup is up, | |
| // and blinking more than "once" (in a second) is not desirable | |
| final long now = System.currentTimeMillis(); | |
| if (now - 1000 < lastBlink) return; | |
| lastBlink = now; | |
| final JList<Object> itemList = popup.getList(); | |
| final ListUI listUI = itemList.getUI(); | |
| if (!(listUI instanceof AquaListUI)) return; | |
| final AquaListUI aquaListUI = (AquaListUI)listUI; | |
| final int selectedIndex = comboBox.getSelectedIndex(); | |
| final ListModel<Object> dataModel = itemList.getModel(); | |
| if (dataModel == null) return; | |
| final Object value = dataModel.getElementAt(selectedIndex); | |
| AquaUtils.blinkMenu(new AquaUtils.Selectable() { | |
| public void paintSelected(final boolean selected) { | |
| aquaListUI.repaintCell(value, selectedIndex, selected); | |
| } | |
| }); | |
| } | |
| }; | |
| } | |
| public void paint(final Graphics g, final JComponent c) { | |
| // this space intentionally left blank | |
| } | |
| protected ListCellRenderer<Object> createRenderer() { | |
| return new AquaComboBoxRenderer(comboBox); | |
| } | |
| protected ComboPopup createPopup() { | |
| return new AquaComboBoxPopup(comboBox); | |
| } | |
| protected JButton createArrowButton() { | |
| return new AquaComboBoxButton(this, comboBox, currentValuePane, listBox); | |
| } | |
| protected ComboBoxEditor createEditor() { | |
| return new AquaComboBoxEditor(); | |
| } | |
| final class AquaComboBoxEditor extends BasicComboBoxEditor | |
| implements UIResource, DocumentListener { | |
| AquaComboBoxEditor() { | |
| super(); | |
| editor = new AquaCustomComboTextField(); | |
| editor.addFocusListener(this); | |
| editor.getDocument().addDocumentListener(this); | |
| } | |
| @Override | |
| public void changedUpdate(final DocumentEvent e) { | |
| editorTextChanged(); | |
| } | |
| @Override | |
| public void insertUpdate(final DocumentEvent e) { | |
| editorTextChanged(); | |
| } | |
| @Override | |
| public void removeUpdate(final DocumentEvent e) { | |
| editorTextChanged(); | |
| } | |
| private void editorTextChanged() { | |
| if (!popup.isVisible()) return; | |
| final Object text = editor.getText(); | |
| final ListModel<Object> model = listBox.getModel(); | |
| final int items = model.getSize(); | |
| for (int i = 0; i < items; i++) { | |
| final Object element = model.getElementAt(i); | |
| if (element == null) continue; | |
| final String asString = element.toString(); | |
| if (asString == null || !asString.equals(text)) continue; | |
| popup.getList().setSelectedIndex(i); | |
| return; | |
| } | |
| popup.getList().clearSelection(); | |
| } | |
| } | |
| @SuppressWarnings("serial") // Superclass is not serializable across versions | |
| class AquaCustomComboTextField extends JTextField { | |
| public AquaCustomComboTextField() { | |
| final InputMap inputMap = getInputMap(); | |
| inputMap.put(KeyStroke.getKeyStroke("DOWN"), highlightNextAction); | |
| inputMap.put(KeyStroke.getKeyStroke("KP_DOWN"), highlightNextAction); | |
| inputMap.put(KeyStroke.getKeyStroke("UP"), highlightPreviousAction); | |
| inputMap.put(KeyStroke.getKeyStroke("KP_UP"), highlightPreviousAction); | |
| inputMap.put(KeyStroke.getKeyStroke("HOME"), highlightFirstAction); | |
| inputMap.put(KeyStroke.getKeyStroke("END"), highlightLastAction); | |
| inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), highlightPageUpAction); | |
| inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), highlightPageDownAction); | |
| final Action action = getActionMap().get(JTextField.notifyAction); | |
| inputMap.put(KeyStroke.getKeyStroke("ENTER"), new AbstractAction() { | |
| public void actionPerformed(final ActionEvent e) { | |
| if (popup.isVisible()) { | |
| triggerSelectionEvent(comboBox, e); | |
| if (editor instanceof AquaCustomComboTextField) { | |
| ((AquaCustomComboTextField)editor).selectAll(); | |
| } | |
| } | |
| action.actionPerformed(e); | |
| } | |
| }); | |
| } | |
| // workaround for 4530952 | |
| public void setText(final String s) { | |
| if (getText().equals(s)) { | |
| return; | |
| } | |
| super.setText(s); | |
| } | |
| } | |
| /** | |
| * This listener hides the popup when the focus is lost. It also repaints | |
| * when focus is gained or lost. | |
| * | |
| * This override is necessary because the Basic L&F for the combo box is working | |
| * around a Solaris-only bug that we don't have on Mac OS X. So, remove the lightweight | |
| * popup check here. rdar://Problem/3518582 | |
| */ | |
| protected FocusListener createFocusListener() { | |
| return new BasicComboBoxUI.FocusHandler() { | |
| @Override | |
| public void focusGained(FocusEvent e) { | |
| super.focusGained(e); | |
| if (arrowButton != null) { | |
| arrowButton.repaint(); | |
| } | |
| } | |
| @Override | |
| public void focusLost(final FocusEvent e) { | |
| hasFocus = false; | |
| if (!e.isTemporary()) { | |
| setPopupVisible(comboBox, false); | |
| } | |
| comboBox.repaint(); | |
| // Notify assistive technologies that the combo box lost focus | |
| final AccessibleContext ac = ((Accessible)comboBox).getAccessibleContext(); | |
| if (ac != null) { | |
| ac.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.FOCUSED, null); | |
| } | |
| if (arrowButton != null) { | |
| arrowButton.repaint(); | |
| } | |
| } | |
| }; | |
| } | |
| protected void installKeyboardActions() { | |
| super.installKeyboardActions(); | |
| ActionMap actionMap = new ActionMapUIResource(); | |
| actionMap.put("aquaSelectNext", highlightNextAction); | |
| actionMap.put("aquaSelectPrevious", highlightPreviousAction); | |
| actionMap.put("enterPressed", triggerSelectionAction); | |
| actionMap.put("aquaSpacePressed", toggleSelectionAction); | |
| actionMap.put("aquaSelectHome", highlightFirstAction); | |
| actionMap.put("aquaSelectEnd", highlightLastAction); | |
| actionMap.put("aquaSelectPageUp", highlightPageUpAction); | |
| actionMap.put("aquaSelectPageDown", highlightPageDownAction); | |
| actionMap.put("aquaHidePopup", hideAction); | |
| actionMap.put("aquaOpenPopupOrhighlightLast", openPopupOrHighlightLast); | |
| actionMap.put("aquaOpenPopupOrhighlightFirst", openPopupOrHighlightFirst); | |
| SwingUtilities.replaceUIActionMap(comboBox, actionMap); | |
| } | |
| @SuppressWarnings("serial") // Superclass is not serializable across versions | |
| private abstract class ComboBoxAction extends AbstractAction { | |
| public void actionPerformed(final ActionEvent e) { | |
| if (!comboBox.isEnabled() || !comboBox.isShowing()) { | |
| return; | |
| } | |
| if (comboBox.isPopupVisible()) { | |
| final AquaComboBoxUI ui = (AquaComboBoxUI)comboBox.getUI(); | |
| performComboBoxAction(ui); | |
| } else { | |
| comboBox.setPopupVisible(true); | |
| } | |
| } | |
| abstract void performComboBoxAction(final AquaComboBoxUI ui); | |
| } | |
| /** | |
| * Highlight _but do not select_ the next item in the list. | |
| */ | |
| private Action highlightNextAction = new ComboBoxAction() { | |
| @Override | |
| public void performComboBoxAction(AquaComboBoxUI ui) { | |
| final int si = listBox.getSelectedIndex(); | |
| if (si < comboBox.getModel().getSize() - 1) { | |
| listBox.setSelectedIndex(si + 1); | |
| listBox.ensureIndexIsVisible(si + 1); | |
| } | |
| comboBox.repaint(); | |
| } | |
| }; | |
| /** | |
| * Highlight _but do not select_ the previous item in the list. | |
| */ | |
| private Action highlightPreviousAction = new ComboBoxAction() { | |
| @Override | |
| void performComboBoxAction(final AquaComboBoxUI ui) { | |
| final int si = listBox.getSelectedIndex(); | |
| if (si > 0) { | |
| listBox.setSelectedIndex(si - 1); | |
| listBox.ensureIndexIsVisible(si - 1); | |
| } | |
| comboBox.repaint(); | |
| } | |
| }; | |
| private Action highlightFirstAction = new ComboBoxAction() { | |
| @Override | |
| void performComboBoxAction(final AquaComboBoxUI ui) { | |
| listBox.setSelectedIndex(0); | |
| listBox.ensureIndexIsVisible(0); | |
| } | |
| }; | |
| private Action highlightLastAction = new ComboBoxAction() { | |
| @Override | |
| void performComboBoxAction(final AquaComboBoxUI ui) { | |
| final int size = listBox.getModel().getSize(); | |
| listBox.setSelectedIndex(size - 1); | |
| listBox.ensureIndexIsVisible(size - 1); | |
| } | |
| }; | |
| private Action highlightPageUpAction = new ComboBoxAction() { | |
| @Override | |
| void performComboBoxAction(final AquaComboBoxUI ui) { | |
| final int current = listBox.getSelectedIndex(); | |
| final int first = listBox.getFirstVisibleIndex(); | |
| if (current != first) { | |
| listBox.setSelectedIndex(first); | |
| return; | |
| } | |
| final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height; | |
| int target = first - page; | |
| if (target < 0) target = 0; | |
| listBox.ensureIndexIsVisible(target); | |
| listBox.setSelectedIndex(target); | |
| } | |
| }; | |
| private Action highlightPageDownAction = new ComboBoxAction() { | |
| @Override | |
| void performComboBoxAction(final AquaComboBoxUI ui) { | |
| final int current = listBox.getSelectedIndex(); | |
| final int last = listBox.getLastVisibleIndex(); | |
| if (current != last) { | |
| listBox.setSelectedIndex(last); | |
| return; | |
| } | |
| final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height; | |
| final int end = listBox.getModel().getSize() - 1; | |
| int target = last + page; | |
| if (target > end) target = end; | |
| listBox.ensureIndexIsVisible(target); | |
| listBox.setSelectedIndex(target); | |
| } | |
| }; | |
| // For <rdar://problem/3759984> Java 1.4.2_5: Serializing Swing components not working | |
| // Inner classes were using a this reference and then trying to serialize the AquaComboBoxUI | |
| // We shouldn't do that. But we need to be able to get the popup from other classes, so we need | |
| // a public accessor. | |
| public ComboPopup getPopup() { | |
| return popup; | |
| } | |
| protected LayoutManager createLayoutManager() { | |
| return new AquaComboBoxLayoutManager(); | |
| } | |
| class AquaComboBoxLayoutManager extends BasicComboBoxUI.ComboBoxLayoutManager { | |
| protected Rectangle rectangleForCurrentValue() { | |
| int width = comboBox.getWidth(); | |
| int height = comboBox.getBorder() == null ? 22 : comboBox.getHeight(); | |
| Insets insets = getInsets(); | |
| int buttonSize = height - (insets.top + insets.bottom); | |
| if ( arrowButton != null ) { | |
| buttonSize = arrowButton.getWidth(); | |
| } | |
| int midHeight = (comboBox.getHeight() - height - (insets.top + insets.bottom)) / 2 - 1; | |
| if (midHeight < 0) { | |
| midHeight = 0; | |
| } | |
| if (comboBox.getComponentOrientation().isLeftToRight()) { | |
| return new Rectangle(insets.left, insets.top + midHeight, | |
| width - (insets.left + insets.right + buttonSize) + 3, | |
| height - (insets.top + insets.bottom)); | |
| } | |
| else { | |
| return new Rectangle(insets.left + buttonSize, insets.top + midHeight, | |
| width - (insets.left + insets.right + buttonSize) + 3, | |
| height - (insets.top + insets.bottom)); | |
| } | |
| } | |
| public void layoutContainer(final Container parent) { | |
| if (arrowButton != null && !comboBox.isEditable()) { | |
| final Insets insets = comboBox.getInsets(); | |
| final int width = comboBox.getWidth(); | |
| final int height = comboBox.getHeight(); | |
| arrowButton.setBounds(insets.left, insets.top, width - (insets.left + insets.right), height - (insets.top + insets.bottom)); | |
| return; | |
| } | |
| final JComboBox<?> cb = (JComboBox<?>) parent; | |
| final int width = cb.getWidth(); | |
| final int height = cb.getHeight(); | |
| final Insets insets = getInsets(); | |
| final int buttonHeight = height - (insets.top + insets.bottom); | |
| final int buttonWidth = 20; | |
| if (arrowButton != null) { | |
| arrowButton.setBounds(width - (insets.right + buttonWidth), insets.top, buttonWidth, buttonHeight); | |
| } | |
| if (editor != null) { | |
| final Rectangle editorRect = rectangleForCurrentValue(); | |
| editor.setBounds(editorRect); | |
| } | |
| } | |
| } | |
| // This is here because Sun can't use protected like they should! | |
| protected static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor"; | |
| protected static boolean isTableCellEditor(final JComponent c) { | |
| return Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.IS_TABLE_CELL_EDITOR)); | |
| } | |
| protected static boolean isPopdown(final JComboBox<?> c) { | |
| return c.isEditable() || Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.POPDOWN_CLIENT_PROPERTY_KEY)); | |
| } | |
| protected static void triggerSelectionEvent(final JComboBox<?> comboBox, final ActionEvent e) { | |
| if (!comboBox.isEnabled()) return; | |
| final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI(); | |
| if (aquaUi.getPopup().getList().getSelectedIndex() < 0) { | |
| comboBox.setPopupVisible(false); | |
| } | |
| if (isTableCellEditor(comboBox)) { | |
| // Forces the selection of the list item if the combo box is in a JTable | |
| comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex()); | |
| return; | |
| } | |
| if (comboBox.isPopupVisible()) { | |
| comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex()); | |
| comboBox.setPopupVisible(false); | |
| return; | |
| } | |
| // Call the default button binding. | |
| // This is a pretty messy way of passing an event through to the root pane | |
| final JRootPane root = SwingUtilities.getRootPane(comboBox); | |
| if (root == null) return; | |
| final InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); | |
| final ActionMap am = root.getActionMap(); | |
| if (im == null || am == null) return; | |
| final Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); | |
| if (obj == null) return; | |
| final Action action = am.get(obj); | |
| if (action == null) return; | |
| action.actionPerformed(new ActionEvent(root, e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers())); | |
| } | |
| // This is somewhat messy. The difference here from BasicComboBoxUI.EnterAction is that | |
| // arrow up or down does not automatically select the | |
| private final Action triggerSelectionAction = new AbstractAction() { | |
| public void actionPerformed(final ActionEvent e) { | |
| triggerSelectionEvent((JComboBox)e.getSource(), e); | |
| } | |
| @Override | |
| public boolean isEnabled() { | |
| return comboBox.isPopupVisible() && super.isEnabled(); | |
| } | |
| }; | |
| private static final Action toggleSelectionAction = new AbstractAction() { | |
| public void actionPerformed(final ActionEvent e) { | |
| final JComboBox<?> comboBox = (JComboBox<?>) e.getSource(); | |
| if (!comboBox.isEnabled()) return; | |
| if (comboBox.isEditable()) return; | |
| final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI(); | |
| if (comboBox.isPopupVisible()) { | |
| comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex()); | |
| comboBox.setPopupVisible(false); | |
| return; | |
| } | |
| comboBox.setPopupVisible(true); | |
| } | |
| }; | |
| private final Action hideAction = new AbstractAction() { | |
| @Override | |
| public void actionPerformed(final ActionEvent e) { | |
| final JComboBox<?> comboBox = (JComboBox<?>) e.getSource(); | |
| comboBox.firePopupMenuCanceled(); | |
| comboBox.setPopupVisible(false); | |
| } | |
| @Override | |
| public boolean isEnabled() { | |
| return comboBox.isPopupVisible() && super.isEnabled(); | |
| } | |
| }; | |
| private final Action openPopupOrHighlightLast = new ComboBoxAction() { | |
| @Override | |
| void performComboBoxAction(final AquaComboBoxUI ui) { | |
| final int size = listBox.getModel().getSize(); | |
| listBox.setSelectedIndex(size - 1); | |
| listBox.ensureIndexIsVisible(size - 1); | |
| comboBox.setSelectedIndex(ui.getPopup().getList().getSelectedIndex()); | |
| } | |
| }; | |
| private final Action openPopupOrHighlightFirst = new ComboBoxAction() { | |
| @Override | |
| void performComboBoxAction(final AquaComboBoxUI ui) { | |
| listBox.setSelectedIndex(0); | |
| listBox.ensureIndexIsVisible(0); | |
| comboBox.setSelectedIndex(ui.getPopup().getList().getSelectedIndex()); | |
| } | |
| }; | |
| public void applySizeFor(final JComponent c, final Size size) { | |
| if (arrowButton == null) return; | |
| final Border border = arrowButton.getBorder(); | |
| if (!(border instanceof AquaButtonBorder)) return; | |
| final AquaButtonBorder aquaBorder = (AquaButtonBorder)border; | |
| arrowButton.setBorder(aquaBorder.deriveBorderForSize(size)); | |
| } | |
| public Dimension getMinimumSize(final JComponent c) { | |
| if (!isMinimumSizeDirty) { | |
| return new Dimension(cachedMinimumSize); | |
| } | |
| final boolean editable = comboBox.isEditable(); | |
| final Dimension size; | |
| if (!editable && arrowButton instanceof final AquaComboBoxButton button) { | |
| final Insets buttonInsets = button.getInsets(); | |
| // Insets insets = comboBox.getInsets(); | |
| final Insets insets = new Insets(0, 5, 0, 25);//comboBox.getInsets(); | |
| size = getDisplaySize(); | |
| size.width += insets.left + insets.right; | |
| size.width += buttonInsets.left + buttonInsets.right; | |
| size.width += buttonInsets.right + 10; | |
| size.height += insets.top + insets.bottom; | |
| size.height += buttonInsets.top + buttonInsets.bottom; | |
| // Min height = Height of arrow button plus 2 pixels fuzz above plus 2 below. 23 + 2 + 2 | |
| size.height = Math.max(27, size.height); | |
| } else if (editable && arrowButton != null && editor != null) { | |
| size = super.getMinimumSize(c); | |
| final Insets margin = arrowButton.getMargin(); | |
| size.height += margin.top + margin.bottom; | |
| } else { | |
| size = super.getMinimumSize(c); | |
| } | |
| cachedMinimumSize.setSize(size.width, size.height); | |
| isMinimumSizeDirty = false; | |
| return new Dimension(cachedMinimumSize); | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static final RecyclableSingleton<ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI>> APPLICATOR = new | |
| RecyclableSingleton<ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI>>() { | |
| @Override | |
| protected ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI> getInstance() { | |
| return new ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI>( | |
| new Property<AquaComboBoxUI>(AquaFocusHandler.FRAME_ACTIVE_PROPERTY) { | |
| public void applyProperty(final AquaComboBoxUI target, final Object value) { | |
| if (Boolean.FALSE.equals(value)) { | |
| if (target.comboBox != null) target.comboBox.hidePopup(); | |
| } | |
| if (target.listBox != null) target.listBox.repaint(); | |
| } | |
| }, | |
| new Property<AquaComboBoxUI>("editable") { | |
| public void applyProperty(final AquaComboBoxUI target, final Object value) { | |
| if (target.comboBox == null) return; | |
| target.comboBox.repaint(); | |
| } | |
| }, | |
| new Property<AquaComboBoxUI>("background") { | |
| public void applyProperty(final AquaComboBoxUI target, final Object value) { | |
| final Color color = (Color)value; | |
| if (target.arrowButton != null) target.arrowButton.setBackground(color); | |
| if (target.listBox != null) target.listBox.setBackground(color); | |
| } | |
| }, | |
| new Property<AquaComboBoxUI>("foreground") { | |
| public void applyProperty(final AquaComboBoxUI target, final Object value) { | |
| final Color color = (Color)value; | |
| if (target.arrowButton != null) target.arrowButton.setForeground(color); | |
| if (target.listBox != null) target.listBox.setForeground(color); | |
| } | |
| }, | |
| new Property<AquaComboBoxUI>(POPDOWN_CLIENT_PROPERTY_KEY) { | |
| public void applyProperty(final AquaComboBoxUI target, final Object value) { | |
| if (!(target.arrowButton instanceof AquaComboBoxButton)) return; | |
| ((AquaComboBoxButton)target.arrowButton).setIsPopDown(Boolean.TRUE.equals(value)); | |
| } | |
| }, | |
| new Property<AquaComboBoxUI>(ISSQUARE_CLIENT_PROPERTY_KEY) { | |
| public void applyProperty(final AquaComboBoxUI target, final Object value) { | |
| if (!(target.arrowButton instanceof AquaComboBoxButton)) return; | |
| ((AquaComboBoxButton)target.arrowButton).setIsSquare(Boolean.TRUE.equals(value)); | |
| } | |
| } | |
| ) { | |
| public AquaComboBoxUI convertJComponentToTarget(final JComboBox<?> combo) { | |
| final ComboBoxUI comboUI = combo.getUI(); | |
| if (comboUI instanceof AquaComboBoxUI) return (AquaComboBoxUI)comboUI; | |
| return null; | |
| } | |
| }; | |
| } | |
| }; | |
| static ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI> getApplicator() { | |
| return APPLICATOR.get(); | |
| } | |
| @Override | |
| public int getAccessibleChildrenCount(JComponent c) { | |
| return 2; | |
| } | |
| @Override | |
| public Accessible getAccessibleChild(JComponent c, int i) { | |
| // 0 = the popup | |
| // 1 = the editor for editable combobox and the arrow button for non-editable combobox | |
| switch ( i ) { | |
| case 0: | |
| if (popup instanceof Accessible accessiblePopup) { | |
| AccessibleContext ac = accessiblePopup.getAccessibleContext(); | |
| ac.setAccessibleParent(comboBox); | |
| return accessiblePopup; | |
| } | |
| break; | |
| case 1: | |
| if (comboBox.isEditable() | |
| && (editor instanceof Accessible accessibleEditor)) { | |
| AccessibleContext ac = accessibleEditor.getAccessibleContext(); | |
| ac.setAccessibleParent(comboBox); | |
| return accessibleEditor; | |
| } else if (!comboBox.isEditable()) { | |
| return arrowButton; | |
| } | |
| break; | |
| } | |
| return null; | |
| } | |
| } |