1 /*
   2  * Copyright (c) 1998, 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 javax.swing.plaf.metal;
  27 
  28 import java.awt.*;
  29 import java.awt.event.*;
  30 import javax.swing.*;
  31 import javax.swing.plaf.*;
  32 import javax.swing.border.*;
  33 import javax.swing.plaf.basic.*;
  34 import java.io.Serializable;
  35 import java.beans.*;
  36 
  37 
  38 /**
  39  * Metal UI for JComboBox
  40  * <p>
  41  * <strong>Warning:</strong>
  42  * Serialized objects of this class will not be compatible with
  43  * future Swing releases. The current serialization support is
  44  * appropriate for short term storage or RMI between applications running
  45  * the same version of Swing.  As of 1.4, support for long term storage
  46  * of all JavaBeans
  47  * has been added to the <code>java.beans</code> package.
  48  * Please see {@link java.beans.XMLEncoder}.
  49  *
  50  * @see MetalComboBoxEditor
  51  * @see MetalComboBoxButton
  52  * @author Tom Santos
  53  */
  54 @SuppressWarnings("serial") // Same-version serialization only
  55 public class MetalComboBoxUI extends BasicComboBoxUI {
  56 
  57     /**
  58      * Constructs an instance of {@code MetalComboBoxUI}.
  59      *
  60      * @param c a component
  61      * @return an instance of {@code MetalComboBoxUI}
  62      */
  63     public static ComponentUI createUI(JComponent c) {
  64         return new MetalComboBoxUI();
  65     }
  66 
  67     public void paint(Graphics g, JComponent c) {
  68         if (MetalLookAndFeel.usingOcean()) {
  69             super.paint(g, c);
  70         }
  71     }
  72 
  73     /**
  74      * If necessary paints the currently selected item.
  75      *
  76      * @param g Graphics to paint to
  77      * @param bounds Region to paint current value to
  78      * @param hasFocus whether or not the JComboBox has focus
  79      * @throws NullPointerException if any of the arguments are null.
  80      * @since 1.5
  81      */
  82     public void paintCurrentValue(Graphics g, Rectangle bounds,
  83                                   boolean hasFocus) {
  84         // This is really only called if we're using ocean.
  85         if (MetalLookAndFeel.usingOcean()) {
  86             bounds.x += 2;
  87             bounds.width -= 3;
  88             if (arrowButton != null) {
  89                 Insets buttonInsets = arrowButton.getInsets();
  90                 bounds.y += buttonInsets.top;
  91                 bounds.height -= (buttonInsets.top + buttonInsets.bottom);
  92             }
  93             else {
  94                 bounds.y += 2;
  95                 bounds.height -= 4;
  96             }
  97             super.paintCurrentValue(g, bounds, hasFocus);
  98         }
  99         else if (g == null || bounds == null) {
 100             throw new NullPointerException(
 101                 "Must supply a non-null Graphics and Rectangle");
 102         }
 103     }
 104 
 105     /**
 106      * If necessary paints the background of the currently selected item.
 107      *
 108      * @param g Graphics to paint to
 109      * @param bounds Region to paint background to
 110      * @param hasFocus whether or not the JComboBox has focus
 111      * @throws NullPointerException if any of the arguments are null.
 112      * @since 1.5
 113      */
 114     public void paintCurrentValueBackground(Graphics g, Rectangle bounds,
 115                                             boolean hasFocus) {
 116         // This is really only called if we're using ocean.
 117         if (MetalLookAndFeel.usingOcean()) {
 118             g.setColor(MetalLookAndFeel.getControlDarkShadow());
 119             g.drawRect(bounds.x, bounds.y, bounds.width, bounds.height - 1);
 120             g.setColor(MetalLookAndFeel.getControlShadow());
 121             g.drawRect(bounds.x + 1, bounds.y + 1, bounds.width - 2,
 122                        bounds.height - 3);
 123             if (hasFocus && !isPopupVisible(comboBox) &&
 124                     arrowButton != null) {
 125                 g.setColor(listBox.getSelectionBackground());
 126                 Insets buttonInsets = arrowButton.getInsets();
 127                 if (buttonInsets.top > 2) {
 128                     g.fillRect(bounds.x + 2, bounds.y + 2, bounds.width - 3,
 129                                buttonInsets.top - 2);
 130                 }
 131                 if (buttonInsets.bottom > 2) {
 132                     g.fillRect(bounds.x + 2, bounds.y + bounds.height -
 133                                buttonInsets.bottom, bounds.width - 3,
 134                                buttonInsets.bottom - 2);
 135                 }
 136             }
 137         }
 138         else if (g == null || bounds == null) {
 139             throw new NullPointerException(
 140                 "Must supply a non-null Graphics and Rectangle");
 141         }
 142     }
 143 
 144     /**
 145      * Returns the baseline.
 146      *
 147      * @throws NullPointerException {@inheritDoc}
 148      * @throws IllegalArgumentException {@inheritDoc}
 149      * @see javax.swing.JComponent#getBaseline(int, int)
 150      * @since 1.6
 151      */
 152     public int getBaseline(JComponent c, int width, int height) {
 153         int baseline;
 154         if (MetalLookAndFeel.usingOcean() && height >= 4) {
 155             height -= 4;
 156             baseline = super.getBaseline(c, width, height);
 157             if (baseline >= 0) {
 158                 baseline += 2;
 159             }
 160         }
 161         else {
 162             baseline = super.getBaseline(c, width, height);
 163         }
 164         return baseline;
 165     }
 166 
 167     protected ComboBoxEditor createEditor() {
 168         return new MetalComboBoxEditor.UIResource();
 169     }
 170 
 171     protected ComboPopup createPopup() {
 172         return super.createPopup();
 173     }
 174 
 175     protected JButton createArrowButton() {
 176         boolean iconOnly = (comboBox.isEditable() ||
 177                             MetalLookAndFeel.usingOcean());
 178         JButton button = new MetalComboBoxButton( comboBox,
 179                                                   new MetalComboBoxIcon(),
 180                                                   iconOnly,
 181                                                   currentValuePane,
 182                                                   listBox );
 183         button.setMargin( new Insets( 0, 1, 1, 3 ) );
 184         if (MetalLookAndFeel.usingOcean()) {
 185             // Disabled rollover effect.
 186             button.putClientProperty(MetalBorders.NO_BUTTON_ROLLOVER,
 187                                      Boolean.TRUE);
 188         }
 189         updateButtonForOcean(button);
 190         return button;
 191     }
 192 
 193     /**
 194      * Resets the necessary state on the ComboBoxButton for ocean.
 195      */
 196     private void updateButtonForOcean(JButton button) {
 197         if (MetalLookAndFeel.usingOcean()) {
 198             // Ocean renders the focus in a different way, this
 199             // would be redundant.
 200             button.setFocusPainted(comboBox.isEditable());
 201         }
 202     }
 203 
 204     public PropertyChangeListener createPropertyChangeListener() {
 205         return new MetalPropertyChangeListener();
 206     }
 207 
 208     /**
 209      * This class should be treated as a &quot;protected&quot; inner class.
 210      * Instantiate it only within subclasses of {@code MetalComboBoxUI}.
 211      */
 212     public class MetalPropertyChangeListener extends BasicComboBoxUI.PropertyChangeHandler {
 213         public void propertyChange(PropertyChangeEvent e) {
 214             super.propertyChange( e );
 215             String propertyName = e.getPropertyName();
 216 
 217             if ( propertyName == "editable" ) {
 218                 if(arrowButton instanceof MetalComboBoxButton) {
 219                             MetalComboBoxButton button = (MetalComboBoxButton)arrowButton;
 220                             button.setIconOnly( comboBox.isEditable() ||
 221                                     MetalLookAndFeel.usingOcean() );
 222                 }
 223                         comboBox.repaint();
 224                 updateButtonForOcean(arrowButton);
 225             } else if ( propertyName == "background" ) {
 226                 Color color = (Color)e.getNewValue();
 227                 arrowButton.setBackground(color);
 228                 listBox.setBackground(color);
 229 
 230             } else if ( propertyName == "foreground" ) {
 231                 Color color = (Color)e.getNewValue();
 232                 arrowButton.setForeground(color);
 233                 listBox.setForeground(color);
 234             }
 235         }
 236     }
 237 
 238     /**
 239      * As of Java 2 platform v1.4 this method is no longer used. Do not call or
 240      * override. All the functionality of this method is in the
 241      * MetalPropertyChangeListener.
 242      *
 243      * @param e an instance of {@code PropertyChangeEvent}
 244      * @deprecated As of Java 2 platform v1.4.
 245      */
 246     @Deprecated
 247     protected void editablePropertyChanged( PropertyChangeEvent e ) { }
 248 
 249     protected LayoutManager createLayoutManager() {
 250         return new MetalComboBoxLayoutManager();
 251     }
 252 
 253     /**
 254      * This class should be treated as a &quot;protected&quot; inner class.
 255      * Instantiate it only within subclasses of {@code MetalComboBoxUI}.
 256      */
 257     public class MetalComboBoxLayoutManager extends BasicComboBoxUI.ComboBoxLayoutManager {
 258         public void layoutContainer( Container parent ) {
 259             layoutComboBox( parent, this );
 260         }
 261 
 262         /**
 263          * Lays out the parent container.
 264          *
 265          * @param parent a container
 266          */
 267         public void superLayout( Container parent ) {
 268             super.layoutContainer( parent );
 269         }
 270     }
 271 
 272     /**
 273      * Lays out the {@code JComboBox} in the {@code parent} container.
 274      *
 275      * @param parent a container
 276      * @param manager an instance of {@code MetalComboBoxLayoutManager}
 277      */
 278     // This is here because of a bug in the compiler.
 279     // When a protected-inner-class-savvy compiler comes out we
 280     // should move this into MetalComboBoxLayoutManager.
 281     public void layoutComboBox( Container parent, MetalComboBoxLayoutManager manager ) {
 282         if (comboBox.isEditable() && !MetalLookAndFeel.usingOcean()) {
 283             manager.superLayout( parent );
 284             return;
 285         }
 286 
 287         if (arrowButton != null) {
 288             if (MetalLookAndFeel.usingOcean() ) {
 289                 Insets insets = comboBox.getInsets();
 290                 int buttonWidth = arrowButton.getMinimumSize().width;
 291                 arrowButton.setBounds(MetalUtils.isLeftToRight(comboBox)
 292                                 ? (comboBox.getWidth() - insets.right - buttonWidth)
 293                                 : insets.left,
 294                             insets.top, buttonWidth,
 295                             comboBox.getHeight() - insets.top - insets.bottom);
 296             }
 297             else {
 298                 Insets insets = comboBox.getInsets();
 299                 int width = comboBox.getWidth();
 300                 int height = comboBox.getHeight();
 301                 arrowButton.setBounds( insets.left, insets.top,
 302                                        width - (insets.left + insets.right),
 303                                        height - (insets.top + insets.bottom) );
 304             }
 305         }
 306 
 307         if (editor != null && MetalLookAndFeel.usingOcean()) {
 308             Rectangle cvb = rectangleForCurrentValue();
 309             editor.setBounds(cvb);
 310         }
 311     }
 312 
 313     /**
 314      * As of Java 2 platform v1.4 this method is no
 315      * longer used.
 316      *
 317      * @deprecated As of Java 2 platform v1.4.
 318      */
 319     @Deprecated
 320     protected void removeListeners() {
 321         if ( propertyChangeListener != null ) {
 322             comboBox.removePropertyChangeListener( propertyChangeListener );
 323         }
 324     }
 325 
 326     // These two methods were overloaded and made public. This was probably a
 327     // mistake in the implementation. The functionality that they used to
 328     // provide is no longer necessary and should be removed. However,
 329     // removing them will create an uncompatible API change.
 330 
 331     public void configureEditor() {
 332         super.configureEditor();
 333     }
 334 
 335     public void unconfigureEditor() {
 336         super.unconfigureEditor();
 337     }
 338 
 339     public Dimension getMinimumSize( JComponent c ) {
 340         if ( !isMinimumSizeDirty ) {
 341             return new Dimension( cachedMinimumSize );
 342         }
 343 
 344         Dimension size = null;
 345 
 346         if ( !comboBox.isEditable() &&
 347              arrowButton != null) {
 348             Insets buttonInsets = arrowButton.getInsets();
 349             Insets insets = comboBox.getInsets();
 350 
 351             size = getDisplaySize();
 352             size.width += insets.left + insets.right;
 353             size.width += buttonInsets.right;
 354             size.width += arrowButton.getMinimumSize().width;
 355             size.height += insets.top + insets.bottom;
 356             size.height += buttonInsets.top + buttonInsets.bottom;
 357         }
 358         else if ( comboBox.isEditable() &&
 359                   arrowButton != null &&
 360                   editor != null ) {
 361             size = super.getMinimumSize( c );
 362             Insets margin = arrowButton.getMargin();
 363             size.height += margin.top + margin.bottom;
 364             size.width += margin.left + margin.right;
 365         }
 366         else {
 367             size = super.getMinimumSize( c );
 368         }
 369 
 370         cachedMinimumSize.setSize( size.width, size.height );
 371         isMinimumSizeDirty = false;
 372 
 373         return new Dimension( cachedMinimumSize );
 374     }
 375 
 376     /**
 377      * This class should be treated as a &quot;protected&quot; inner class.
 378      * Instantiate it only within subclasses of {@code MetalComboBoxUI}.
 379      *
 380      * This class is now obsolete and doesn't do anything and
 381      * is only included for backwards API compatibility. Do not call or
 382      * override.
 383      *
 384      * @deprecated As of Java 2 platform v1.4.
 385      */
 386     @Deprecated
 387     public class MetalComboPopup extends BasicComboPopup {
 388 
 389         /**
 390          * Constructs a new instance of {@code MetalComboPopup}.
 391          *
 392          * @param cBox an instance of {@code JComboBox}
 393          */
 394         public MetalComboPopup( JComboBox<Object> cBox) {
 395             super( cBox );
 396         }
 397 
 398         // This method was overloaded and made public. This was probably
 399         // mistake in the implementation. The functionality that they used to
 400         // provide is no longer necessary and should be removed. However,
 401         // removing them will create an uncompatible API change.
 402 
 403         public void delegateFocus(MouseEvent e) {
 404             super.delegateFocus(e);
 405         }
 406     }
 407 }