1/*
2 * Copyright (c) 2001, 2016, 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 */
25package com.sun.java.swing.plaf.windows;
26
27import java.awt.*;
28import java.beans.*;
29import java.lang.ref.*;
30import javax.swing.*;
31import javax.swing.plaf.*;
32import sun.awt.AppContext;
33
34/**
35 * Wrapper for a value from the desktop. The value is lazily looked up, and
36 * can be accessed using the <code>UIManager.ActiveValue</code> method
37 * <code>createValue</code>. If the underlying desktop property changes this
38 * will force the UIs to update all known Frames. You can invoke
39 * <code>invalidate</code> to force the value to be fetched again.
40 *
41 */
42// NOTE: Don't rely on this class staying in this location. It is likely
43// to move to a different package in the future.
44public class DesktopProperty implements UIDefaults.ActiveValue {
45    private static final StringBuilder DESKTOP_PROPERTY_UPDATE_PENDING_KEY =
46            new StringBuilder("DesktopPropertyUpdatePending");
47
48    /**
49     * ReferenceQueue of unreferenced WeakPCLs.
50     */
51    private static final ReferenceQueue<DesktopProperty> queue = new ReferenceQueue<DesktopProperty>();
52
53    /**
54     * PropertyChangeListener attached to the Toolkit.
55     */
56    private WeakPCL pcl;
57    /**
58     * Key used to lookup value from desktop.
59     */
60    private final String key;
61    /**
62     * Value to return.
63     */
64    private Object value;
65    /**
66     * Fallback value in case we get null from desktop.
67     */
68    private final Object fallback;
69
70
71    /**
72     * Cleans up any lingering state held by unrefeernced
73     * DesktopProperties.
74     */
75    static void flushUnreferencedProperties() {
76        WeakPCL pcl;
77
78        while ((pcl = (WeakPCL)queue.poll()) != null) {
79            pcl.dispose();
80        }
81    }
82
83
84    /**
85     * Sets whether or not an updateUI call is pending.
86     */
87    private static synchronized void setUpdatePending(boolean update) {
88        AppContext.getAppContext()
89                .put(DESKTOP_PROPERTY_UPDATE_PENDING_KEY, update);
90    }
91
92    /**
93     * Returns true if a UI update is pending.
94     */
95    private static synchronized boolean isUpdatePending() {
96        return Boolean.TRUE.equals(AppContext.getAppContext()
97                .get(DESKTOP_PROPERTY_UPDATE_PENDING_KEY));
98    }
99
100    /**
101     * Updates the UIs of all the known Frames.
102     */
103    private static void updateAllUIs() {
104        // Check if the current UI is WindowsLookAndfeel and flush the XP style map.
105        // Note: Change the package test if this class is moved to a different package.
106        Class<?> uiClass = UIManager.getLookAndFeel().getClass();
107        if (uiClass.getPackage().equals(DesktopProperty.class.getPackage())) {
108            XPStyle.invalidateStyle();
109        }
110        Frame appFrames[] = Frame.getFrames();
111        for (Frame appFrame : appFrames) {
112            updateWindowUI(appFrame);
113        }
114    }
115
116    /**
117     * Updates the UI of the passed in window and all its children.
118     */
119    private static void updateWindowUI(Window window) {
120        SwingUtilities.updateComponentTreeUI(window);
121        Window ownedWins[] = window.getOwnedWindows();
122        for (Window ownedWin : ownedWins) {
123            updateWindowUI(ownedWin);
124        }
125    }
126
127
128    /**
129     * Creates a DesktopProperty.
130     *
131     * @param key Key used in looking up desktop value.
132     * @param fallback Value used if desktop property is null.
133     */
134    public DesktopProperty(String key, Object fallback) {
135        this.key = key;
136        this.fallback = fallback;
137        // The only sure fire way to clear our references is to create a
138        // Thread and wait for a reference to be added to the queue.
139        // Because it is so rare that you will actually change the look
140        // and feel, this stepped is forgoed and a middle ground of
141        // flushing references from the constructor is instead done.
142        // The implication is that once one DesktopProperty is created
143        // there will most likely be n (number of DesktopProperties created
144        // by the LookAndFeel) WeakPCLs around, but this number will not
145        // grow past n.
146        flushUnreferencedProperties();
147    }
148
149    /**
150     * UIManager.LazyValue method, returns the value from the desktop
151     * or the fallback value if the desktop value is null.
152     */
153    public Object createValue(UIDefaults table) {
154        if (value == null) {
155            value = configureValue(getValueFromDesktop());
156            if (value == null) {
157                value = configureValue(getDefaultValue());
158            }
159        }
160        return value;
161    }
162
163    /**
164     * Returns the value from the desktop.
165     */
166    protected Object getValueFromDesktop() {
167        Toolkit toolkit = Toolkit.getDefaultToolkit();
168
169        if (pcl == null) {
170            pcl = new WeakPCL(this, getKey(), UIManager.getLookAndFeel());
171            toolkit.addPropertyChangeListener(getKey(), pcl);
172        }
173
174        return toolkit.getDesktopProperty(getKey());
175    }
176
177    /**
178     * Returns the value to use if the desktop property is null.
179     */
180    protected Object getDefaultValue() {
181        return fallback;
182    }
183
184    /**
185     * Invalidates the current value.
186     *
187     * @param laf the LookAndFeel this DesktopProperty was created with
188     */
189    public void invalidate(LookAndFeel laf) {
190        invalidate();
191    }
192
193    /**
194     * Invalides the current value so that the next invocation of
195     * <code>createValue</code> will ask for the property again.
196     */
197    public void invalidate() {
198        value = null;
199    }
200
201    /**
202     * Requests that all components in the GUI hierarchy be updated
203     * to reflect dynamic changes in this {@literal look&feel}. This update occurs
204     * by uninstalling and re-installing the UI objects. Requests are
205     * batched and collapsed into a single update pass because often
206     * many desktop properties will change at once.
207     */
208    protected void updateUI() {
209        if (!isUpdatePending()) {
210            setUpdatePending(true);
211            Runnable uiUpdater = new Runnable() {
212                public void run() {
213                    updateAllUIs();
214                    setUpdatePending(false);
215                }
216            };
217            SwingUtilities.invokeLater(uiUpdater);
218        }
219    }
220
221    /**
222     * Configures the value as appropriate for a defaults property in
223     * the UIDefaults table.
224     */
225    protected Object configureValue(Object value) {
226        if (value != null) {
227            if (value instanceof Color) {
228                return new ColorUIResource((Color)value);
229            }
230            else if (value instanceof Font) {
231                return new FontUIResource((Font)value);
232            }
233            else if (value instanceof UIDefaults.LazyValue) {
234                value = ((UIDefaults.LazyValue)value).createValue(null);
235            }
236            else if (value instanceof UIDefaults.ActiveValue) {
237                value = ((UIDefaults.ActiveValue)value).createValue(null);
238            }
239        }
240        return value;
241    }
242
243    /**
244     * Returns the key used to lookup the desktop properties value.
245     */
246    protected String getKey() {
247        return key;
248    }
249
250
251
252    /**
253     * As there is typically only one Toolkit, the PropertyChangeListener
254     * is handled via a WeakReference so as not to pin down the
255     * DesktopProperty.
256     */
257    private static class WeakPCL extends WeakReference<DesktopProperty>
258                               implements PropertyChangeListener {
259        private String key;
260        private LookAndFeel laf;
261
262        WeakPCL(DesktopProperty target, String key, LookAndFeel laf) {
263            super(target, queue);
264            this.key = key;
265            this.laf = laf;
266        }
267
268        public void propertyChange(PropertyChangeEvent pce) {
269            DesktopProperty property = get();
270
271            if (property == null || laf != UIManager.getLookAndFeel()) {
272                // The property was GC'ed, we're no longer interested in
273                // PropertyChanges, remove the listener.
274                dispose();
275            }
276            else {
277                property.invalidate(laf);
278                property.updateUI();
279            }
280        }
281
282        void dispose() {
283            Toolkit.getDefaultToolkit().removePropertyChangeListener(key, this);
284        }
285    }
286}
287