package com.apple.laf;
import java.awt.*;
import java.awt.event.*;
import javax.accessibility.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.*;
import com.apple.laf.ClientPropertyApplicator.Property;
import apple.laf.JRSUIConstants.Size;
import com.apple.laf.AquaUtilControlSize.Sizeable;
import com.apple.laf.AquaUtils.RecyclableSingleton;
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);
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();
getApplicator().attachAndApplyClientProperties(comboBox);
}
protected void uninstallComponents() {
getApplicator().removeFrom(comboBox);
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;
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) {
}
protected ListCellRenderer<Object> createRenderer() {
return new AquaComboBoxRenderer(comboBox);
}
protected ComboPopup () {
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")
class AquaCustomComboTextField extends JTextField {
@SuppressWarnings("serial")
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();
}
} else {
action.actionPerformed(e);
}
}
});
}
public void setText(final String s) {
if (getText().equals(s)) {
return;
}
super.setText(s);
}
}
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();
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);
SwingUtilities.replaceUIActionMap(comboBox, actionMap);
}
@SuppressWarnings("serial")
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);
}
@SuppressWarnings("serial")
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();
}
};
@SuppressWarnings("serial")
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();
}
};
@SuppressWarnings("serial")
private Action highlightFirstAction = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
listBox.setSelectedIndex(0);
listBox.ensureIndexIsVisible(0);
}
};
@SuppressWarnings("serial")
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);
}
};
@SuppressWarnings("serial")
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);
}
};
@SuppressWarnings("serial")
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);
}
};
public ComboPopup () {
return popup;
}
protected LayoutManager createLayoutManager() {
return new AquaComboBoxLayoutManager();
}
class AquaComboBoxLayoutManager extends BasicComboBoxUI.ComboBoxLayoutManager {
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();
editorRect.width += 4;
editorRect.height += 1;
editor.setBounds(editorRect);
}
}
}
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)) {
comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
return;
}
if (comboBox.isPopupVisible()) {
comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
comboBox.setPopupVisible(false);
return;
}
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()));
}
@SuppressWarnings("serial")
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();
}
};
@SuppressWarnings("serial")
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);
}
};
@SuppressWarnings("serial")
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();
}
};
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 != null && arrowButton instanceof AquaComboBoxButton) {
final AquaComboBoxButton button = (AquaComboBoxButton)arrowButton;
final Insets buttonInsets = button.getInsets();
final Insets insets = new Insets(0, 5, 0, 25);
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;
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);
}
final Border border = c.getBorder();
if (border != null) {
final Insets insets = border.getBorderInsets(c);
size.height += insets.top + insets.bottom;
size.width += insets.left + insets.right;
}
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();
}
}