1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.java.swing.plaf.windows;
  27 
  28 import java.beans.PropertyChangeListener;
  29 import java.beans.PropertyChangeEvent;
  30 import javax.swing.plaf.basic.*;
  31 import javax.swing.plaf.*;
  32 import javax.swing.border.*;
  33 import javax.swing.*;
  34 import java.awt.event.*;
  35 import java.awt.*;
  36 
  37 import static com.sun.java.swing.plaf.windows.TMSchema.Part;
  38 import static com.sun.java.swing.plaf.windows.TMSchema.State;
  39 import static com.sun.java.swing.plaf.windows.XPStyle.Skin;
  40 
  41 import sun.swing.DefaultLookup;
  42 import sun.swing.StringUIClientPropertyKey;
  43 
  44 
  45 /**
  46  * Windows combo box.
  47  * <p>
  48  * <strong>Warning:</strong>
  49  * Serialized objects of this class will not be compatible with
  50  * future Swing releases.  The current serialization support is appropriate
  51  * for short term storage or RMI between applications running the same
  52  * version of Swing.  A future release of Swing will provide support for
  53  * long term persistence.
  54  *
  55  * @author Tom Santos
  56  * @author Igor Kushnirskiy
  57  */
  58 
  59 public class WindowsComboBoxUI extends BasicComboBoxUI {
  60 
  61     private static final MouseListener rolloverListener =
  62         new MouseAdapter() {
  63             private void handleRollover(MouseEvent e, boolean isRollover) {
  64                 JComboBox comboBox = getComboBox(e);
  65                 WindowsComboBoxUI comboBoxUI = getWindowsComboBoxUI(e);
  66                 if (comboBox == null || comboBoxUI == null) {
  67                     return;
  68                 }
  69                 if (! comboBox.isEditable()) {
  70                     //mouse over editable ComboBox does not switch rollover
  71                     //for the arrow button
  72                     ButtonModel m = null;
  73                     if (comboBoxUI.arrowButton != null) {
  74                         m = comboBoxUI.arrowButton.getModel();
  75                     }
  76                     if (m != null ) {
  77                         m.setRollover(isRollover);
  78                     }
  79                 }
  80                 comboBoxUI.isRollover = isRollover;
  81                 comboBox.repaint();
  82             }
  83 
  84             public void mouseEntered(MouseEvent e) {
  85                 handleRollover(e, true);
  86             }
  87 
  88             public void mouseExited(MouseEvent e) {
  89                 handleRollover(e, false);
  90             }
  91 
  92             private JComboBox getComboBox(MouseEvent event) {
  93                 Object source = event.getSource();
  94                 JComboBox rv = null;
  95                 if (source instanceof JComboBox) {
  96                     rv = (JComboBox) source;
  97                 } else if (source instanceof XPComboBoxButton) {
  98                     rv = ((XPComboBoxButton) source)
  99                         .getWindowsComboBoxUI().comboBox;
 100                 }
 101                 return rv;
 102             }
 103 
 104             private WindowsComboBoxUI getWindowsComboBoxUI(MouseEvent event) {
 105                 JComboBox comboBox = getComboBox(event);
 106                 WindowsComboBoxUI rv = null;
 107                 if (comboBox != null
 108                     && comboBox.getUI() instanceof WindowsComboBoxUI) {
 109                     rv = (WindowsComboBoxUI) comboBox.getUI();
 110                 }
 111                 return rv;
 112             }
 113 
 114         };
 115     private boolean isRollover = false;
 116 
 117     private static final PropertyChangeListener componentOrientationListener =
 118         new PropertyChangeListener() {
 119             public void propertyChange(PropertyChangeEvent e) {
 120                 String propertyName = e.getPropertyName();
 121                 Object source = null;
 122                 if ("componentOrientation" == propertyName
 123                     && (source = e.getSource()) instanceof JComboBox
 124                     && ((JComboBox) source).getUI() instanceof
 125                       WindowsComboBoxUI) {
 126                     JComboBox comboBox = (JComboBox) source;
 127                     WindowsComboBoxUI comboBoxUI = (WindowsComboBoxUI) comboBox.getUI();
 128                     if (comboBoxUI.arrowButton instanceof XPComboBoxButton) {
 129                         ((XPComboBoxButton) comboBoxUI.arrowButton).setPart(
 130                                     (comboBox.getComponentOrientation() ==
 131                                        ComponentOrientation.RIGHT_TO_LEFT)
 132                                     ? Part.CP_DROPDOWNBUTTONLEFT
 133                                     : Part.CP_DROPDOWNBUTTONRIGHT);
 134                             }
 135                         }
 136                     }
 137                 };
 138 
 139     public static ComponentUI createUI(JComponent c) {
 140         return new WindowsComboBoxUI();
 141     }
 142 
 143     public void installUI( JComponent c ) {
 144         super.installUI( c );
 145         isRollover = false;
 146         comboBox.setRequestFocusEnabled( true );
 147         if (XPStyle.getXP() != null && arrowButton != null) {
 148             //we can not do it in installListeners because arrowButton
 149             //is initialized after installListeners is invoked
 150             comboBox.addMouseListener(rolloverListener);
 151             arrowButton.addMouseListener(rolloverListener);
 152         }
 153     }
 154 
 155     public void uninstallUI(JComponent c ) {
 156         comboBox.removeMouseListener(rolloverListener);
 157         if(arrowButton != null) {
 158             arrowButton.removeMouseListener(rolloverListener);
 159         }
 160         super.uninstallUI( c );
 161     }
 162 
 163     /**
 164      * {@inheritDoc}
 165      * @since 1.6
 166      */
 167     @Override
 168     protected void installListeners() {
 169         super.installListeners();
 170         XPStyle xp = XPStyle.getXP();
 171         //button glyph for LTR and RTL combobox might differ
 172         if (xp != null
 173               && xp.isSkinDefined(comboBox, Part.CP_DROPDOWNBUTTONRIGHT)) {
 174             comboBox.addPropertyChangeListener("componentOrientation",
 175                                                componentOrientationListener);
 176         }
 177     }
 178 
 179     /**
 180      * {@inheritDoc}
 181      * @since 1.6
 182      */
 183     @Override
 184     protected void uninstallListeners() {
 185         super.uninstallListeners();
 186         comboBox.removePropertyChangeListener("componentOrientation",
 187                                               componentOrientationListener);
 188     }
 189 
 190     /**
 191      * {@inheritDoc}
 192      * @since 1.6
 193      */
 194     protected void configureEditor() {
 195         super.configureEditor();
 196         if (XPStyle.getXP() != null) {
 197             editor.addMouseListener(rolloverListener);
 198         }
 199     }
 200 
 201     /**
 202      * {@inheritDoc}
 203      * @since 1.6
 204      */
 205     protected void unconfigureEditor() {
 206         super.unconfigureEditor();
 207         editor.removeMouseListener(rolloverListener);
 208     }
 209 
 210     /**
 211      * {@inheritDoc}
 212      * @since 1.6
 213      */
 214     public void paint(Graphics g, JComponent c) {
 215         if (XPStyle.getXP() != null) {
 216             paintXPComboBoxBackground(g, c);
 217         }
 218         super.paint(g, c);
 219     }
 220 
 221     State getXPComboBoxState(JComponent c) {
 222         State state = State.NORMAL;
 223         if (!c.isEnabled()) {
 224             state = State.DISABLED;
 225         } else if (isPopupVisible(comboBox)) {
 226             state = State.PRESSED;
 227         } else if (isRollover) {
 228             state = State.HOT;
 229         }
 230         return state;
 231     }
 232 
 233     private void paintXPComboBoxBackground(Graphics g, JComponent c) {
 234         XPStyle xp = XPStyle.getXP();
 235         if (xp == null) {
 236             return;
 237         }
 238         State state = getXPComboBoxState(c);
 239         Skin skin = null;
 240         if (! comboBox.isEditable()
 241               && xp.isSkinDefined(c, Part.CP_READONLY)) {
 242             skin = xp.getSkin(c, Part.CP_READONLY);
 243         }
 244         if (skin == null) {
 245             skin = xp.getSkin(c, Part.CP_COMBOBOX);
 246         }
 247         skin.paintSkin(g, 0, 0, c.getWidth(), c.getHeight(), state);
 248     }
 249 
 250     /**
 251      * If necessary paints the currently selected item.
 252      *
 253      * @param g Graphics to paint to
 254      * @param bounds Region to paint current value to
 255      * @param hasFocus whether or not the JComboBox has focus
 256      * @throws NullPointerException if any of the arguments are null.
 257      * @since 1.5
 258      */
 259     public void paintCurrentValue(Graphics g, Rectangle bounds,
 260                                   boolean hasFocus) {
 261         XPStyle xp = XPStyle.getXP();
 262         if ( xp != null) {
 263             bounds.x += 2;
 264             bounds.y += 2;
 265             bounds.width -= 4;
 266             bounds.height -= 4;
 267         } else {
 268             bounds.x += 1;
 269             bounds.y += 1;
 270             bounds.width -= 2;
 271             bounds.height -= 2;
 272         }
 273         if (! comboBox.isEditable()
 274             && xp != null
 275             && xp.isSkinDefined(comboBox, Part.CP_READONLY)) {
 276             // On vista for READNLY ComboBox
 277             // color for currentValue is the same as for any other item
 278 
 279             // mostly copied from javax.swing.plaf.basic.BasicComboBoxUI.paintCurrentValue
 280             ListCellRenderer renderer = comboBox.getRenderer();
 281             Component c;
 282             if ( hasFocus && !isPopupVisible(comboBox) ) {
 283                 c = renderer.getListCellRendererComponent(
 284                         listBox,
 285                         comboBox.getSelectedItem(),
 286                         -1,
 287                         true,
 288                         false );
 289             } else {
 290                 c = renderer.getListCellRendererComponent(
 291                         listBox,
 292                         comboBox.getSelectedItem(),
 293                         -1,
 294                         false,
 295                         false );
 296             }
 297             c.setFont(comboBox.getFont());
 298             if ( comboBox.isEnabled() ) {
 299                 c.setForeground(comboBox.getForeground());
 300                 c.setBackground(comboBox.getBackground());
 301             } else {
 302                 c.setForeground(DefaultLookup.getColor(
 303                          comboBox, this, "ComboBox.disabledForeground", null));
 304                 c.setBackground(DefaultLookup.getColor(
 305                          comboBox, this, "ComboBox.disabledBackground", null));
 306             }
 307             boolean shouldValidate = false;
 308             if (c instanceof JPanel)  {
 309                 shouldValidate = true;
 310             }
 311             currentValuePane.paintComponent(g, c, comboBox, bounds.x, bounds.y,
 312                                             bounds.width, bounds.height, shouldValidate);
 313 
 314         } else {
 315             super.paintCurrentValue(g, bounds, hasFocus);
 316         }
 317     }
 318 
 319     /**
 320      * {@inheritDoc}
 321      * @since 1.6
 322      */
 323     public void paintCurrentValueBackground(Graphics g, Rectangle bounds,
 324                                             boolean hasFocus) {
 325         if (XPStyle.getXP() == null) {
 326             super.paintCurrentValueBackground(g, bounds, hasFocus);
 327         }
 328     }
 329 
 330     public Dimension getMinimumSize( JComponent c ) {
 331         Dimension d = super.getMinimumSize(c);
 332         if (XPStyle.getXP() != null) {
 333             d.width += 5;
 334         } else {
 335             d.width += 4;
 336         }
 337         d.height += 2;
 338         return d;
 339     }
 340 
 341     /**
 342      * Creates a layout manager for managing the components which make up the
 343      * combo box.
 344      *
 345      * @return an instance of a layout manager
 346      */
 347     protected LayoutManager createLayoutManager() {
 348         return new BasicComboBoxUI.ComboBoxLayoutManager() {
 349             public void layoutContainer(Container parent) {
 350                 super.layoutContainer(parent);
 351 
 352                 if (XPStyle.getXP() != null && arrowButton != null) {
 353                     Dimension d = parent.getSize();
 354                     Insets insets = getInsets();
 355                     int buttonWidth = arrowButton.getPreferredSize().width;
 356                     arrowButton.setBounds(WindowsGraphicsUtils.isLeftToRight((JComboBox)parent)
 357                                           ? (d.width - insets.right - buttonWidth)
 358                                           : insets.left,
 359                                           insets.top,
 360                                           buttonWidth, d.height - insets.top - insets.bottom);
 361                 }
 362             }
 363         };
 364     }
 365 
 366     protected void installKeyboardActions() {
 367         super.installKeyboardActions();
 368     }
 369 
 370     protected ComboPopup createPopup() {
 371         return super.createPopup();
 372     }
 373 
 374     /**
 375      * Creates the default editor that will be used in editable combo boxes.
 376      * A default editor will be used only if an editor has not been
 377      * explicitly set with <code>setEditor</code>.
 378      *
 379      * @return a <code>ComboBoxEditor</code> used for the combo box
 380      * @see javax.swing.JComboBox#setEditor
 381      */
 382     protected ComboBoxEditor createEditor() {
 383         return new WindowsComboBoxEditor();
 384     }
 385 
 386     /**
 387      * {@inheritDoc}
 388      * @since 1.6
 389      */
 390     @Override
 391     protected ListCellRenderer createRenderer() {
 392         XPStyle xp = XPStyle.getXP();
 393         if (xp != null && xp.isSkinDefined(comboBox, Part.CP_READONLY)) {
 394             return new WindowsComboBoxRenderer();
 395         } else {
 396             return super.createRenderer();
 397         }
 398     }
 399 
 400     /**
 401      * Creates an button which will be used as the control to show or hide
 402      * the popup portion of the combo box.
 403      *
 404      * @return a button which represents the popup control
 405      */
 406     protected JButton createArrowButton() {
 407         XPStyle xp = XPStyle.getXP();
 408         if (xp != null) {
 409             return new XPComboBoxButton(xp);
 410         } else {
 411             return super.createArrowButton();
 412         }
 413     }
 414 
 415     @SuppressWarnings("serial") // Superclass is not serializable across versions
 416     private class XPComboBoxButton extends XPStyle.GlyphButton {
 417         public XPComboBoxButton(XPStyle xp) {
 418             super(null,
 419                   (! xp.isSkinDefined(comboBox, Part.CP_DROPDOWNBUTTONRIGHT))
 420                    ? Part.CP_DROPDOWNBUTTON
 421                    : (comboBox.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT)
 422                      ? Part.CP_DROPDOWNBUTTONLEFT
 423                      : Part.CP_DROPDOWNBUTTONRIGHT
 424                   );
 425             setRequestFocusEnabled(false);
 426         }
 427 
 428         @Override
 429         protected State getState() {
 430             State rv;
 431             rv = super.getState();
 432             XPStyle xp = XPStyle.getXP();
 433             if (rv != State.DISABLED
 434                 && comboBox != null && ! comboBox.isEditable()
 435                 && xp != null && xp.isSkinDefined(comboBox,
 436                                                   Part.CP_DROPDOWNBUTTONRIGHT)) {
 437                 /*
 438                  * for non editable ComboBoxes Vista seems to have the
 439                  * same glyph for all non DISABLED states
 440                  */
 441                 rv = State.NORMAL;
 442             }
 443             return rv;
 444         }
 445 
 446         public Dimension getPreferredSize() {
 447             return new Dimension(17, 21);
 448         }
 449 
 450         void setPart(Part part) {
 451             setPart(comboBox, part);
 452         }
 453 
 454         WindowsComboBoxUI getWindowsComboBoxUI() {
 455             return WindowsComboBoxUI.this;
 456         }
 457     }
 458 
 459 
 460     /**
 461      * Subclassed to add Windows specific Key Bindings.
 462      * This class is now obsolete and doesn't do anything.
 463      * Only included for backwards API compatibility.
 464      * Do not call or override.
 465      *
 466      * @deprecated As of Java 2 platform v1.4.
 467      */
 468     @Deprecated
 469     @SuppressWarnings("serial") // Superclass is not serializable across versions
 470     protected class WindowsComboPopup extends BasicComboPopup {
 471 
 472         public WindowsComboPopup( JComboBox cBox ) {
 473             super( cBox );
 474         }
 475 
 476         protected KeyListener createKeyListener() {
 477             return new InvocationKeyHandler();
 478         }
 479 
 480         protected class InvocationKeyHandler extends BasicComboPopup.InvocationKeyHandler {
 481             protected InvocationKeyHandler() {
 482                 WindowsComboPopup.this.super();
 483             }
 484         }
 485     }
 486 
 487 
 488     /**
 489      * Subclassed to highlight selected item in an editable combo box.
 490      */
 491     public static class WindowsComboBoxEditor
 492         extends BasicComboBoxEditor.UIResource {
 493 
 494         /**
 495          * {@inheritDoc}
 496          * @since 1.6
 497          */
 498         protected JTextField createEditorComponent() {
 499             JTextField editor = super.createEditorComponent();
 500             Border border = (Border)UIManager.get("ComboBox.editorBorder");
 501             if (border != null) {
 502                 editor.setBorder(border);
 503             }
 504             editor.setOpaque(false);
 505             return editor;
 506         }
 507 
 508         public void setItem(Object item) {
 509             super.setItem(item);
 510             Object focus = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
 511             if ((focus == editor) || (focus == editor.getParent())) {
 512                 editor.selectAll();
 513             }
 514         }
 515     }
 516 
 517     /**
 518      * Subclassed to set opacity {@code false} on the renderer
 519      * and to show border for focused cells.
 520      */
 521     @SuppressWarnings("serial") // Superclass is not serializable across versions
 522     private static class WindowsComboBoxRenderer
 523           extends BasicComboBoxRenderer.UIResource {
 524         private static final Object BORDER_KEY
 525             = new StringUIClientPropertyKey("BORDER_KEY");
 526         private static final Border NULL_BORDER = new EmptyBorder(0, 0, 0, 0);
 527         /**
 528          * {@inheritDoc}
 529          */
 530         @Override
 531         public Component getListCellRendererComponent(
 532                                                  JList list,
 533                                                  Object value,
 534                                                  int index,
 535                                                  boolean isSelected,
 536                                                  boolean cellHasFocus) {
 537             Component rv =
 538                 super.getListCellRendererComponent(list, value, index,
 539                                                    isSelected, cellHasFocus);
 540             if (rv instanceof JComponent) {
 541                 JComponent component = (JComponent) rv;
 542                 if (index == -1 && isSelected) {
 543                     Border border = component.getBorder();
 544                     Border dashedBorder =
 545                         new WindowsBorders.DashedBorder(list.getForeground());
 546                     component.setBorder(dashedBorder);
 547                     //store current border in client property if needed
 548                     if (component.getClientProperty(BORDER_KEY) == null) {
 549                         component.putClientProperty(BORDER_KEY,
 550                                        (border == null) ? NULL_BORDER : border);
 551                     }
 552                 } else {
 553                     if (component.getBorder() instanceof
 554                           WindowsBorders.DashedBorder) {
 555                         Object storedBorder = component.getClientProperty(BORDER_KEY);
 556                         if (storedBorder instanceof Border) {
 557                             component.setBorder(
 558                                 (storedBorder == NULL_BORDER) ? null
 559                                     : (Border) storedBorder);
 560                         }
 561                         component.putClientProperty(BORDER_KEY, null);
 562                     }
 563                 }
 564                 if (index == -1) {
 565                     component.setOpaque(false);
 566                     component.setForeground(list.getForeground());
 567                 } else {
 568                     component.setOpaque(true);
 569                 }
 570             }
 571             return rv;
 572         }
 573 
 574     }
 575 }