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     private static final RecyclableSizingIcon regularIcon = new RecyclableSizingIcon(18);
46     private static final RecyclableSizingIcon smallIcon = new RecyclableSizingIcon(16);
47     private static final RecyclableSizingIcon miniIcon = new RecyclableSizingIcon(14);
48 
49     protected static class RecyclableSizingIcon extends RecyclableSingleton<Icon> {
50         final int iconSize;
RecyclableSizingIcon(final int iconSize)51         public RecyclableSizingIcon(final int iconSize) { this.iconSize = iconSize; }
52 
getInstance()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 
AquaButtonLabeledUI()60     public AquaButtonLabeledUI() {
61         widgetBorder = getPainter();
62     }
63 
applySizeFor(final JComponent c, final Size newSize)64     public void applySizeFor(final JComponent c, final Size newSize) {
65         super.applySizeFor(c, newSize);
66         widgetBorder = (AquaButtonBorder)widgetBorder.deriveBorderForSize(newSize);
67     }
68 
getDefaultIcon(final JComponent c)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 
setThemeBorder(final AbstractButton b)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 
getPainter()87     protected abstract AquaButtonBorder getPainter();
88 
paint(final Graphics g, final JComponent c)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      */
getPreferredSize(final JComponent c)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 abstract static class LabeledButtonBorder extends AquaButtonBorder {
LabeledButtonBorder(final SizeDescriptor sizeDescriptor)225         public LabeledButtonBorder(final SizeDescriptor sizeDescriptor) {
226             super(sizeDescriptor);
227         }
228 
LabeledButtonBorder(final LabeledButtonBorder other)229         public LabeledButtonBorder(final LabeledButtonBorder other) {
230             super(other);
231         }
232 
233         @Override
createPainter()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 
doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, final int x, final int y, final int width, final int height)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 
getButtonState(final AbstractButton b, final ButtonModel model)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 
isIndeterminate(final AbstractButton b)258         static boolean isIndeterminate(final AbstractButton b) {
259             return "indeterminate".equals(b.getClientProperty("JButton.selectedState"));
260         }
261     }
262 }
263