1 /* |
|
2 * Copyright (c) 2002, 2017, 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 /* |
|
27 * <p>These classes are designed to be used while the |
|
28 * corresponding <code>LookAndFeel</code> class has been installed |
|
29 * (<code>UIManager.setLookAndFeel(new <i>XXX</i>LookAndFeel())</code>). |
|
30 * Using them while a different <code>LookAndFeel</code> is installed |
|
31 * may produce unexpected results, including exceptions. |
|
32 * Additionally, changing the <code>LookAndFeel</code> |
|
33 * maintained by the <code>UIManager</code> without updating the |
|
34 * corresponding <code>ComponentUI</code> of any |
|
35 * <code>JComponent</code>s may also produce unexpected results, |
|
36 * such as the wrong colors showing up, and is generally not |
|
37 * encouraged. |
|
38 * |
|
39 */ |
|
40 |
|
41 package com.sun.java.swing.plaf.windows; |
|
42 |
|
43 import java.awt.*; |
|
44 import java.awt.image.*; |
|
45 import java.security.AccessController; |
|
46 import java.util.*; |
|
47 |
|
48 import javax.swing.*; |
|
49 import javax.swing.border.*; |
|
50 import javax.swing.plaf.*; |
|
51 import javax.swing.text.JTextComponent; |
|
52 |
|
53 import sun.awt.image.SunWritableRaster; |
|
54 import sun.awt.windows.ThemeReader; |
|
55 import sun.security.action.GetPropertyAction; |
|
56 import sun.swing.CachedPainter; |
|
57 |
|
58 import static com.sun.java.swing.plaf.windows.TMSchema.*; |
|
59 |
|
60 |
|
61 /** |
|
62 * Implements Windows XP Styles for the Windows Look and Feel. |
|
63 * |
|
64 * @author Leif Samuelsson |
|
65 */ |
|
66 class XPStyle { |
|
67 // Singleton instance of this class |
|
68 private static XPStyle xp; |
|
69 |
|
70 // Singleton instance of SkinPainter |
|
71 private static SkinPainter skinPainter = new SkinPainter(); |
|
72 |
|
73 private static Boolean themeActive = null; |
|
74 |
|
75 private HashMap<String, Border> borderMap; |
|
76 private HashMap<String, Color> colorMap; |
|
77 |
|
78 private boolean flatMenus; |
|
79 |
|
80 static { |
|
81 invalidateStyle(); |
|
82 } |
|
83 |
|
84 /** Static method for clearing the hashmap and loading the |
|
85 * current XP style and theme |
|
86 */ |
|
87 static synchronized void invalidateStyle() { |
|
88 xp = null; |
|
89 themeActive = null; |
|
90 skinPainter.flush(); |
|
91 } |
|
92 |
|
93 /** Get the singleton instance of this class |
|
94 * |
|
95 * @return the singleton instance of this class or null if XP styles |
|
96 * are not active or if this is not Windows XP |
|
97 */ |
|
98 static synchronized XPStyle getXP() { |
|
99 if (themeActive == null) { |
|
100 Toolkit toolkit = Toolkit.getDefaultToolkit(); |
|
101 themeActive = |
|
102 (Boolean)toolkit.getDesktopProperty("win.xpstyle.themeActive"); |
|
103 if (themeActive == null) { |
|
104 themeActive = Boolean.FALSE; |
|
105 } |
|
106 if (themeActive.booleanValue()) { |
|
107 GetPropertyAction propertyAction = |
|
108 new GetPropertyAction("swing.noxp"); |
|
109 if (AccessController.doPrivileged(propertyAction) == null && |
|
110 ThemeReader.isThemed() && |
|
111 !(UIManager.getLookAndFeel() |
|
112 instanceof WindowsClassicLookAndFeel)) { |
|
113 |
|
114 xp = new XPStyle(); |
|
115 } |
|
116 } |
|
117 } |
|
118 return ThemeReader.isXPStyleEnabled() ? xp : null; |
|
119 } |
|
120 |
|
121 static boolean isVista() { |
|
122 XPStyle xp = XPStyle.getXP(); |
|
123 return (xp != null && xp.isSkinDefined(null, Part.CP_DROPDOWNBUTTONRIGHT)); |
|
124 } |
|
125 |
|
126 /** Get a named <code>String</code> value from the current style |
|
127 * |
|
128 * @param part a <code>Part</code> |
|
129 * @param state a <code>String</code> |
|
130 * @param prop a <code>String</code> |
|
131 * @return a <code>String</code> or null if key is not found |
|
132 * in the current style |
|
133 * |
|
134 * This is currently only used by WindowsInternalFrameTitlePane for painting |
|
135 * title foregound and can be removed when no longer needed |
|
136 */ |
|
137 String getString(Component c, Part part, State state, Prop prop) { |
|
138 return getTypeEnumName(c, part, state, prop); |
|
139 } |
|
140 |
|
141 TypeEnum getTypeEnum(Component c, Part part, State state, Prop prop) { |
|
142 int enumValue = ThemeReader.getEnum(part.getControlName(c), part.getValue(), |
|
143 State.getValue(part, state), |
|
144 prop.getValue()); |
|
145 return TypeEnum.getTypeEnum(prop, enumValue); |
|
146 } |
|
147 |
|
148 private static String getTypeEnumName(Component c, Part part, State state, Prop prop) { |
|
149 int enumValue = ThemeReader.getEnum(part.getControlName(c), part.getValue(), |
|
150 State.getValue(part, state), |
|
151 prop.getValue()); |
|
152 if (enumValue == -1) { |
|
153 return null; |
|
154 } |
|
155 return TypeEnum.getTypeEnum(prop, enumValue).getName(); |
|
156 } |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 /** Get a named <code>int</code> value from the current style |
|
162 * |
|
163 * @param part a <code>Part</code> |
|
164 * @return an <code>int</code> or null if key is not found |
|
165 * in the current style |
|
166 */ |
|
167 int getInt(Component c, Part part, State state, Prop prop, int fallback) { |
|
168 return ThemeReader.getInt(part.getControlName(c), part.getValue(), |
|
169 State.getValue(part, state), |
|
170 prop.getValue()); |
|
171 } |
|
172 |
|
173 /** Get a named <code>Dimension</code> value from the current style |
|
174 * |
|
175 * @return a <code>Dimension</code> or null if key is not found |
|
176 * in the current style |
|
177 * |
|
178 * This is currently only used by WindowsProgressBarUI and the value |
|
179 * should probably be cached there instead of here. |
|
180 */ |
|
181 Dimension getDimension(Component c, Part part, State state, Prop prop) { |
|
182 Dimension d = ThemeReader.getPosition(part.getControlName(c), part.getValue(), |
|
183 State.getValue(part, state), |
|
184 prop.getValue()); |
|
185 return (d != null) ? d : new Dimension(); |
|
186 } |
|
187 |
|
188 /** Get a named <code>Point</code> (e.g. a location or an offset) value |
|
189 * from the current style |
|
190 * |
|
191 * @return a <code>Point</code> or null if key is not found |
|
192 * in the current style |
|
193 * |
|
194 * This is currently only used by WindowsInternalFrameTitlePane for painting |
|
195 * title foregound and can be removed when no longer needed |
|
196 */ |
|
197 Point getPoint(Component c, Part part, State state, Prop prop) { |
|
198 Dimension d = ThemeReader.getPosition(part.getControlName(c), part.getValue(), |
|
199 State.getValue(part, state), |
|
200 prop.getValue()); |
|
201 return (d != null) ? new Point(d.width, d.height) : new Point(); |
|
202 } |
|
203 |
|
204 /** Get a named <code>Insets</code> value from the current style |
|
205 * |
|
206 * @return an <code>Insets</code> object or null if key is not found |
|
207 * in the current style |
|
208 * |
|
209 * This is currently only used to create borders and by |
|
210 * WindowsInternalFrameTitlePane for painting title foregound. |
|
211 * The return value is already cached in those places. |
|
212 */ |
|
213 Insets getMargin(Component c, Part part, State state, Prop prop) { |
|
214 Insets insets = ThemeReader.getThemeMargins(part.getControlName(c), part.getValue(), |
|
215 State.getValue(part, state), |
|
216 prop.getValue()); |
|
217 return (insets != null) ? insets : new Insets(0, 0, 0, 0); |
|
218 } |
|
219 |
|
220 |
|
221 /** Get a named <code>Color</code> value from the current style |
|
222 * |
|
223 * @return a <code>Color</code> or null if key is not found |
|
224 * in the current style |
|
225 */ |
|
226 synchronized Color getColor(Skin skin, Prop prop, Color fallback) { |
|
227 String key = skin.toString() + "." + prop.name(); |
|
228 Part part = skin.part; |
|
229 Color color = colorMap.get(key); |
|
230 if (color == null) { |
|
231 color = ThemeReader.getColor(part.getControlName(null), part.getValue(), |
|
232 State.getValue(part, skin.state), |
|
233 prop.getValue()); |
|
234 if (color != null) { |
|
235 color = new ColorUIResource(color); |
|
236 colorMap.put(key, color); |
|
237 } |
|
238 } |
|
239 return (color != null) ? color : fallback; |
|
240 } |
|
241 |
|
242 Color getColor(Component c, Part part, State state, Prop prop, Color fallback) { |
|
243 return getColor(new Skin(c, part, state), prop, fallback); |
|
244 } |
|
245 |
|
246 |
|
247 |
|
248 /** Get a named <code>Border</code> value from the current style |
|
249 * |
|
250 * @param part a <code>Part</code> |
|
251 * @return a <code>Border</code> or null if key is not found |
|
252 * in the current style or if the style for the particular |
|
253 * part is not defined as "borderfill". |
|
254 */ |
|
255 synchronized Border getBorder(Component c, Part part) { |
|
256 if (part == Part.MENU) { |
|
257 // Special case because XP has no skin for menus |
|
258 if (flatMenus) { |
|
259 // TODO: The classic border uses this color, but we should |
|
260 // create a new UI property called "PopupMenu.borderColor" |
|
261 // instead. |
|
262 return new XPFillBorder(UIManager.getColor("InternalFrame.borderShadow"), |
|
263 1); |
|
264 } else { |
|
265 return null; // Will cause L&F to use classic border |
|
266 } |
|
267 } |
|
268 Skin skin = new Skin(c, part, null); |
|
269 Border border = borderMap.get(skin.string); |
|
270 if (border == null) { |
|
271 String bgType = getTypeEnumName(c, part, null, Prop.BGTYPE); |
|
272 if ("borderfill".equalsIgnoreCase(bgType)) { |
|
273 int thickness = getInt(c, part, null, Prop.BORDERSIZE, 1); |
|
274 Color color = getColor(skin, Prop.BORDERCOLOR, Color.black); |
|
275 border = new XPFillBorder(color, thickness); |
|
276 if (part == Part.CP_COMBOBOX) { |
|
277 border = new XPStatefulFillBorder(color, thickness, part, Prop.BORDERCOLOR); |
|
278 } |
|
279 } else if ("imagefile".equalsIgnoreCase(bgType)) { |
|
280 Insets m = getMargin(c, part, null, Prop.SIZINGMARGINS); |
|
281 if (m != null) { |
|
282 if (getBoolean(c, part, null, Prop.BORDERONLY)) { |
|
283 border = new XPImageBorder(c, part); |
|
284 } else if (part == Part.CP_COMBOBOX) { |
|
285 border = new EmptyBorder(1, 1, 1, 1); |
|
286 } else { |
|
287 if(part == Part.TP_BUTTON) { |
|
288 border = new XPEmptyBorder(new Insets(3,3,3,3)); |
|
289 } else { |
|
290 border = new XPEmptyBorder(m); |
|
291 } |
|
292 } |
|
293 } |
|
294 } |
|
295 if (border != null) { |
|
296 borderMap.put(skin.string, border); |
|
297 } |
|
298 } |
|
299 return border; |
|
300 } |
|
301 |
|
302 @SuppressWarnings("serial") // Superclass is not serializable across versions |
|
303 private class XPFillBorder extends LineBorder implements UIResource { |
|
304 XPFillBorder(Color color, int thickness) { |
|
305 super(color, thickness); |
|
306 } |
|
307 |
|
308 public Insets getBorderInsets(Component c, Insets insets) { |
|
309 Insets margin = null; |
|
310 // |
|
311 // Ideally we'd have an interface defined for classes which |
|
312 // support margins (to avoid this hackery), but we've |
|
313 // decided against it for simplicity |
|
314 // |
|
315 if (c instanceof AbstractButton) { |
|
316 margin = ((AbstractButton)c).getMargin(); |
|
317 } else if (c instanceof JToolBar) { |
|
318 margin = ((JToolBar)c).getMargin(); |
|
319 } else if (c instanceof JTextComponent) { |
|
320 margin = ((JTextComponent)c).getMargin(); |
|
321 } |
|
322 insets.top = (margin != null? margin.top : 0) + thickness; |
|
323 insets.left = (margin != null? margin.left : 0) + thickness; |
|
324 insets.bottom = (margin != null? margin.bottom : 0) + thickness; |
|
325 insets.right = (margin != null? margin.right : 0) + thickness; |
|
326 |
|
327 return insets; |
|
328 } |
|
329 } |
|
330 |
|
331 @SuppressWarnings("serial") // Superclass is not serializable across versions |
|
332 private class XPStatefulFillBorder extends XPFillBorder { |
|
333 private final Part part; |
|
334 private final Prop prop; |
|
335 XPStatefulFillBorder(Color color, int thickness, Part part, Prop prop) { |
|
336 super(color, thickness); |
|
337 this.part = part; |
|
338 this.prop = prop; |
|
339 } |
|
340 |
|
341 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { |
|
342 State state = State.NORMAL; |
|
343 // special casing for comboboxes. |
|
344 // there may be more special cases in the future |
|
345 if(c instanceof JComboBox) { |
|
346 JComboBox<?> cb = (JComboBox)c; |
|
347 // note. in the future this should be replaced with a call |
|
348 // to BasicLookAndFeel.getUIOfType() |
|
349 if(cb.getUI() instanceof WindowsComboBoxUI) { |
|
350 WindowsComboBoxUI wcb = (WindowsComboBoxUI)cb.getUI(); |
|
351 state = wcb.getXPComboBoxState(cb); |
|
352 } |
|
353 } |
|
354 lineColor = getColor(c, part, state, prop, Color.black); |
|
355 super.paintBorder(c, g, x, y, width, height); |
|
356 } |
|
357 } |
|
358 |
|
359 @SuppressWarnings("serial") // Superclass is not serializable across versions |
|
360 private class XPImageBorder extends AbstractBorder implements UIResource { |
|
361 Skin skin; |
|
362 |
|
363 XPImageBorder(Component c, Part part) { |
|
364 this.skin = getSkin(c, part); |
|
365 } |
|
366 |
|
367 public void paintBorder(Component c, Graphics g, |
|
368 int x, int y, int width, int height) { |
|
369 skin.paintSkin(g, x, y, width, height, null); |
|
370 } |
|
371 |
|
372 public Insets getBorderInsets(Component c, Insets insets) { |
|
373 Insets margin = null; |
|
374 Insets borderInsets = skin.getContentMargin(); |
|
375 if(borderInsets == null) { |
|
376 borderInsets = new Insets(0, 0, 0, 0); |
|
377 } |
|
378 // |
|
379 // Ideally we'd have an interface defined for classes which |
|
380 // support margins (to avoid this hackery), but we've |
|
381 // decided against it for simplicity |
|
382 // |
|
383 if (c instanceof AbstractButton) { |
|
384 margin = ((AbstractButton)c).getMargin(); |
|
385 } else if (c instanceof JToolBar) { |
|
386 margin = ((JToolBar)c).getMargin(); |
|
387 } else if (c instanceof JTextComponent) { |
|
388 margin = ((JTextComponent)c).getMargin(); |
|
389 } |
|
390 insets.top = (margin != null? margin.top : 0) + borderInsets.top; |
|
391 insets.left = (margin != null? margin.left : 0) + borderInsets.left; |
|
392 insets.bottom = (margin != null? margin.bottom : 0) + borderInsets.bottom; |
|
393 insets.right = (margin != null? margin.right : 0) + borderInsets.right; |
|
394 |
|
395 return insets; |
|
396 } |
|
397 } |
|
398 |
|
399 @SuppressWarnings("serial") // Superclass is not serializable across versions |
|
400 private class XPEmptyBorder extends EmptyBorder implements UIResource { |
|
401 XPEmptyBorder(Insets m) { |
|
402 super(m.top+2, m.left+2, m.bottom+2, m.right+2); |
|
403 } |
|
404 |
|
405 public Insets getBorderInsets(Component c, Insets insets) { |
|
406 insets = super.getBorderInsets(c, insets); |
|
407 |
|
408 Insets margin = null; |
|
409 if (c instanceof AbstractButton) { |
|
410 Insets m = ((AbstractButton)c).getMargin(); |
|
411 // if this is a toolbar button then ignore getMargin() |
|
412 // and subtract the padding added by the constructor |
|
413 if(c.getParent() instanceof JToolBar |
|
414 && ! (c instanceof JRadioButton) |
|
415 && ! (c instanceof JCheckBox) |
|
416 && m instanceof InsetsUIResource) { |
|
417 insets.top -= 2; |
|
418 insets.left -= 2; |
|
419 insets.bottom -= 2; |
|
420 insets.right -= 2; |
|
421 } else { |
|
422 margin = m; |
|
423 } |
|
424 } else if (c instanceof JToolBar) { |
|
425 margin = ((JToolBar)c).getMargin(); |
|
426 } else if (c instanceof JTextComponent) { |
|
427 margin = ((JTextComponent)c).getMargin(); |
|
428 } |
|
429 if (margin != null) { |
|
430 insets.top = margin.top + 2; |
|
431 insets.left = margin.left + 2; |
|
432 insets.bottom = margin.bottom + 2; |
|
433 insets.right = margin.right + 2; |
|
434 } |
|
435 return insets; |
|
436 } |
|
437 } |
|
438 boolean isSkinDefined(Component c, Part part) { |
|
439 return (part.getValue() == 0) |
|
440 || ThemeReader.isThemePartDefined( |
|
441 part.getControlName(c), part.getValue(), 0); |
|
442 } |
|
443 |
|
444 |
|
445 /** Get a <code>Skin</code> object from the current style |
|
446 * for a named part (component type) |
|
447 * |
|
448 * @param part a <code>Part</code> |
|
449 * @return a <code>Skin</code> object |
|
450 */ |
|
451 synchronized Skin getSkin(Component c, Part part) { |
|
452 assert isSkinDefined(c, part) : "part " + part + " is not defined"; |
|
453 return new Skin(c, part, null); |
|
454 } |
|
455 |
|
456 |
|
457 long getThemeTransitionDuration(Component c, Part part, State stateFrom, |
|
458 State stateTo, Prop prop) { |
|
459 return ThemeReader.getThemeTransitionDuration(part.getControlName(c), |
|
460 part.getValue(), |
|
461 State.getValue(part, stateFrom), |
|
462 State.getValue(part, stateTo), |
|
463 (prop != null) ? prop.getValue() : 0); |
|
464 } |
|
465 |
|
466 |
|
467 /** A class which encapsulates attributes for a given part |
|
468 * (component type) and which provides methods for painting backgrounds |
|
469 * and glyphs |
|
470 */ |
|
471 static class Skin { |
|
472 final Component component; |
|
473 final Part part; |
|
474 final State state; |
|
475 |
|
476 private final String string; |
|
477 private Dimension size = null; |
|
478 private boolean switchStates = false; |
|
479 |
|
480 Skin(Component component, Part part) { |
|
481 this(component, part, null); |
|
482 } |
|
483 |
|
484 Skin(Part part, State state) { |
|
485 this(null, part, state); |
|
486 } |
|
487 |
|
488 Skin(Component component, Part part, State state) { |
|
489 this.component = component; |
|
490 this.part = part; |
|
491 this.state = state; |
|
492 |
|
493 String str = part.getControlName(component) +"." + part.name(); |
|
494 if (state != null) { |
|
495 str += "("+state.name()+")"; |
|
496 } |
|
497 string = str; |
|
498 } |
|
499 |
|
500 Insets getContentMargin() { |
|
501 /* idk: it seems margins are the same for all 'big enough' |
|
502 * bounding rectangles. |
|
503 */ |
|
504 int boundingWidth = 100; |
|
505 int boundingHeight = 100; |
|
506 |
|
507 Insets insets = ThemeReader.getThemeBackgroundContentMargins( |
|
508 part.getControlName(null), part.getValue(), |
|
509 0, boundingWidth, boundingHeight); |
|
510 return (insets != null) ? insets : new Insets(0, 0, 0, 0); |
|
511 } |
|
512 |
|
513 boolean haveToSwitchStates() { |
|
514 return switchStates; |
|
515 } |
|
516 |
|
517 void switchStates(boolean b) { |
|
518 switchStates = b; |
|
519 } |
|
520 |
|
521 private int getWidth(State state) { |
|
522 if (size == null) { |
|
523 size = getPartSize(part, state); |
|
524 } |
|
525 return (size != null) ? size.width : 0; |
|
526 } |
|
527 |
|
528 int getWidth() { |
|
529 return getWidth((state != null) ? state : State.NORMAL); |
|
530 } |
|
531 |
|
532 private int getHeight(State state) { |
|
533 if (size == null) { |
|
534 size = getPartSize(part, state); |
|
535 } |
|
536 return (size != null) ? size.height : 0; |
|
537 } |
|
538 |
|
539 int getHeight() { |
|
540 return getHeight((state != null) ? state : State.NORMAL); |
|
541 } |
|
542 |
|
543 public String toString() { |
|
544 return string; |
|
545 } |
|
546 |
|
547 public boolean equals(Object obj) { |
|
548 return (obj instanceof Skin && ((Skin)obj).string.equals(string)); |
|
549 } |
|
550 |
|
551 public int hashCode() { |
|
552 return string.hashCode(); |
|
553 } |
|
554 |
|
555 /** Paint a skin at x, y. |
|
556 * |
|
557 * @param g the graphics context to use for painting |
|
558 * @param dx the destination <i>x</i> coordinate |
|
559 * @param dy the destination <i>y</i> coordinate |
|
560 * @param state which state to paint |
|
561 */ |
|
562 void paintSkin(Graphics g, int dx, int dy, State state) { |
|
563 if (state == null) { |
|
564 state = this.state; |
|
565 } |
|
566 paintSkin(g, dx, dy, getWidth(state), getHeight(state), state); |
|
567 } |
|
568 |
|
569 /** Paint a skin in an area defined by a rectangle. |
|
570 * |
|
571 * @param g the graphics context to use for painting |
|
572 * @param r a <code>Rectangle</code> defining the area to fill, |
|
573 * may cause the image to be stretched or tiled |
|
574 * @param state which state to paint |
|
575 */ |
|
576 void paintSkin(Graphics g, Rectangle r, State state) { |
|
577 paintSkin(g, r.x, r.y, r.width, r.height, state); |
|
578 } |
|
579 |
|
580 /** Paint a skin at a defined position and size |
|
581 * This method supports animation. |
|
582 * |
|
583 * @param g the graphics context to use for painting |
|
584 * @param dx the destination <i>x</i> coordinate |
|
585 * @param dy the destination <i>y</i> coordinate |
|
586 * @param dw the width of the area to fill, may cause |
|
587 * the image to be stretched or tiled |
|
588 * @param dh the height of the area to fill, may cause |
|
589 * the image to be stretched or tiled |
|
590 * @param state which state to paint |
|
591 */ |
|
592 void paintSkin(Graphics g, int dx, int dy, int dw, int dh, State state) { |
|
593 if (XPStyle.getXP() == null) { |
|
594 return; |
|
595 } |
|
596 if (ThemeReader.isGetThemeTransitionDurationDefined() |
|
597 && component instanceof JComponent |
|
598 && SwingUtilities.getAncestorOfClass(CellRendererPane.class, |
|
599 component) == null) { |
|
600 AnimationController.paintSkin((JComponent) component, this, |
|
601 g, dx, dy, dw, dh, state); |
|
602 } else { |
|
603 paintSkinRaw(g, dx, dy, dw, dh, state); |
|
604 } |
|
605 } |
|
606 |
|
607 /** Paint a skin at a defined position and size. This method |
|
608 * does not trigger animation. It is needed for the animation |
|
609 * support. |
|
610 * |
|
611 * @param g the graphics context to use for painting |
|
612 * @param dx the destination <i>x</i> coordinate. |
|
613 * @param dy the destination <i>y</i> coordinate. |
|
614 * @param dw the width of the area to fill, may cause |
|
615 * the image to be stretched or tiled |
|
616 * @param dh the height of the area to fill, may cause |
|
617 * the image to be stretched or tiled |
|
618 * @param state which state to paint |
|
619 */ |
|
620 void paintSkinRaw(Graphics g, int dx, int dy, int dw, int dh, State state) { |
|
621 if (XPStyle.getXP() == null) { |
|
622 return; |
|
623 } |
|
624 skinPainter.paint(null, g, dx, dy, dw, dh, this, state); |
|
625 } |
|
626 |
|
627 /** Paint a skin at a defined position and size |
|
628 * |
|
629 * @param g the graphics context to use for painting |
|
630 * @param dx the destination <i>x</i> coordinate |
|
631 * @param dy the destination <i>y</i> coordinate |
|
632 * @param dw the width of the area to fill, may cause |
|
633 * the image to be stretched or tiled |
|
634 * @param dh the height of the area to fill, may cause |
|
635 * the image to be stretched or tiled |
|
636 * @param state which state to paint |
|
637 * @param borderFill should test if the component uses a border fill |
|
638 and skip painting if it is |
|
639 */ |
|
640 void paintSkin(Graphics g, int dx, int dy, int dw, int dh, State state, |
|
641 boolean borderFill) { |
|
642 if (XPStyle.getXP() == null) { |
|
643 return; |
|
644 } |
|
645 if(borderFill && "borderfill".equals(getTypeEnumName(component, part, |
|
646 state, Prop.BGTYPE))) { |
|
647 return; |
|
648 } |
|
649 skinPainter.paint(null, g, dx, dy, dw, dh, this, state); |
|
650 } |
|
651 } |
|
652 |
|
653 private static class SkinPainter extends CachedPainter { |
|
654 SkinPainter() { |
|
655 super(30); |
|
656 flush(); |
|
657 } |
|
658 |
|
659 public void flush() { |
|
660 super.flush(); |
|
661 } |
|
662 |
|
663 protected void paintToImage(Component c, Image image, Graphics g, |
|
664 int w, int h, Object[] args) { |
|
665 Skin skin = (Skin)args[0]; |
|
666 Part part = skin.part; |
|
667 State state = (State)args[1]; |
|
668 if (state == null) { |
|
669 state = skin.state; |
|
670 } |
|
671 if (c == null) { |
|
672 c = skin.component; |
|
673 } |
|
674 BufferedImage bi = (BufferedImage)image; |
|
675 w = bi.getWidth(); |
|
676 h = bi.getHeight(); |
|
677 |
|
678 WritableRaster raster = bi.getRaster(); |
|
679 DataBufferInt dbi = (DataBufferInt)raster.getDataBuffer(); |
|
680 // Note that stealData() requires a markDirty() afterwards |
|
681 // since we modify the data in it. |
|
682 ThemeReader.paintBackground(SunWritableRaster.stealData(dbi, 0), |
|
683 part.getControlName(c), part.getValue(), |
|
684 State.getValue(part, state), |
|
685 0, 0, w, h, w); |
|
686 SunWritableRaster.markDirty(dbi); |
|
687 } |
|
688 |
|
689 protected Image createImage(Component c, int w, int h, |
|
690 GraphicsConfiguration config, Object[] args) { |
|
691 return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); |
|
692 } |
|
693 } |
|
694 |
|
695 @SuppressWarnings("serial") // Superclass is not serializable across versions |
|
696 static class GlyphButton extends JButton { |
|
697 protected Skin skin; |
|
698 |
|
699 public GlyphButton(Component parent, Part part) { |
|
700 XPStyle xp = getXP(); |
|
701 skin = xp != null ? xp.getSkin(parent, part) : null; |
|
702 setBorder(null); |
|
703 setContentAreaFilled(false); |
|
704 setMinimumSize(new Dimension(5, 5)); |
|
705 setPreferredSize(new Dimension(16, 16)); |
|
706 setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)); |
|
707 } |
|
708 |
|
709 @SuppressWarnings("deprecation") |
|
710 public boolean isFocusTraversable() { |
|
711 return false; |
|
712 } |
|
713 |
|
714 protected State getState() { |
|
715 State state = State.NORMAL; |
|
716 if (!isEnabled()) { |
|
717 state = State.DISABLED; |
|
718 } else if (getModel().isPressed()) { |
|
719 state = State.PRESSED; |
|
720 } else if (getModel().isRollover()) { |
|
721 state = State.HOT; |
|
722 } |
|
723 return state; |
|
724 } |
|
725 |
|
726 public void paintComponent(Graphics g) { |
|
727 if (XPStyle.getXP() == null || skin == null) { |
|
728 return; |
|
729 } |
|
730 Dimension d = getSize(); |
|
731 skin.paintSkin(g, 0, 0, d.width, d.height, getState()); |
|
732 } |
|
733 |
|
734 public void setPart(Component parent, Part part) { |
|
735 XPStyle xp = getXP(); |
|
736 skin = xp != null ? xp.getSkin(parent, part) : null; |
|
737 revalidate(); |
|
738 repaint(); |
|
739 } |
|
740 |
|
741 protected void paintBorder(Graphics g) { |
|
742 } |
|
743 |
|
744 |
|
745 } |
|
746 |
|
747 // Private constructor |
|
748 private XPStyle() { |
|
749 flatMenus = getSysBoolean(Prop.FLATMENUS); |
|
750 |
|
751 colorMap = new HashMap<String, Color>(); |
|
752 borderMap = new HashMap<String, Border>(); |
|
753 // Note: All further access to the maps must be synchronized |
|
754 } |
|
755 |
|
756 |
|
757 private boolean getBoolean(Component c, Part part, State state, Prop prop) { |
|
758 return ThemeReader.getBoolean(part.getControlName(c), part.getValue(), |
|
759 State.getValue(part, state), |
|
760 prop.getValue()); |
|
761 } |
|
762 |
|
763 |
|
764 |
|
765 static Dimension getPartSize(Part part, State state) { |
|
766 return ThemeReader.getPartSize(part.getControlName(null), part.getValue(), |
|
767 State.getValue(part, state)); |
|
768 } |
|
769 |
|
770 private static boolean getSysBoolean(Prop prop) { |
|
771 // We can use any widget name here, I guess. |
|
772 return ThemeReader.getSysBoolean("window", prop.getValue()); |
|
773 } |
|
774 } |
|