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