1 /*
2 * Copyright (c) 1997, 2006, 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 javax.swing.plaf.basic.*;
29 import javax.swing.border.*;
30 import javax.swing.plaf.*;
31 import javax.swing.*;
32
33 import java.awt.*;
34
35 import static com.sun.java.swing.plaf.windows.TMSchema.*;
36 import static com.sun.java.swing.plaf.windows.TMSchema.Part.*;
37 import static com.sun.java.swing.plaf.windows.XPStyle.Skin;
38 import static java.lang.Boolean.FALSE;
39 import static java.lang.Boolean.TRUE;
40 import sun.awt.AppContext;
41
42
43 /**
44 * Windows button.
45 * <p>
46 * <strong>Warning:</strong>
47 * Serialized objects of this class will not be compatible with
48 * future Swing releases. The current serialization support is appropriate
49 * for short term storage or RMI between applications running the same
50 * version of Swing. A future release of Swing will provide support for
51 * long term persistence.
52 *
53 * @author Jeff Dinkins
54 *
55 */
56 public class WindowsButtonUI extends BasicButtonUI
57 {
58 protected int dashedRectGapX;
59 protected int dashedRectGapY;
60 protected int dashedRectGapWidth;
61 protected int dashedRectGapHeight;
62
63 protected Color focusColor;
64
65 private boolean defaults_initialized = false;
66
67 private static final Object WINDOWS_BUTTON_UI_KEY = new Object();
68
69 // ********************************
70 // Create PLAF
71 // ********************************
72 public static ComponentUI createUI(JComponent c) {
73 AppContext appContext = AppContext.getAppContext();
74 WindowsButtonUI windowsButtonUI =
75 (WindowsButtonUI) appContext.get(WINDOWS_BUTTON_UI_KEY);
76 if (windowsButtonUI == null) {
77 windowsButtonUI = new WindowsButtonUI();
78 appContext.put(WINDOWS_BUTTON_UI_KEY, windowsButtonUI);
79 }
80 return windowsButtonUI;
81 }
82
83
84 // ********************************
85 // Defaults
86 // ********************************
87 protected void installDefaults(AbstractButton b) {
88 super.installDefaults(b);
89 if(!defaults_initialized) {
90 String pp = getPropertyPrefix();
91 dashedRectGapX = UIManager.getInt(pp + "dashedRectGapX");
92 dashedRectGapY = UIManager.getInt(pp + "dashedRectGapY");
93 dashedRectGapWidth = UIManager.getInt(pp + "dashedRectGapWidth");
94 dashedRectGapHeight = UIManager.getInt(pp + "dashedRectGapHeight");
95 focusColor = UIManager.getColor(pp + "focus");
96 defaults_initialized = true;
97 }
98
99 XPStyle xp = XPStyle.getXP();
100 if (xp != null) {
101 b.setBorder(xp.getBorder(b, getXPButtonType(b)));
102 LookAndFeel.installProperty(b, "rolloverEnabled", Boolean.TRUE);
103 }
104 }
105
106 protected void uninstallDefaults(AbstractButton b) {
107 super.uninstallDefaults(b);
108 defaults_initialized = false;
109 }
110
111 protected Color getFocusColor() {
112 return focusColor;
113 }
114
115 // ********************************
116 // Paint Methods
117 // ********************************
118
119 /**
120 * Overridden method to render the text without the mnemonic
121 */
122 protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, String text) {
123 WindowsGraphicsUtils.paintText(g, b, textRect, text, getTextShiftOffset());
124 }
125
126 protected void paintFocus(Graphics g, AbstractButton b, Rectangle viewRect, Rectangle textRect, Rectangle iconRect){
127
128 // focus painted same color as text on Basic??
129 int width = b.getWidth();
130 int height = b.getHeight();
131 g.setColor(getFocusColor());
132 BasicGraphicsUtils.drawDashedRect(g, dashedRectGapX, dashedRectGapY,
133 width - dashedRectGapWidth, height - dashedRectGapHeight);
134 }
135
136 protected void paintButtonPressed(Graphics g, AbstractButton b){
137 setTextShiftOffset();
138 }
139
140 // ********************************
141 // Layout Methods
142 // ********************************
143 public Dimension getPreferredSize(JComponent c) {
144 Dimension d = super.getPreferredSize(c);
145
146 /* Ensure that the width and height of the button is odd,
147 * to allow for the focus line if focus is painted
148 */
149 AbstractButton b = (AbstractButton)c;
150 if (d != null && b.isFocusPainted()) {
151 if(d.width % 2 == 0) { d.width += 1; }
152 if(d.height % 2 == 0) { d.height += 1; }
153 }
154 return d;
155 }
156
157
158 /* These rectangles/insets are allocated once for all
159 * ButtonUI.paint() calls. Re-using rectangles rather than
160 * allocating them in each paint call substantially reduced the time
161 * it took paint to run. Obviously, this method can't be re-entered.
162 */
163 private Rectangle viewRect = new Rectangle();
164
165 public void paint(Graphics g, JComponent c) {
166 AbstractButton b = (AbstractButton)c;
167
168 if(isStateChangeAnimated(b)) {
169 boolean rollover = b.getModel().isRollover();
170 AnimationController anim = AnimationController.getAnimationController();
171 boolean wasRollover = b.getClientProperty("WindowsButtonUI.wasRollover") == TRUE;
172
173 if(rollover != wasRollover) {
174 State currentState = wasRollover?State.HOT:State.NORMAL;
175 State targetState = rollover?State.HOT:State.NORMAL;
176 anim.startAnimation(c, getXPButtonType(b), currentState, targetState, 250);
177 }
178
179 b.putClientProperty("WindowsButtonUI.animateToNullState", !rollover);
180 b.putClientProperty("WindowsButtonUI.wasRollover", rollover);
181 }
182
183 if (XPStyle.getXP() != null) {
184 WindowsButtonUI.paintXPButtonBackground(g, c);
185 }
186 super.paint(g, c);
187 }
188
189 private static boolean isStateChangeAnimated(AbstractButton button) {
190 final String propName = "WindowsButtonUI.animateStateChange";
191
192 if(button.getParent() instanceof JToolBar) {
193 JToolBar toolBar = (JToolBar)button.getParent();
194 if(toolBar.getClientProperty(propName)==TRUE)
195 return true;
196 }
197
198 return button.getClientProperty(propName)==TRUE;
199 }
200
201 static Part getXPButtonType(AbstractButton b) {
202 if(b instanceof JCheckBox) {
203 return Part.BP_CHECKBOX;
204 }
205 if(b instanceof JRadioButton) {
206 return Part.BP_RADIOBUTTON;
207 }
208 boolean toolbar = (b.getParent() instanceof JToolBar);
209 if(b.getClientProperty("WindowsButtonUI.displayAsInToolbar") == TRUE)
210 toolbar = true;
211 return toolbar ? Part.TP_BUTTON : Part.BP_PUSHBUTTON;
212 }
213
214 static State getXPButtonState(AbstractButton b) {
215 Part part = getXPButtonType(b);
216 ButtonModel model = b.getModel();
217 State state = State.NORMAL;
218 switch (part) {
219 case BP_RADIOBUTTON:
220 /* falls through */
221 case BP_CHECKBOX:
222 if (! model.isEnabled()) {
223 state = (model.isSelected()) ? State.CHECKEDDISABLED
224 : State.UNCHECKEDDISABLED;
225 } else if (model.isPressed() && model.isArmed()) {
226 state = (model.isSelected()) ? State.CHECKEDPRESSED
227 : State.UNCHECKEDPRESSED;
228 } else if (model.isRollover()) {
229 state = (model.isSelected()) ? State.CHECKEDHOT
230 : State.UNCHECKEDHOT;
231 } else {
232 state = (model.isSelected()) ? State.CHECKEDNORMAL
233 : State.UNCHECKEDNORMAL;
234 }
235 break;
236 case BP_PUSHBUTTON:
237 /* falls through */
238 case TP_BUTTON:
239 boolean toolbar = (b.getParent() instanceof JToolBar);
240 if (toolbar) {
241 if (model.isArmed() && model.isPressed()) {
242 state = State.PRESSED;
243 } else if (!model.isEnabled()) {
244 state = State.DISABLED;
245 } else if (model.isSelected() && model.isRollover()) {
246 state = State.HOTCHECKED;
247 } else if (model.isSelected()) {
248 state = State.CHECKED;
249 } else if (model.isRollover()) {
250 state = State.HOT;
251 } else if (b.hasFocus()) {
252 state = State.HOT;
253 }
254 } else {
255 if ((model.isArmed() && model.isPressed())
256 || model.isSelected()) {
257 state = State.PRESSED;
258 } else if (!model.isEnabled()) {
259 state = State.DISABLED;
260 } else if (model.isRollover() || model.isPressed()) {
261 state = State.HOT;
262 } else if (b instanceof JButton
263 && ((JButton)b).isDefaultButton()) {
264 state = State.DEFAULTED;
265 } else if (b.hasFocus()) {
266 state = State.HOT;
267 }
268 }
269 break;
270 default :
271 state = State.NORMAL;
272 }
273
274 return state;
275 }
276
277 static void paintXPButtonBackground(Graphics g, JComponent c) {
278 AbstractButton b = (AbstractButton)c;
279
280 XPStyle xp = XPStyle.getXP();
281
282 Part part = getXPButtonType(b);
283
284 if (b.isContentAreaFilled() && b.getBorder() != null
285 && b.isBorderPainted() && xp != null) {
286
287 Skin skin = xp.getSkin(b, part);
288
289 State state = getXPButtonState(b);
290 Dimension d = c.getSize();
291 int dx = 0;
292 int dy = 0;
293 int dw = d.width;
294 int dh = d.height;
295
296 Border border = c.getBorder();
297 Insets insets;
298 if (border != null) {
299 // Note: The border may be compound, containing an outer
300 // opaque border (supplied by the application), plus an
301 // inner transparent margin border. We want to size the
302 // background to fill the transparent part, but stay
303 // inside the opaque part.
304 insets = WindowsButtonUI.getOpaqueInsets(border, c);
305 } else {
306 insets = c.getInsets();
307 }
308 if (insets != null) {
309 dx += insets.left;
310 dy += insets.top;
311 dw -= (insets.left + insets.right);
312 dh -= (insets.top + insets.bottom);
313 }
314
315 if(c.getClientProperty("WindowsButtonUI.animateToNullState") == TRUE)
316 state = null;
317
318 AnimationController.paintSkin(c, skin, g, dx, dy, dw, dh, state);
319 }
320 }
321
322 /**
323 * returns - b.getBorderInsets(c) if border is opaque
324 * - null if border is completely non-opaque
325 * - somewhere inbetween if border is compound and
326 * outside border is opaque and inside isn't
327 */
328 private static Insets getOpaqueInsets(Border b, Component c) {
329 if (b == null) {
330 return null;
331 }
332 if (b.isBorderOpaque()) {
333 return b.getBorderInsets(c);
334 } else if (b instanceof CompoundBorder) {
335 CompoundBorder cb = (CompoundBorder)b;
336 Insets iOut = getOpaqueInsets(cb.getOutsideBorder(), c);
337 if (iOut != null && iOut.equals(cb.getOutsideBorder().getBorderInsets(c))) {
338 // Outside border is opaque, keep looking
339 Insets iIn = getOpaqueInsets(cb.getInsideBorder(), c);
340 if (iIn == null) {
341 // Inside is non-opaque, use outside insets
342 return iOut;
343 } else {
344 // Found non-opaque somewhere in the inside (which is
345 // also compound).
346 return new Insets(iOut.top + iIn.top, iOut.left + iIn.left,
347 iOut.bottom + iIn.bottom, iOut.right + iIn.right);
348 }
349 } else {
350 // Outside is either all non-opaque or has non-opaque
351 // border inside another compound border
352 return iOut;
353 }
354 } else {
355 return null;
356 }
357 }
358 }