1 /*
   2  * Copyright (c) 2011, 2012, 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.image.BufferedImage;
  30 
  31 import javax.swing.*;
  32 import javax.swing.border.Border;
  33 import javax.swing.plaf.UIResource;
  34 import javax.swing.plaf.basic.BasicHTML;
  35 import javax.swing.text.View;
  36 
  37 import apple.laf.*;
  38 import apple.laf.JRSUIConstants.*;
  39 import apple.laf.JRSUIState.ValueState;
  40 
  41 import com.apple.laf.AquaUtilControlSize.*;
  42 import com.apple.laf.AquaUtils.RecyclableSingleton;
  43 
  44 public abstract class AquaButtonLabeledUI extends AquaButtonToggleUI implements Sizeable {
  45     protected static RecyclableSizingIcon regularIcon = new RecyclableSizingIcon(18);
  46     protected static RecyclableSizingIcon smallIcon = new RecyclableSizingIcon(16);
  47     protected static RecyclableSizingIcon miniIcon = new RecyclableSizingIcon(14);
  48 
  49     protected static class RecyclableSizingIcon extends RecyclableSingleton<Icon> {
  50         final int iconSize;
  51         public RecyclableSizingIcon(final int iconSize) { this.iconSize = iconSize; }
  52 
  53         protected Icon getInstance() {
  54             return new ImageIcon(new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB_PRE));
  55         }
  56     }
  57 
  58     protected AquaButtonBorder widgetBorder;
  59 
  60     public AquaButtonLabeledUI() {
  61         widgetBorder = getPainter();
  62     }
  63 
  64     public void applySizeFor(final JComponent c, final Size newSize) {
  65         super.applySizeFor(c, newSize);
  66         widgetBorder = (AquaButtonBorder)widgetBorder.deriveBorderForSize(newSize);
  67     }
  68 
  69     public Icon getDefaultIcon(final JComponent c) {
  70         final Size componentSize = AquaUtilControlSize.getUserSizeFrom(c);
  71         if (componentSize == Size.REGULAR) return regularIcon.get();
  72         if (componentSize == Size.SMALL) return smallIcon.get();
  73         if (componentSize == Size.MINI) return miniIcon.get();
  74         return regularIcon.get();
  75     }
  76 
  77     protected void setThemeBorder(final AbstractButton b) {
  78         super.setThemeBorder(b);
  79 
  80         Border border = b.getBorder();
  81         if (border == null || border instanceof UIResource) {
  82             // Set the correct border
  83             b.setBorder(AquaButtonBorder.getBevelButtonBorder());
  84         }
  85     }
  86 
  87     protected abstract AquaButtonBorder getPainter();
  88 
  89     public synchronized void paint(final Graphics g, final JComponent c) {
  90         final AbstractButton b = (AbstractButton)c;
  91         final ButtonModel model = b.getModel();
  92 
  93         final Font f = c.getFont();
  94         g.setFont(f);
  95         final FontMetrics fm = g.getFontMetrics();
  96 
  97         Dimension size = b.getSize();
  98 
  99         final Insets i = c.getInsets();
 100 
 101         Rectangle viewRect = new Rectangle(b.getWidth(), b.getHeight());
 102         Rectangle iconRect = new Rectangle();
 103         Rectangle textRect = new Rectangle();
 104 
 105         Icon altIcon = b.getIcon();
 106 
 107         final boolean isCellEditor = c.getParent() instanceof CellRendererPane;
 108 
 109         // This was erroneously removed to fix [3155996] but really we wanted the controls to just be
 110         // opaque. So we put this back in to fix [3179839] (radio buttons not being translucent)
 111         if (b.isOpaque() || isCellEditor) {
 112             g.setColor(b.getBackground());
 113             g.fillRect(0, 0, size.width, size.height);
 114         }
 115 
 116         // only do this if borders are on!
 117         if (((AbstractButton)c).isBorderPainted() && !isCellEditor) {
 118             final Border border = c.getBorder();
 119             if (border instanceof AquaButtonBorder) {
 120                 ((AquaButtonBorder)border).paintButton(c, g, viewRect.x, viewRect.y, viewRect.width, viewRect.height);
 121             }
 122         }
 123 
 124         viewRect.x = i.left;
 125         viewRect.y = i.top;
 126         viewRect.width = b.getWidth() - (i.right + viewRect.x);
 127         viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
 128 
 129         // normal size ??
 130         // at some point we substitute the small icon instead of the normal icon
 131         // we should base this on height. Use normal unless we are under a certain size
 132         // see our button code!
 133 
 134         final String text = SwingUtilities.layoutCompoundLabel(c, fm, b.getText(), altIcon != null ? altIcon : getDefaultIcon(b), b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, b.getText() == null ? 0 : b.getIconTextGap());
 135 
 136         // fill background
 137 
 138         // draw the native radio button stuff here.
 139         if (altIcon == null) {
 140             widgetBorder.paintButton(c, g, iconRect.x, iconRect.y, iconRect.width, iconRect.height);
 141         } else {
 142             // Paint the button
 143             if (!model.isEnabled()) {
 144                 if (model.isSelected()) {
 145                     altIcon = b.getDisabledSelectedIcon();
 146                 } else {
 147                     altIcon = b.getDisabledIcon();
 148                 }
 149             } else if (model.isPressed() && model.isArmed()) {
 150                 altIcon = b.getPressedIcon();
 151                 if (altIcon == null) {
 152                     // Use selected icon
 153                     altIcon = b.getSelectedIcon();
 154                 }
 155             } else if (model.isSelected()) {
 156                 if (b.isRolloverEnabled() && model.isRollover()) {
 157                     altIcon = b.getRolloverSelectedIcon();
 158                     if (altIcon == null) {
 159                         altIcon = b.getSelectedIcon();
 160                     }
 161                 } else {
 162                     altIcon = b.getSelectedIcon();
 163                 }
 164             } else if (b.isRolloverEnabled() && model.isRollover()) {
 165                 altIcon = b.getRolloverIcon();
 166             }
 167 
 168             if (altIcon == null) {
 169                 altIcon = b.getIcon();
 170             }
 171 
 172             altIcon.paintIcon(c, g, iconRect.x, iconRect.y);
 173         }
 174 
 175         // Draw the Text
 176         if (text != null) {
 177             final View v = (View)c.getClientProperty(BasicHTML.propertyKey);
 178             if (v != null) {
 179                 v.paint(g, textRect);
 180             } else {
 181                 paintText(g, b, textRect, text);
 182             }
 183         }
 184     }
 185 
 186     /**
 187      * The preferred size of the button
 188      */
 189     public Dimension getPreferredSize(final JComponent c) {
 190         if (c.getComponentCount() > 0) { return null; }
 191 
 192         final AbstractButton b = (AbstractButton)c;
 193 
 194         final String text = b.getText();
 195 
 196         Icon buttonIcon = b.getIcon();
 197         if (buttonIcon == null) {
 198             buttonIcon = getDefaultIcon(b);
 199         }
 200 
 201         final Font font = b.getFont();
 202         final FontMetrics fm = b.getFontMetrics(font);
 203 
 204         Rectangle prefViewRect = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
 205         Rectangle prefIconRect = new Rectangle();
 206         Rectangle prefTextRect = new Rectangle();
 207 
 208         SwingUtilities.layoutCompoundLabel(c, fm, text, buttonIcon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), prefViewRect, prefIconRect, prefTextRect, text == null ? 0 : b.getIconTextGap());
 209 
 210         // find the union of the icon and text rects (from Rectangle.java)
 211         final int x1 = Math.min(prefIconRect.x, prefTextRect.x);
 212         final int x2 = Math.max(prefIconRect.x + prefIconRect.width, prefTextRect.x + prefTextRect.width);
 213         final int y1 = Math.min(prefIconRect.y, prefTextRect.y);
 214         final int y2 = Math.max(prefIconRect.y + prefIconRect.height, prefTextRect.y + prefTextRect.height);
 215         int width = x2 - x1;
 216         int height = y2 - y1;
 217 
 218         Insets prefInsets = b.getInsets();
 219         width += prefInsets.left + prefInsets.right;
 220         height += prefInsets.top + prefInsets.bottom;
 221         return new Dimension(width, height);
 222     }
 223 
 224     public static abstract class LabeledButtonBorder extends AquaButtonBorder {
 225         public LabeledButtonBorder(final SizeDescriptor sizeDescriptor) {
 226             super(sizeDescriptor);
 227         }
 228 
 229         public LabeledButtonBorder(final LabeledButtonBorder other) {
 230             super(other);
 231         }
 232 
 233         @Override
 234         protected AquaPainter<? extends JRSUIState> createPainter() {
 235             final AquaPainter<ValueState> painter = AquaPainter.create(JRSUIStateFactory.getLabeledButton());
 236             painter.state.set(AlignmentVertical.CENTER);
 237             painter.state.set(AlignmentHorizontal.CENTER);
 238             return painter;
 239         }
 240 
 241         protected void doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, final int x, final int y, final int width, final int height) {
 242             painter.state.set(AquaUtilControlSize.getUserSizeFrom(b));
 243             ((ValueState)painter.state).setValue(model.isSelected() ? isIndeterminate(b) ? 2 : 1 : 0); // 2=mixed, 1=on, 0=off
 244             super.doButtonPaint(b, model, g, x, y, width, height);
 245         }
 246 
 247         protected State getButtonState(final AbstractButton b, final ButtonModel model) {
 248             final State state = super.getButtonState(b, model);
 249 
 250             if (state == State.INACTIVE) return State.INACTIVE;
 251             if (state == State.DISABLED) return State.DISABLED;
 252             if (model.isArmed() && model.isPressed()) return State.PRESSED;
 253             if (model.isSelected()) return State.ACTIVE;
 254 
 255             return state;
 256         }
 257 
 258         static boolean isIndeterminate(final AbstractButton b) {
 259             return "indeterminate".equals(b.getClientProperty("JButton.selectedState"));
 260         }
 261     }
 262 }