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 }