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