Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/macosx/classes/com/apple/laf/AquaComboBoxUI.java
38831 views
/*1* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;2627import java.awt.*;28import java.awt.event.*;2930import javax.accessibility.*;31import javax.swing.*;32import javax.swing.border.Border;33import javax.swing.event.*;34import javax.swing.plaf.*;35import javax.swing.plaf.basic.*;36import com.apple.laf.ClientPropertyApplicator.Property;37import apple.laf.JRSUIConstants.Size;3839import com.apple.laf.AquaUtilControlSize.Sizeable;40import com.apple.laf.AquaUtils.RecyclableSingleton;4142// Inspired by MetalComboBoxUI, which also has a combined text-and-arrow button for noneditables43public class AquaComboBoxUI extends BasicComboBoxUI implements Sizeable {44static final String POPDOWN_CLIENT_PROPERTY_KEY = "JComboBox.isPopDown";45static final String ISSQUARE_CLIENT_PROPERTY_KEY = "JComboBox.isSquare";4647public static ComponentUI createUI(final JComponent c) {48return new AquaComboBoxUI();49}5051private boolean wasOpaque;52public void installUI(final JComponent c) {53super.installUI(c);5455// this doesn't work right now, because the JComboBox.init() method calls56// .setOpaque(false) directly, and doesn't allow the LaF to decided. Bad Sun!57LookAndFeel.installProperty(c, "opaque", Boolean.FALSE);5859wasOpaque = c.isOpaque();60c.setOpaque(false);61}6263public void uninstallUI(final JComponent c) {64c.setOpaque(wasOpaque);65super.uninstallUI(c);66}6768protected void installListeners() {69super.installListeners();70AquaUtilControlSize.addSizePropertyListener(comboBox);71}7273protected void uninstallListeners() {74AquaUtilControlSize.removeSizePropertyListener(comboBox);75super.uninstallListeners();76}7778protected void installComponents() {79super.installComponents();8081// client properties must be applied after the components have been installed,82// because isSquare and isPopdown are applied to the installed button83getApplicator().attachAndApplyClientProperties(comboBox);84}8586protected void uninstallComponents() {87getApplicator().removeFrom(comboBox);88super.uninstallComponents();89}9091protected ItemListener createItemListener() {92return new ItemListener() {93long lastBlink = 0L;94public void itemStateChanged(final ItemEvent e) {95if (e.getStateChange() != ItemEvent.SELECTED) return;96if (!popup.isVisible()) return;9798// sometimes, multiple selection changes can occur while the popup is up,99// and blinking more than "once" (in a second) is not desirable100final long now = System.currentTimeMillis();101if (now - 1000 < lastBlink) return;102lastBlink = now;103104final JList itemList = popup.getList();105final ListUI listUI = itemList.getUI();106if (!(listUI instanceof AquaListUI)) return;107final AquaListUI aquaListUI = (AquaListUI)listUI;108109final int selectedIndex = comboBox.getSelectedIndex();110final ListModel dataModel = itemList.getModel();111if (dataModel == null) return;112113final Object value = dataModel.getElementAt(selectedIndex);114AquaUtils.blinkMenu(new AquaUtils.Selectable() {115public void paintSelected(final boolean selected) {116aquaListUI.repaintCell(value, selectedIndex, selected);117}118});119}120};121}122123public void paint(final Graphics g, final JComponent c) {124// this space intentionally left blank125}126127protected ListCellRenderer createRenderer() {128return new AquaComboBoxRenderer(comboBox);129}130131protected ComboPopup createPopup() {132return new AquaComboBoxPopup(comboBox);133}134135protected JButton createArrowButton() {136return new AquaComboBoxButton(this, comboBox, currentValuePane, listBox);137}138139protected ComboBoxEditor createEditor() {140return new AquaComboBoxEditor();141}142143final class AquaComboBoxEditor extends BasicComboBoxEditor144implements UIResource, DocumentListener {145146AquaComboBoxEditor() {147super();148editor = new AquaCustomComboTextField();149editor.addFocusListener(this);150editor.getDocument().addDocumentListener(this);151}152153@Override154public void changedUpdate(final DocumentEvent e) {155editorTextChanged();156}157158@Override159public void insertUpdate(final DocumentEvent e) {160editorTextChanged();161}162163@Override164public void removeUpdate(final DocumentEvent e) {165editorTextChanged();166}167168private void editorTextChanged() {169if (!popup.isVisible()) return;170171final Object text = editor.getText();172173final ListModel model = listBox.getModel();174final int items = model.getSize();175for (int i = 0; i < items; i++) {176final Object element = model.getElementAt(i);177if (element == null) continue;178179final String asString = element.toString();180if (asString == null || !asString.equals(text)) continue;181182popup.getList().setSelectedIndex(i);183return;184}185186popup.getList().clearSelection();187}188}189190class AquaCustomComboTextField extends JTextField {191public AquaCustomComboTextField() {192final InputMap inputMap = getInputMap();193inputMap.put(KeyStroke.getKeyStroke("DOWN"), highlightNextAction);194inputMap.put(KeyStroke.getKeyStroke("KP_DOWN"), highlightNextAction);195inputMap.put(KeyStroke.getKeyStroke("UP"), highlightPreviousAction);196inputMap.put(KeyStroke.getKeyStroke("KP_UP"), highlightPreviousAction);197198inputMap.put(KeyStroke.getKeyStroke("HOME"), highlightFirstAction);199inputMap.put(KeyStroke.getKeyStroke("END"), highlightLastAction);200inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), highlightPageUpAction);201inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), highlightPageDownAction);202203final Action action = getActionMap().get(JTextField.notifyAction);204inputMap.put(KeyStroke.getKeyStroke("ENTER"), new AbstractAction() {205public void actionPerformed(final ActionEvent e) {206if (popup.isVisible()) {207triggerSelectionEvent(comboBox, e);208209if (editor instanceof AquaCustomComboTextField) {210((AquaCustomComboTextField)editor).selectAll();211}212} else {213action.actionPerformed(e);214}215}216});217}218219// workaround for 4530952220public void setText(final String s) {221if (getText().equals(s)) {222return;223}224super.setText(s);225}226}227228/**229* This listener hides the popup when the focus is lost. It also repaints230* when focus is gained or lost.231*232* This override is necessary because the Basic L&F for the combo box is working233* around a Solaris-only bug that we don't have on Mac OS X. So, remove the lightweight234* popup check here. rdar://Problem/3518582235*/236protected FocusListener createFocusListener() {237return new BasicComboBoxUI.FocusHandler() {238@Override239public void focusGained(FocusEvent e) {240super.focusGained(e);241242if (arrowButton != null) {243arrowButton.repaint();244}245}246247@Override248public void focusLost(final FocusEvent e) {249hasFocus = false;250if (!e.isTemporary()) {251setPopupVisible(comboBox, false);252}253comboBox.repaint();254255// Notify assistive technologies that the combo box lost focus256final AccessibleContext ac = ((Accessible)comboBox).getAccessibleContext();257if (ac != null) {258ac.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.FOCUSED, null);259}260261if (arrowButton != null) {262arrowButton.repaint();263}264}265};266}267268protected void installKeyboardActions() {269super.installKeyboardActions();270271ActionMap actionMap = new ActionMapUIResource();272273actionMap.put("aquaSelectNext", highlightNextAction);274actionMap.put("aquaSelectPrevious", highlightPreviousAction);275actionMap.put("enterPressed", triggerSelectionAction);276actionMap.put("aquaSpacePressed", toggleSelectionAction);277278actionMap.put("aquaSelectHome", highlightFirstAction);279actionMap.put("aquaSelectEnd", highlightLastAction);280actionMap.put("aquaSelectPageUp", highlightPageUpAction);281actionMap.put("aquaSelectPageDown", highlightPageDownAction);282283actionMap.put("aquaHidePopup", hideAction);284285SwingUtilities.replaceUIActionMap(comboBox, actionMap);286}287288private abstract class ComboBoxAction extends AbstractAction {289public void actionPerformed(final ActionEvent e) {290if (!comboBox.isEnabled() || !comboBox.isShowing()) {291return;292}293294if (comboBox.isPopupVisible()) {295final AquaComboBoxUI ui = (AquaComboBoxUI)comboBox.getUI();296performComboBoxAction(ui);297} else {298comboBox.setPopupVisible(true);299}300}301302abstract void performComboBoxAction(final AquaComboBoxUI ui);303}304305/**306* Hilight _but do not select_ the next item in the list.307*/308private Action highlightNextAction = new ComboBoxAction() {309@Override310public void performComboBoxAction(AquaComboBoxUI ui) {311final int si = listBox.getSelectedIndex();312313if (si < comboBox.getModel().getSize() - 1) {314listBox.setSelectedIndex(si + 1);315listBox.ensureIndexIsVisible(si + 1);316}317comboBox.repaint();318}319};320321/**322* Hilight _but do not select_ the previous item in the list.323*/324private Action highlightPreviousAction = new ComboBoxAction() {325@Override326void performComboBoxAction(final AquaComboBoxUI ui) {327final int si = listBox.getSelectedIndex();328if (si > 0) {329listBox.setSelectedIndex(si - 1);330listBox.ensureIndexIsVisible(si - 1);331}332comboBox.repaint();333}334};335336private Action highlightFirstAction = new ComboBoxAction() {337@Override338void performComboBoxAction(final AquaComboBoxUI ui) {339listBox.setSelectedIndex(0);340listBox.ensureIndexIsVisible(0);341}342};343344private Action highlightLastAction = new ComboBoxAction() {345@Override346void performComboBoxAction(final AquaComboBoxUI ui) {347final int size = listBox.getModel().getSize();348listBox.setSelectedIndex(size - 1);349listBox.ensureIndexIsVisible(size - 1);350}351};352353private Action highlightPageUpAction = new ComboBoxAction() {354@Override355void performComboBoxAction(final AquaComboBoxUI ui) {356final int current = listBox.getSelectedIndex();357final int first = listBox.getFirstVisibleIndex();358359if (current != first) {360listBox.setSelectedIndex(first);361return;362}363364final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height;365int target = first - page;366if (target < 0) target = 0;367368listBox.ensureIndexIsVisible(target);369listBox.setSelectedIndex(target);370}371};372373private Action highlightPageDownAction = new ComboBoxAction() {374@Override375void performComboBoxAction(final AquaComboBoxUI ui) {376final int current = listBox.getSelectedIndex();377final int last = listBox.getLastVisibleIndex();378379if (current != last) {380listBox.setSelectedIndex(last);381return;382}383384final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height;385final int end = listBox.getModel().getSize() - 1;386int target = last + page;387if (target > end) target = end;388389listBox.ensureIndexIsVisible(target);390listBox.setSelectedIndex(target);391}392};393394// For <rdar://problem/3759984> Java 1.4.2_5: Serializing Swing components not working395// Inner classes were using a this reference and then trying to serialize the AquaComboBoxUI396// We shouldn't do that. But we need to be able to get the popup from other classes, so we need397// a public accessor.398public ComboPopup getPopup() {399return popup;400}401402protected LayoutManager createLayoutManager() {403return new AquaComboBoxLayoutManager();404}405406class AquaComboBoxLayoutManager extends BasicComboBoxUI.ComboBoxLayoutManager {407public void layoutContainer(final Container parent) {408if (arrowButton != null && !comboBox.isEditable()) {409final Insets insets = comboBox.getInsets();410final int width = comboBox.getWidth();411final int height = comboBox.getHeight();412arrowButton.setBounds(insets.left, insets.top, width - (insets.left + insets.right), height - (insets.top + insets.bottom));413return;414}415416final JComboBox cb = (JComboBox)parent;417final int width = cb.getWidth();418final int height = cb.getHeight();419420final Insets insets = getInsets();421final int buttonHeight = height - (insets.top + insets.bottom);422final int buttonWidth = 20;423424if (arrowButton != null) {425arrowButton.setBounds(width - (insets.right + buttonWidth), insets.top, buttonWidth, buttonHeight);426}427428if (editor != null) {429final Rectangle editorRect = rectangleForCurrentValue();430editorRect.width += 4;431editorRect.height += 1;432editor.setBounds(editorRect);433}434}435}436437// This is here because Sun can't use protected like they should!438protected static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";439440protected static boolean isTableCellEditor(final JComponent c) {441return Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.IS_TABLE_CELL_EDITOR));442}443444protected static boolean isPopdown(final JComboBox c) {445return c.isEditable() || Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.POPDOWN_CLIENT_PROPERTY_KEY));446}447448protected static void triggerSelectionEvent(final JComboBox comboBox, final ActionEvent e) {449if (!comboBox.isEnabled()) return;450451final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI();452453if (aquaUi.getPopup().getList().getSelectedIndex() < 0) {454comboBox.setPopupVisible(false);455}456457if (isTableCellEditor(comboBox)) {458// Forces the selection of the list item if the combo box is in a JTable459comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());460return;461}462463if (comboBox.isPopupVisible()) {464comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());465comboBox.setPopupVisible(false);466return;467}468469// Call the default button binding.470// This is a pretty messy way of passing an event through to the root pane471final JRootPane root = SwingUtilities.getRootPane(comboBox);472if (root == null) return;473474final InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);475final ActionMap am = root.getActionMap();476if (im == null || am == null) return;477478final Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));479if (obj == null) return;480481final Action action = am.get(obj);482if (action == null) return;483484action.actionPerformed(new ActionEvent(root, e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers()));485}486487// This is somewhat messy. The difference here from BasicComboBoxUI.EnterAction is that488// arrow up or down does not automatically select the489private final Action triggerSelectionAction = new AbstractAction() {490public void actionPerformed(final ActionEvent e) {491triggerSelectionEvent((JComboBox)e.getSource(), e);492}493494@Override495public boolean isEnabled() {496return comboBox.isPopupVisible() && super.isEnabled();497}498};499500private static final Action toggleSelectionAction = new AbstractAction() {501public void actionPerformed(final ActionEvent e) {502final JComboBox comboBox = (JComboBox)e.getSource();503if (!comboBox.isEnabled()) return;504if (comboBox.isEditable()) return;505506final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI();507508if (comboBox.isPopupVisible()) {509comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());510comboBox.setPopupVisible(false);511return;512}513514comboBox.setPopupVisible(true);515}516};517518private final Action hideAction = new AbstractAction() {519@Override520public void actionPerformed(final ActionEvent e) {521final JComboBox comboBox = (JComboBox)e.getSource();522comboBox.firePopupMenuCanceled();523comboBox.setPopupVisible(false);524}525526@Override527public boolean isEnabled() {528return comboBox.isPopupVisible() && super.isEnabled();529}530};531532public void applySizeFor(final JComponent c, final Size size) {533if (arrowButton == null) return;534final Border border = arrowButton.getBorder();535if (!(border instanceof AquaButtonBorder)) return;536final AquaButtonBorder aquaBorder = (AquaButtonBorder)border;537arrowButton.setBorder(aquaBorder.deriveBorderForSize(size));538}539540public Dimension getMinimumSize(final JComponent c) {541if (!isMinimumSizeDirty) {542return new Dimension(cachedMinimumSize);543}544545final boolean editable = comboBox.isEditable();546547final Dimension size;548if (!editable && arrowButton != null && arrowButton instanceof AquaComboBoxButton) {549final AquaComboBoxButton button = (AquaComboBoxButton)arrowButton;550final Insets buttonInsets = button.getInsets();551// Insets insets = comboBox.getInsets();552final Insets insets = new Insets(0, 5, 0, 25);//comboBox.getInsets();553554size = getDisplaySize();555size.width += insets.left + insets.right;556size.width += buttonInsets.left + buttonInsets.right;557size.width += buttonInsets.right + 10;558size.height += insets.top + insets.bottom;559size.height += buttonInsets.top + buttonInsets.bottom;560// Min height = Height of arrow button plus 2 pixels fuzz above plus 2 below. 23 + 2 + 2561size.height = Math.max(27, size.height);562} else if (editable && arrowButton != null && editor != null) {563size = super.getMinimumSize(c);564final Insets margin = arrowButton.getMargin();565size.height += margin.top + margin.bottom;566} else {567size = super.getMinimumSize(c);568}569570final Border border = c.getBorder();571if (border != null) {572final Insets insets = border.getBorderInsets(c);573size.height += insets.top + insets.bottom;574size.width += insets.left + insets.right;575}576577cachedMinimumSize.setSize(size.width, size.height);578isMinimumSizeDirty = false;579580return new Dimension(cachedMinimumSize);581}582583@SuppressWarnings("unchecked")584static final RecyclableSingleton<ClientPropertyApplicator<JComboBox, AquaComboBoxUI>> APPLICATOR = new RecyclableSingleton<ClientPropertyApplicator<JComboBox, AquaComboBoxUI>>() {585@Override586protected ClientPropertyApplicator<JComboBox, AquaComboBoxUI> getInstance() {587return new ClientPropertyApplicator<JComboBox, AquaComboBoxUI>(588new Property<AquaComboBoxUI>(AquaFocusHandler.FRAME_ACTIVE_PROPERTY) {589public void applyProperty(final AquaComboBoxUI target, final Object value) {590if (Boolean.FALSE.equals(value)) {591if (target.comboBox != null) target.comboBox.hidePopup();592}593if (target.listBox != null) target.listBox.repaint();594}595},596new Property<AquaComboBoxUI>("editable") {597public void applyProperty(final AquaComboBoxUI target, final Object value) {598if (target.comboBox == null) return;599target.comboBox.repaint();600}601},602new Property<AquaComboBoxUI>("background") {603public void applyProperty(final AquaComboBoxUI target, final Object value) {604final Color color = (Color)value;605if (target.arrowButton != null) target.arrowButton.setBackground(color);606if (target.listBox != null) target.listBox.setBackground(color);607}608},609new Property<AquaComboBoxUI>("foreground") {610public void applyProperty(final AquaComboBoxUI target, final Object value) {611final Color color = (Color)value;612if (target.arrowButton != null) target.arrowButton.setForeground(color);613if (target.listBox != null) target.listBox.setForeground(color);614}615},616new Property<AquaComboBoxUI>(POPDOWN_CLIENT_PROPERTY_KEY) {617public void applyProperty(final AquaComboBoxUI target, final Object value) {618if (!(target.arrowButton instanceof AquaComboBoxButton)) return;619((AquaComboBoxButton)target.arrowButton).setIsPopDown(Boolean.TRUE.equals(value));620}621},622new Property<AquaComboBoxUI>(ISSQUARE_CLIENT_PROPERTY_KEY) {623public void applyProperty(final AquaComboBoxUI target, final Object value) {624if (!(target.arrowButton instanceof AquaComboBoxButton)) return;625((AquaComboBoxButton)target.arrowButton).setIsSquare(Boolean.TRUE.equals(value));626}627}628) {629public AquaComboBoxUI convertJComponentToTarget(final JComboBox combo) {630final ComboBoxUI comboUI = combo.getUI();631if (comboUI instanceof AquaComboBoxUI) return (AquaComboBoxUI)comboUI;632return null;633}634};635}636};637static ClientPropertyApplicator<JComboBox, AquaComboBoxUI> getApplicator() {638return APPLICATOR.get();639}640}641642643