1 /*
   2  * Copyright (c) 2011, 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
  23  * questions.
  24  */
  25 
  26 package com.apple.laf;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 
  31 import javax.accessibility.*;
  32 import javax.swing.*;
  33 import javax.swing.border.Border;
  34 import javax.swing.event.*;
  35 import javax.swing.plaf.*;
  36 import javax.swing.plaf.basic.*;
  37 import com.apple.laf.ClientPropertyApplicator.Property;
  38 import apple.laf.JRSUIConstants.Size;
  39 
  40 import com.apple.laf.AquaUtilControlSize.Sizeable;
  41 import com.apple.laf.AquaUtils.RecyclableSingleton;
  42 
  43 // Inspired by MetalComboBoxUI, which also has a combined text-and-arrow button for noneditables
  44 public class AquaComboBoxUI extends BasicComboBoxUI implements Sizeable {
  45     static final String POPDOWN_CLIENT_PROPERTY_KEY = "JComboBox.isPopDown";
  46     static final String ISSQUARE_CLIENT_PROPERTY_KEY = "JComboBox.isSquare";
  47 
  48     public static ComponentUI createUI(final JComponent c) {
  49         return new AquaComboBoxUI();
  50     }
  51 
  52     private boolean wasOpaque;
  53     public void installUI(final JComponent c) {
  54         super.installUI(c);
  55 
  56         // this doesn't work right now, because the JComboBox.init() method calls
  57         // .setOpaque(false) directly, and doesn't allow the LaF to decided. Bad Sun!
  58         LookAndFeel.installProperty(c, "opaque", Boolean.FALSE);
  59 
  60         wasOpaque = c.isOpaque();
  61         c.setOpaque(false);
  62     }
  63 
  64     public void uninstallUI(final JComponent c) {
  65         c.setOpaque(wasOpaque);
  66         super.uninstallUI(c);
  67     }
  68 
  69     protected void installListeners() {
  70         super.installListeners();
  71         AquaUtilControlSize.addSizePropertyListener(comboBox);
  72     }
  73 
  74     protected void uninstallListeners() {
  75         AquaUtilControlSize.removeSizePropertyListener(comboBox);
  76         super.uninstallListeners();
  77     }
  78 
  79     protected void installComponents() {
  80         super.installComponents();
  81 
  82         // client properties must be applied after the components have been installed,
  83         // because isSquare and isPopdown are applied to the installed button
  84         getApplicator().attachAndApplyClientProperties(comboBox);
  85     }
  86 
  87     protected void uninstallComponents() {
  88         getApplicator().removeFrom(comboBox);
  89         super.uninstallComponents();
  90     }
  91 
  92     protected ItemListener createItemListener() {
  93         return new ItemListener() {
  94             long lastBlink = 0L;
  95             public void itemStateChanged(final ItemEvent e) {
  96                 if (e.getStateChange() != ItemEvent.SELECTED) return;
  97                 if (!popup.isVisible()) return;
  98 
  99                 // sometimes, multiple selection changes can occur while the popup is up,
 100                 // and blinking more than "once" (in a second) is not desirable
 101                 final long now = System.currentTimeMillis();
 102                 if (now - 1000 < lastBlink) return;
 103                 lastBlink = now;
 104 
 105                 final JList itemList = popup.getList();
 106                 final ListUI listUI = itemList.getUI();
 107                 if (!(listUI instanceof AquaListUI)) return;
 108                 final AquaListUI aquaListUI = (AquaListUI)listUI;
 109 
 110                 final int selectedIndex = comboBox.getSelectedIndex();
 111                 final ListModel dataModel = itemList.getModel();
 112                 if (dataModel == null) return;
 113 
 114                 final Object value = dataModel.getElementAt(selectedIndex);
 115                 AquaUtils.blinkMenu(new AquaUtils.Selectable() {
 116                     public void paintSelected(final boolean selected) {
 117                         aquaListUI.repaintCell(value, selectedIndex, selected);
 118                     }
 119                 });
 120             }
 121         };
 122     }
 123 
 124     public void paint(final Graphics g, final JComponent c) {
 125         // this space intentionally left blank
 126     }
 127 
 128     protected ListCellRenderer createRenderer() {
 129         return new AquaComboBoxRenderer(comboBox);
 130     }
 131 
 132     protected ComboPopup createPopup() {
 133         return new AquaComboBoxPopup(comboBox);
 134     }
 135 
 136     protected JButton createArrowButton() {
 137         return new AquaComboBoxButton(this, comboBox, currentValuePane, listBox);
 138     }
 139 
 140     protected ComboBoxEditor createEditor() {
 141         return new AquaComboBoxEditor();
 142     }
 143 
 144     final class AquaComboBoxEditor extends BasicComboBoxEditor
 145             implements UIResource, DocumentListener {
 146 
 147         AquaComboBoxEditor() {
 148             super();
 149             editor = new AquaCustomComboTextField();
 150             editor.addFocusListener(this);
 151             editor.getDocument().addDocumentListener(this);
 152         }
 153 
 154         @Override
 155         public void focusGained(final FocusEvent e) {
 156             if (arrowButton != null) {
 157                 arrowButton.repaint();
 158             }
 159         }
 160 
 161         @Override
 162         public void focusLost(final FocusEvent e) {
 163             if (arrowButton != null) {
 164                 arrowButton.repaint();
 165             }
 166         }
 167 
 168         @Override
 169         public void changedUpdate(final DocumentEvent e) {
 170             editorTextChanged();
 171         }
 172 
 173         @Override
 174         public void insertUpdate(final DocumentEvent e) {
 175             editorTextChanged();
 176         }
 177 
 178         @Override
 179         public void removeUpdate(final DocumentEvent e) {
 180             editorTextChanged();
 181         }
 182 
 183         private void editorTextChanged() {
 184             if (!popup.isVisible()) return;
 185 
 186             final Object text = editor.getText();
 187 
 188             final ListModel model = listBox.getModel();
 189             final int items = model.getSize();
 190             for (int i = 0; i < items; i++) {
 191                 final Object element = model.getElementAt(i);
 192                 if (element == null) continue;
 193 
 194                 final String asString = element.toString();
 195                 if (asString == null || !asString.equals(text)) continue;
 196 
 197                 popup.getList().setSelectedIndex(i);
 198                 return;
 199             }
 200 
 201             popup.getList().clearSelection();
 202         }
 203     }
 204 
 205     @SuppressWarnings("serial") // Superclass is not serializable across versions
 206     class AquaCustomComboTextField extends JTextField {
 207         @SuppressWarnings("serial") // anonymous class
 208         public AquaCustomComboTextField() {
 209             final InputMap inputMap = getInputMap();
 210             inputMap.put(KeyStroke.getKeyStroke("DOWN"), highlightNextAction);
 211             inputMap.put(KeyStroke.getKeyStroke("KP_DOWN"), highlightNextAction);
 212             inputMap.put(KeyStroke.getKeyStroke("UP"), highlightPreviousAction);
 213             inputMap.put(KeyStroke.getKeyStroke("KP_UP"), highlightPreviousAction);
 214 
 215             inputMap.put(KeyStroke.getKeyStroke("HOME"), highlightFirstAction);
 216             inputMap.put(KeyStroke.getKeyStroke("END"), highlightLastAction);
 217             inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), highlightPageUpAction);
 218             inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), highlightPageDownAction);
 219 
 220             final Action action = getActionMap().get(JTextField.notifyAction);
 221             inputMap.put(KeyStroke.getKeyStroke("ENTER"), new AbstractAction() {
 222                 public void actionPerformed(final ActionEvent e) {
 223                     if (popup.isVisible()) {
 224                         triggerSelectionEvent(comboBox, e);
 225 
 226                         if (editor instanceof AquaCustomComboTextField) {
 227                             ((AquaCustomComboTextField)editor).selectAll();
 228                         }
 229                     } else {
 230                         action.actionPerformed(e);
 231                     }
 232                 }
 233             });
 234         }
 235 
 236         // workaround for 4530952
 237         public void setText(final String s) {
 238             if (getText().equals(s)) {
 239                 return;
 240             }
 241             super.setText(s);
 242         }
 243     }
 244 
 245     /**
 246      * This listener hides the popup when the focus is lost.  It also repaints
 247      * when focus is gained or lost.
 248      *
 249      * This override is necessary because the Basic L&F for the combo box is working
 250      * around a Solaris-only bug that we don't have on Mac OS X.  So, remove the lightweight
 251      * popup check here. rdar://Problem/3518582
 252      */
 253     protected FocusListener createFocusListener() {
 254         return new BasicComboBoxUI.FocusHandler() {
 255             public void focusLost(final FocusEvent e) {
 256                 hasFocus = false;
 257                 if (!e.isTemporary()) {
 258                     setPopupVisible(comboBox, false);
 259                 }
 260                 comboBox.repaint();
 261 
 262                 // Notify assistive technologies that the combo box lost focus
 263                 final AccessibleContext ac = ((Accessible)comboBox).getAccessibleContext();
 264                 if (ac != null) {
 265                     ac.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.FOCUSED, null);
 266                 }
 267             }
 268         };
 269     }
 270 
 271     protected void installKeyboardActions() {
 272         super.installKeyboardActions();
 273 
 274         ActionMap actionMap = new ActionMapUIResource();
 275 
 276         actionMap.put("aquaSelectNext", highlightNextAction);
 277         actionMap.put("aquaSelectPrevious", highlightPreviousAction);
 278         actionMap.put("aquaEnterPressed", triggerSelectionAction);
 279         actionMap.put("aquaSpacePressed", toggleSelectionAction);
 280 
 281         actionMap.put("aquaSelectHome", highlightFirstAction);
 282         actionMap.put("aquaSelectEnd", highlightLastAction);
 283         actionMap.put("aquaSelectPageUp", highlightPageUpAction);
 284         actionMap.put("aquaSelectPageDown", highlightPageDownAction);
 285 
 286         actionMap.put("aquaHidePopup", hideAction);
 287 
 288         SwingUtilities.replaceUIActionMap(comboBox, actionMap);
 289     }
 290 
 291     @SuppressWarnings("serial") // Superclass is not serializable across versions
 292     private abstract class ComboBoxAction extends AbstractAction {
 293         public void actionPerformed(final ActionEvent e) {
 294             if (!comboBox.isEnabled() || !comboBox.isShowing()) {
 295                 return;
 296             }
 297 
 298             if (comboBox.isPopupVisible()) {
 299                 final AquaComboBoxUI ui = (AquaComboBoxUI)comboBox.getUI();
 300                 performComboBoxAction(ui);
 301             } else {
 302                 comboBox.setPopupVisible(true);
 303             }
 304         }
 305 
 306         abstract void performComboBoxAction(final AquaComboBoxUI ui);
 307     }
 308 
 309     /**
 310      * Hilight _but do not select_ the next item in the list.
 311      */
 312     @SuppressWarnings("serial") // anonymous class
 313     private Action highlightNextAction = new ComboBoxAction() {
 314         @Override
 315         public void performComboBoxAction(AquaComboBoxUI ui) {
 316             final int si = listBox.getSelectedIndex();
 317 
 318             if (si < comboBox.getModel().getSize() - 1) {
 319                 listBox.setSelectedIndex(si + 1);
 320                 listBox.ensureIndexIsVisible(si + 1);
 321             }
 322             comboBox.repaint();
 323         }
 324     };
 325 
 326     /**
 327      * Hilight _but do not select_ the previous item in the list.
 328      */
 329     @SuppressWarnings("serial") // anonymous class
 330     private Action highlightPreviousAction = new ComboBoxAction() {
 331         @Override
 332         void performComboBoxAction(final AquaComboBoxUI ui) {
 333             final int si = listBox.getSelectedIndex();
 334             if (si > 0) {
 335                 listBox.setSelectedIndex(si - 1);
 336                 listBox.ensureIndexIsVisible(si - 1);
 337             }
 338             comboBox.repaint();
 339         }
 340     };
 341 
 342     @SuppressWarnings("serial") // anonymous class
 343     private Action highlightFirstAction = new ComboBoxAction() {
 344         @Override
 345         void performComboBoxAction(final AquaComboBoxUI ui) {
 346             listBox.setSelectedIndex(0);
 347             listBox.ensureIndexIsVisible(0);
 348         }
 349     };
 350 
 351     @SuppressWarnings("serial") // anonymous class
 352     private Action highlightLastAction = new ComboBoxAction() {
 353         @Override
 354         void performComboBoxAction(final AquaComboBoxUI ui) {
 355             final int size = listBox.getModel().getSize();
 356             listBox.setSelectedIndex(size - 1);
 357             listBox.ensureIndexIsVisible(size - 1);
 358         }
 359     };
 360 
 361     @SuppressWarnings("serial") // anonymous class
 362     private Action highlightPageUpAction = new ComboBoxAction() {
 363         @Override
 364         void performComboBoxAction(final AquaComboBoxUI ui) {
 365             final int current = listBox.getSelectedIndex();
 366             final int first = listBox.getFirstVisibleIndex();
 367 
 368             if (current != first) {
 369                 listBox.setSelectedIndex(first);
 370                 return;
 371             }
 372 
 373             final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height;
 374             int target = first - page;
 375             if (target < 0) target = 0;
 376 
 377             listBox.ensureIndexIsVisible(target);
 378             listBox.setSelectedIndex(target);
 379         }
 380     };
 381 
 382     @SuppressWarnings("serial") // anonymous class
 383     private Action highlightPageDownAction = new ComboBoxAction() {
 384         @Override
 385         void performComboBoxAction(final AquaComboBoxUI ui) {
 386             final int current = listBox.getSelectedIndex();
 387             final int last = listBox.getLastVisibleIndex();
 388 
 389             if (current != last) {
 390                 listBox.setSelectedIndex(last);
 391                 return;
 392             }
 393 
 394             final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height;
 395             final int end = listBox.getModel().getSize() - 1;
 396             int target = last + page;
 397             if (target > end) target = end;
 398 
 399             listBox.ensureIndexIsVisible(target);
 400             listBox.setSelectedIndex(target);
 401         }
 402     };
 403 
 404     // For <rdar://problem/3759984> Java 1.4.2_5: Serializing Swing components not working
 405     // Inner classes were using a this reference and then trying to serialize the AquaComboBoxUI
 406     // We shouldn't do that. But we need to be able to get the popup from other classes, so we need
 407     // a public accessor.
 408     public ComboPopup getPopup() {
 409         return popup;
 410     }
 411 
 412     protected LayoutManager createLayoutManager() {
 413         return new AquaComboBoxLayoutManager();
 414     }
 415 
 416     class AquaComboBoxLayoutManager extends BasicComboBoxUI.ComboBoxLayoutManager {
 417         public void layoutContainer(final Container parent) {
 418             if (arrowButton != null && !comboBox.isEditable()) {
 419                 final Insets insets = comboBox.getInsets();
 420                 final int width = comboBox.getWidth();
 421                 final int height = comboBox.getHeight();
 422                 arrowButton.setBounds(insets.left, insets.top, width - (insets.left + insets.right), height - (insets.top + insets.bottom));
 423                 return;
 424             }
 425 
 426             final JComboBox cb = (JComboBox)parent;
 427             final int width = cb.getWidth();
 428             final int height = cb.getHeight();
 429 
 430             final Insets insets = getInsets();
 431             final int buttonHeight = height - (insets.top + insets.bottom);
 432             final int buttonWidth = 20;
 433 
 434             if (arrowButton != null) {
 435                 arrowButton.setBounds(width - (insets.right + buttonWidth), insets.top, buttonWidth, buttonHeight);
 436             }
 437 
 438             if (editor != null) {
 439                 final Rectangle editorRect = rectangleForCurrentValue();
 440                 editorRect.width += 4;
 441                 editor.setBounds(editorRect);
 442             }
 443         }
 444     }
 445 
 446     // This is here because Sun can't use protected like they should!
 447     protected static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";
 448 
 449     protected static boolean isTableCellEditor(final JComponent c) {
 450         return Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.IS_TABLE_CELL_EDITOR));
 451     }
 452 
 453     protected static boolean isPopdown(final JComboBox c) {
 454         return c.isEditable() || Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.POPDOWN_CLIENT_PROPERTY_KEY));
 455     }
 456 
 457     protected static void triggerSelectionEvent(final JComboBox comboBox, final ActionEvent e) {
 458         if (!comboBox.isEnabled()) return;
 459 
 460         final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI();
 461 
 462         if (aquaUi.getPopup().getList().getSelectedIndex() < 0) {
 463             comboBox.setPopupVisible(false);
 464         }
 465 
 466         if (isTableCellEditor(comboBox)) {
 467             // Forces the selection of the list item if the combo box is in a JTable
 468             comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
 469             return;
 470         }
 471 
 472         if (comboBox.isPopupVisible()) {
 473             comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
 474             comboBox.setPopupVisible(false);
 475             return;
 476         }
 477 
 478         // Call the default button binding.
 479         // This is a pretty messy way of passing an event through to the root pane
 480         final JRootPane root = SwingUtilities.getRootPane(comboBox);
 481         if (root == null) return;
 482 
 483         final InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
 484         final ActionMap am = root.getActionMap();
 485         if (im == null || am == null) return;
 486 
 487         final Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
 488         if (obj == null) return;
 489 
 490         final Action action = am.get(obj);
 491         if (action == null) return;
 492 
 493         action.actionPerformed(new ActionEvent(root, e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers()));
 494     }
 495 
 496     // This is somewhat messy.  The difference here from BasicComboBoxUI.EnterAction is that
 497     // arrow up or down does not automatically select the
 498     @SuppressWarnings("serial") // anonymous class
 499     private static final Action triggerSelectionAction = new AbstractAction() {
 500         public void actionPerformed(final ActionEvent e) {
 501             triggerSelectionEvent((JComboBox)e.getSource(), e);
 502         }
 503     };
 504 
 505     @SuppressWarnings("serial") // anonymous class
 506     private static final Action toggleSelectionAction = new AbstractAction() {
 507         public void actionPerformed(final ActionEvent e) {
 508             final JComboBox comboBox = (JComboBox)e.getSource();
 509             if (!comboBox.isEnabled()) return;
 510             if (comboBox.isEditable()) return;
 511 
 512             final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI();
 513 
 514             if (comboBox.isPopupVisible()) {
 515                 comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
 516                 comboBox.setPopupVisible(false);
 517                 return;
 518             }
 519 
 520             comboBox.setPopupVisible(true);
 521         }
 522     };
 523 
 524     @SuppressWarnings("serial") // anonymous class
 525     private static Action hideAction = new AbstractAction() {
 526         @Override
 527         public void actionPerformed(final ActionEvent e) {
 528             final JComboBox comboBox = (JComboBox)e.getSource();
 529 
 530             if (comboBox.isPopupVisible()) {
 531                 comboBox.firePopupMenuCanceled();
 532                 comboBox.setPopupVisible(false);
 533             }
 534         }
 535     };
 536 
 537     public void applySizeFor(final JComponent c, final Size size) {
 538         if (arrowButton == null) return;
 539         final Border border = arrowButton.getBorder();
 540         if (!(border instanceof AquaButtonBorder)) return;
 541         final AquaButtonBorder aquaBorder = (AquaButtonBorder)border;
 542         arrowButton.setBorder(aquaBorder.deriveBorderForSize(size));
 543     }
 544 
 545     public Dimension getMinimumSize(final JComponent c) {
 546         if (!isMinimumSizeDirty) {
 547             return new Dimension(cachedMinimumSize);
 548         }
 549 
 550         final boolean editable = comboBox.isEditable();
 551 
 552         final Dimension size;
 553         if (!editable && arrowButton != null && arrowButton instanceof AquaComboBoxButton) {
 554             final AquaComboBoxButton button = (AquaComboBoxButton)arrowButton;
 555             final Insets buttonInsets = button.getInsets();
 556             //  Insets insets = comboBox.getInsets();
 557             final Insets insets = new Insets(0, 5, 0, 25);//comboBox.getInsets();
 558 
 559             size = getDisplaySize();
 560             size.width += insets.left + insets.right;
 561             size.width += buttonInsets.left + buttonInsets.right;
 562             size.width += buttonInsets.right + 10;
 563             size.height += insets.top + insets.bottom;
 564             size.height += buttonInsets.top + buttonInsets.bottom;
 565             // Min height = Height of arrow button plus 2 pixels fuzz above plus 2 below.  23 + 2 + 2
 566             size.height = Math.max(27, size.height);
 567         } else if (editable && arrowButton != null && editor != null) {
 568             size = super.getMinimumSize(c);
 569             final Insets margin = arrowButton.getMargin();
 570             size.height += margin.top + margin.bottom;
 571         } else {
 572             size = super.getMinimumSize(c);
 573         }
 574 
 575         final Border border = c.getBorder();
 576         if (border != null) {
 577             final Insets insets = border.getBorderInsets(c);
 578             size.height += insets.top + insets.bottom;
 579             size.width += insets.left + insets.right;
 580         }
 581 
 582         cachedMinimumSize.setSize(size.width, size.height);
 583         isMinimumSizeDirty = false;
 584 
 585         return new Dimension(cachedMinimumSize);
 586     }
 587 
 588     @SuppressWarnings("unchecked")
 589     static final RecyclableSingleton<ClientPropertyApplicator<JComboBox, AquaComboBoxUI>> APPLICATOR = new RecyclableSingleton<ClientPropertyApplicator<JComboBox, AquaComboBoxUI>>() {
 590         @Override
 591         protected ClientPropertyApplicator<JComboBox, AquaComboBoxUI> getInstance() {
 592             return new ClientPropertyApplicator<JComboBox, AquaComboBoxUI>(
 593                 new Property<AquaComboBoxUI>(AquaFocusHandler.FRAME_ACTIVE_PROPERTY) {
 594                     public void applyProperty(final AquaComboBoxUI target, final Object value) {
 595                         if (Boolean.FALSE.equals(value)) {
 596                             if (target.comboBox != null) target.comboBox.hidePopup();
 597                         }
 598                         if (target.listBox != null) target.listBox.repaint();
 599                     }
 600                 },
 601                 new Property<AquaComboBoxUI>("editable") {
 602                     public void applyProperty(final AquaComboBoxUI target, final Object value) {
 603                         if (target.comboBox == null) return;
 604                         target.comboBox.repaint();
 605                     }
 606                 },
 607                 new Property<AquaComboBoxUI>("background") {
 608                     public void applyProperty(final AquaComboBoxUI target, final Object value) {
 609                         final Color color = (Color)value;
 610                         if (target.arrowButton != null) target.arrowButton.setBackground(color);
 611                         if (target.listBox != null) target.listBox.setBackground(color);
 612                     }
 613                 },
 614                 new Property<AquaComboBoxUI>("foreground") {
 615                     public void applyProperty(final AquaComboBoxUI target, final Object value) {
 616                         final Color color = (Color)value;
 617                         if (target.arrowButton != null) target.arrowButton.setForeground(color);
 618                         if (target.listBox != null) target.listBox.setForeground(color);
 619                     }
 620                 },
 621                 new Property<AquaComboBoxUI>(POPDOWN_CLIENT_PROPERTY_KEY) {
 622                     public void applyProperty(final AquaComboBoxUI target, final Object value) {
 623                         if (!(target.arrowButton instanceof AquaComboBoxButton)) return;
 624                         ((AquaComboBoxButton)target.arrowButton).setIsPopDown(Boolean.TRUE.equals(value));
 625                     }
 626                 },
 627                 new Property<AquaComboBoxUI>(ISSQUARE_CLIENT_PROPERTY_KEY) {
 628                     public void applyProperty(final AquaComboBoxUI target, final Object value) {
 629                         if (!(target.arrowButton instanceof AquaComboBoxButton)) return;
 630                         ((AquaComboBoxButton)target.arrowButton).setIsSquare(Boolean.TRUE.equals(value));
 631                     }
 632                 }
 633             ) {
 634                 public AquaComboBoxUI convertJComponentToTarget(final JComboBox combo) {
 635                     final ComboBoxUI comboUI = combo.getUI();
 636                     if (comboUI instanceof AquaComboBoxUI) return (AquaComboBoxUI)comboUI;
 637                     return null;
 638                 }
 639             };
 640         }
 641     };
 642     static ClientPropertyApplicator<JComboBox, AquaComboBoxUI> getApplicator() {
 643         return APPLICATOR.get();
 644     }
 645 }