001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.swing.plaf.gtk;
043:
044: import javax.swing.*;
045: import java.awt.*;
046: import java.beans.PropertyChangeEvent;
047: import java.beans.PropertyChangeListener;
048: import java.lang.reflect.Constructor;
049: import java.lang.reflect.Field;
050: import java.lang.reflect.Method;
051: import java.util.HashSet;
052: import java.util.Iterator;
053:
054: /**
055: * Value which will look something up via reflection from the GTK theme.
056: *
057: * @author Tim Boudreau
058: */
059: final class ThemeValue implements UIDefaults.ActiveValue {
060: private final Object fallback;
061: private final Object aRegion;
062: private Object aColorType = null;
063: private boolean darken = false;
064:
065: private Object value = null;
066:
067: private static Boolean functioning = null;
068:
069: /** Creates a new instance of GTKColor */
070: public ThemeValue(Object region, Object colorType, Object fallback) {
071: this .fallback = fallback;
072: this .aRegion = region;
073: this .aColorType = colorType;
074: register(this );
075: }
076:
077: /** Creates a new instance of GTKColor */
078: public ThemeValue(Object region, Object colorType, Object fallback,
079: boolean darken) {
080: this .fallback = fallback;
081: this .aRegion = region;
082: this .aColorType = colorType;
083: this .darken = darken;
084: register(this );
085: }
086:
087: public ThemeValue(Object region, Font fallback) {
088: this .fallback = fallback;
089: this .aRegion = region;
090: register(this );
091: }
092:
093: public Object createValue(UIDefaults table) {
094: if (value == null) {
095: if (!functioning()) {
096: value = fallback;
097: } else {
098: if (fallback instanceof Font) {
099: Object val = getFont();
100: if (ct++ < 4) {
101: //Wrong values returned if GTK not yet initialized
102: return val;
103: }
104: value = val;
105: } else {
106: value = getColor();
107: }
108: }
109: }
110: return value != null ? value : fallback;
111: }
112:
113: private int ct = 0;
114:
115: void clear() {
116: value = null;
117: }
118:
119: public Font getFont() {
120: Object style = getSynthStyle(aRegion);
121: if (Boolean.TRUE.equals(functioning)) {
122: try {
123: Font result = (Font) synthStyle_getFontForState.invoke(
124: style, new Object[] { getSynthContext() });
125: if (result == null) {
126: result = (Font) fallback;
127: }
128: return result;
129: } catch (Exception e) {
130: functioning = Boolean.FALSE;
131: if (log) {
132: e.printStackTrace();
133: }
134: }
135: }
136: //This will only happen once, after which functioning will be false
137: return null;
138: }
139:
140: private static boolean log = Boolean.getBoolean("themeValue.log");
141:
142: public Color getColor() {
143: Object style = getSynthStyle(aRegion);
144: if (Boolean.TRUE.equals(functioning)) {
145: try {
146: Color result = (Color) synthStyle_getColorForState
147: .invoke(style, new Object[] {
148: getSynthContext(), aColorType });
149: if (result == null) {
150: result = (Color) fallback;
151: }
152: if (darken) {
153: result = result.darker();
154: }
155: return result;
156: } catch (Exception e) {
157: functioning = Boolean.FALSE;
158: if (log) {
159: e.printStackTrace();
160: }
161: }
162: }
163: //This will only happen once, after which functioning will be false
164: return null;
165: }
166:
167: public static boolean functioning() {
168: if (functioning == null) {
169: checkFunctioning();
170: }
171: return functioning.booleanValue();
172: }
173:
174: private static void checkFunctioning() {
175: functioning = Boolean.FALSE;
176: try {
177: gtkLookAndFeel = Class
178: .forName("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"); //NOI18N
179: synthLookAndFeel = Class
180: .forName("javax.swing.plaf.synth.SynthLookAndFeel"); //NOI18N
181: region = Class.forName("javax.swing.plaf.synth.Region"); //NOI18N
182: synthStyle = Class
183: .forName("javax.swing.plaf.synth.SynthStyle"); //NOI18N
184: synthContext = Class
185: .forName("javax.swing.plaf.synth.SynthContext"); //NOI18N
186: colorType = Class
187: .forName("javax.swing.plaf.synth.ColorType"); //NOI18N
188: gtkColorType = Class
189: .forName("com.sun.java.swing.plaf.gtk.GTKColorType"); //NOI18N
190: synthUI = Class.forName("sun.swing.plaf.synth.SynthUI"); //NOI18N
191:
192: synthContextConstructor = synthContext
193: .getDeclaredConstructor(JComponent.class, region,
194: synthStyle, Integer.TYPE);
195: synthContextConstructor.setAccessible(true);
196:
197: synthStyle_getColorForState = synthStyle.getDeclaredMethod(
198: "getColorForState", //NOI18N
199: synthContext, colorType);
200:
201: synthStyle_getColorForState.setAccessible(true);
202:
203: synthStyle_getFontForState = synthStyle.getDeclaredMethod(
204: "getFontForState", //NOI18N
205: synthContext);
206:
207: synthStyle_getFontForState.setAccessible(true);
208:
209: LIGHT = valueOfField(gtkColorType, "LIGHT"); //NOI18N
210: DARK = valueOfField(gtkColorType, "DARK"); //NOI18N
211: MID = valueOfField(gtkColorType, "MID"); //NOI18N
212: BLACK = valueOfField(gtkColorType, "BLACK"); //NOI18N
213: WHITE = valueOfField(gtkColorType, "WHITE"); //NOI18N
214: TEXT_FOREGROUND = valueOfField(colorType, "TEXT_FOREGROUND"); //NOI18N
215: TEXT_BACKGROUND = valueOfField(colorType, "TEXT_BACKGROUND"); //NOI18N
216: FOCUS = valueOfField(colorType, "FOCUS"); //NOI18N
217:
218: synthContext_getContext = synthContext.getDeclaredMethod(
219: "getContext", new Class[] { Class.class,
220: JComponent.class, region, synthStyle,
221: Integer.TYPE });
222: synthContext_getContext.setAccessible(true);
223:
224: synthLookAndFeel_getStyle = synthLookAndFeel
225: .getDeclaredMethod("getStyle", JComponent.class,
226: region);
227: synthLookAndFeel_getStyle.setAccessible(true);
228:
229: REGION_BUTTON = valueOfField(region, "BUTTON"); //NOI18N
230: REGION_PANEL = valueOfField(region, "PANEL"); //NOI18N
231: REGION_SCROLLBAR_THUMB = valueOfField(region,
232: "SCROLL_BAR_THUMB"); //NOI18N
233: REGION_TAB = valueOfField(region, "TABBED_PANE_TAB"); //NOI18N
234: REGION_INTFRAME = valueOfField(region,
235: "INTERNAL_FRAME_TITLE_PANE"); //NOI18N
236:
237: synthUI_getContext = synthUI.getDeclaredMethod(
238: "getContext", JComponent.class); //NOI18N
239:
240: functioning = Boolean.TRUE;
241: } catch (Exception e) {
242: System.err
243: .println("Cannot initialize GTK colors - using hardcoded defaults "
244: + e.getMessage()); //NOI18N
245: if (log) {
246: e.printStackTrace();
247: }
248: return;
249: }
250: }
251:
252: private static JButton getDummyButton() {
253: if (dummyButton == null) {
254: dummyButton = new JButton();
255: CellRendererPane crp = new CellRendererPane();
256: crp.add(dummyButton);
257: }
258: ButtonModel mdl = dummyButton.getModel();
259: return dummyButton;
260: }
261:
262: private static JButton dummyButton = null;
263:
264: private static Object getSynthContext() {
265: try {
266: JButton dummyButton = getDummyButton();
267:
268: if (synthUI
269: .isAssignableFrom(dummyButton.getUI().getClass())) {
270: return synthUI_getContext.invoke(dummyButton.getUI(),
271: new Object[] { dummyButton });
272: } else {
273: throw new IllegalStateException(
274: "I don't have a SynthButtonUI to play with"); //NOI18N
275: }
276: } catch (Exception e) {
277: functioning = Boolean.FALSE;
278: if (log) {
279: e.printStackTrace();
280: }
281: return null;
282: }
283: }
284:
285: private static Object getSynthStyle(Object region) {
286: try {
287: return synthLookAndFeel_getStyle.invoke(null, new Object[] {
288: getDummyButton(), region });
289: } catch (Exception e) {
290: functioning = Boolean.FALSE;
291: if (log) {
292: e.printStackTrace();
293: }
294: return null;
295: }
296: }
297:
298: private static Object valueOfField(Class clazz, String field)
299: throws NoSuchFieldException, IllegalAccessException {
300: Field f = clazz.getDeclaredField(field);
301: f.setAccessible(true);
302: return f.get(null);
303: }
304:
305: private static Class<?> synthLookAndFeel = null;
306: private static Class<?> gtkLookAndFeel = null;
307: private static Class<?> colorType = null;
308: private static Class<?> region = null;
309: private static Class<?> synthStyle = null;
310: private static Class<?> synthContext = null;
311: private static Class<?> gtkColorType = null;
312: private static Class<?> synthUI = null;
313:
314: private static Constructor synthContextConstructor;
315: private static Method synthStyle_getColorForState = null;
316: private static Method synthStyle_getFontForState = null;
317: private static Method synthLookAndFeel_getStyle = null;
318:
319: private static Method synthContext_getContext = null;
320: private static Method synthUI_getContext = null;
321:
322: //XXX should be some to delete here once done experimenting
323: static Object /* <Region> */REGION_BUTTON = null;
324: static Object /* <Region> */REGION_PANEL = null;
325: static Object /* <Region> */REGION_SCROLLBAR_THUMB = null;
326: static Object /* <Region> */REGION_TAB = null;
327: static Object /* <Region> */REGION_INTFRAME = null;
328:
329: static Object /* <GTKColorType> */LIGHT = null;
330: static Object /* <GTKColorType> */DARK = null;
331: static Object /* <GTKColorType> */BLACK = null;
332: static Object /* <GTKColorType> */WHITE = null;
333: static Object /* <GTKColorType> */MID = null;
334: static Object /* <ColorType> */TEXT_FOREGROUND = null;
335: static Object /* <ColorType> */TEXT_BACKGROUND = null;
336: static Object /* <ColorType> */FOCUS = null;
337:
338: private static HashSet<ThemeValue> instances = null;
339:
340: /**
341: * Unbeautiful caching - the reflection lookup has serious performance
342: * issues - we will cache values instead. */
343: private static synchronized void register(ThemeValue value) {
344: if (instances == null) {
345: instances = new HashSet<ThemeValue>();
346: registerPcl();
347: }
348: instances.add(value);
349: }
350:
351: private static void registerPcl() {
352: PropertyChangeListener l = new Listener();
353: UIManager.addPropertyChangeListener(l);
354:
355: //Thanks to Scott Violet for how to do this. See also
356: //com.sun.java.swing.plaf.gtk.GtkLookAndFeel.WeakPCL
357:
358: Toolkit.getDefaultToolkit().addPropertyChangeListener(
359: "gnome.Gtk/FontName", l); //NOI18N
360: Toolkit.getDefaultToolkit().addPropertyChangeListener(
361: "gnome.Xft/DPI", l); //NOI18N
362: Toolkit.getDefaultToolkit().addPropertyChangeListener(
363: "gnome.Net/ThemeName", l); //NOI18N
364:
365: }
366:
367: private static class Listener implements PropertyChangeListener {
368: public void propertyChange(PropertyChangeEvent pce) {
369: if (pce.getSource() instanceof UIManager
370: && "lookAndFeel".equals( //NOI18N
371: pce.getPropertyName())) {
372:
373: String s = UIManager.getLookAndFeel().getClass()
374: .getName();
375: if (s.indexOf("gtk") < 0) { //NOI18N
376: //We have changed look and feels somehow. Unregister.
377: UIManager.removePropertyChangeListener(this );
378: Toolkit.getDefaultToolkit()
379: .removePropertyChangeListener(
380: "gnome.Gtk/FontName", this ); //NOI18N
381: Toolkit.getDefaultToolkit()
382: .removePropertyChangeListener(
383: "gnome.Xft/DPI", this ); //NOI18N
384: Toolkit.getDefaultToolkit()
385: .removePropertyChangeListener(
386: "gnome.Net/ThemeName", this ); //NOI18N
387: }
388: } else {
389: for (ThemeValue tv : instances) {
390: tv.clear();
391: }
392: }
393: }
394: }
395:
396: static {
397: //This must be called to initialize the fields before anyone tries
398: //to construct a ThemeValue passing, say, ThemeValue.LIGHT. These are
399: //populated with values from GTKLookAndFeel by reflection
400: functioning();
401: }
402: }
|