1 /* 2 * Copyright (c) 2006, 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 com.sun.java.swing.plaf.windows; 27 28 import java.security.AccessController; 29 import sun.security.action.GetBooleanAction; 30 31 import java.util.*; 32 import java.beans.PropertyChangeListener; 33 import java.beans.PropertyChangeEvent; 34 import java.awt.*; 35 import java.awt.event.*; 36 import javax.swing.*; 37 38 39 40 import sun.swing.UIClientPropertyKey; 41 import com.sun.java.swing.plaf.windows.TMSchema.State; 42 import static com.sun.java.swing.plaf.windows.TMSchema.State.*; 43 import com.sun.java.swing.plaf.windows.TMSchema.Part; 44 import com.sun.java.swing.plaf.windows.TMSchema.Prop; 45 import com.sun.java.swing.plaf.windows.XPStyle.Skin; 46 47 import sun.awt.AppContext; 48 49 /** 50 * A class to help mimic Vista theme animations. The only kind of 51 * animation it handles for now is 'transition' animation (this seems 52 * to be the only animation which Vista theme can do). This is when 53 * one picture fadein over another one in some period of time. 54 * According to 55 * https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=86852&SiteID=4 56 * The animations are all linear. 57 * 58 * This class has a number of responsibilities. 59 * <ul> 60 * <li> It trigger rapaint for the UI components involved in the animation 61 * <li> It tracks the animation state for every UI component involved in the 62 * animation and paints {@code Skin} in new {@code State} over the 63 * {@code Skin} in last {@code State} using 64 * {@code AlphaComposite.SrcOver.derive(alpha)} where {code alpha} 65 * depends on the state of animation 66 * </ul> 67 * 68 * @author Igor Kushnirskiy 69 */ 70 class AnimationController implements ActionListener, PropertyChangeListener { 71 72 private final static boolean VISTA_ANIMATION_DISABLED = 73 AccessController.doPrivileged(new GetBooleanAction("swing.disablevistaanimation")); 74 75 76 private final static Object ANIMATION_CONTROLLER_KEY = 77 new StringBuilder("ANIMATION_CONTROLLER_KEY"); 78 79 private final Map<JComponent, Map<Part, AnimationState>> animationStateMap = 80 new WeakHashMap<JComponent, Map<Part, AnimationState>>(); 81 82 //this timer is used to cause repaint on animated components 83 //30 repaints per second should give smooth animation affect 84 private final javax.swing.Timer timer = 85 new javax.swing.Timer(1000/30, this); 86 87 private static synchronized AnimationController getAnimationController() { 88 AppContext appContext = AppContext.getAppContext(); 89 Object obj = appContext.get(ANIMATION_CONTROLLER_KEY); 90 if (obj == null) { 91 obj = new AnimationController(); 92 appContext.put(ANIMATION_CONTROLLER_KEY, obj); 93 } 94 return (AnimationController) obj; 95 } 96 97 private AnimationController() { 98 timer.setRepeats(true); 99 timer.setCoalesce(true); 100 //we need to dispose the controller on l&f change 101 UIManager.addPropertyChangeListener(this); 102 } 103 104 private static void triggerAnimation(JComponent c, 105 Part part, State newState) { 106 if (c instanceof javax.swing.JTabbedPane 107 || part == Part.TP_BUTTON) { 108 //idk: we can not handle tabs animation because 109 //the same (component,part) is used to handle all the tabs 110 //and we can not track the states 111 //Vista theme might have transition duration for toolbar buttons 112 //but native application does not seem to animate them 113 return; 114 } 115 AnimationController controller = 116 AnimationController.getAnimationController(); 117 State oldState = controller.getState(c, part); 118 if (oldState != newState) { 119 controller.putState(c, part, newState); 120 if (newState == State.DEFAULTED) { 121 // it seems for DEFAULTED button state Vista does animation from 122 // HOT 123 oldState = State.HOT; 124 } 125 if (oldState != null) { 126 long duration; 127 if (newState == State.DEFAULTED) { 128 //Only button might have DEFAULTED state 129 //idk: do not know how to get the value from Vista 130 //one second seems plausible value 131 duration = 1000; 132 } else { 133 duration = XPStyle.getXP().getThemeTransitionDuration( 134 c, part, 135 normalizeState(oldState), 136 normalizeState(newState), 137 Prop.TRANSITIONDURATIONS); 138 } 139 controller.startAnimation(c, part, oldState, newState, duration); 140 } 141 } 142 } 143 144 // for scrollbar up, down, left and right button pictures are 145 // defined by states. It seems that theme has duration defined 146 // only for up button states thus we doing this translation here. 147 private static State normalizeState(State state) { 148 State rv; 149 switch (state) { 150 case DOWNPRESSED: 151 /* falls through */ 152 case LEFTPRESSED: 153 /* falls through */ 154 case RIGHTPRESSED: 155 rv = UPPRESSED; 156 break; 157 158 case DOWNDISABLED: 159 /* falls through */ 160 case LEFTDISABLED: 161 /* falls through */ 162 case RIGHTDISABLED: 163 rv = UPDISABLED; 164 break; 165 166 case DOWNHOT: 167 /* falls through */ 168 case LEFTHOT: 169 /* falls through */ 170 case RIGHTHOT: 171 rv = UPHOT; 172 break; 173 174 case DOWNNORMAL: 175 /* falls through */ 176 case LEFTNORMAL: 177 /* falls through */ 178 case RIGHTNORMAL: 179 rv = UPNORMAL; 180 break; 181 182 default : 183 rv = state; 184 break; 185 } 186 return rv; 187 } 188 189 private synchronized State getState(JComponent component, Part part) { 190 State rv = null; 191 Object tmpObject = 192 component.getClientProperty(PartUIClientPropertyKey.getKey(part)); 193 if (tmpObject instanceof State) { 194 rv = (State) tmpObject; 195 } 196 return rv; 197 } 198 199 private synchronized void putState(JComponent component, Part part, 200 State state) { 201 component.putClientProperty(PartUIClientPropertyKey.getKey(part), 202 state); 203 } 204 205 private synchronized void startAnimation(JComponent component, 206 Part part, 207 State startState, 208 State endState, 209 long millis) { 210 boolean isForwardAndReverse = false; 211 if (endState == State.DEFAULTED) { 212 isForwardAndReverse = true; 213 } 214 Map<Part, AnimationState> map = animationStateMap.get(component); 215 if (millis <= 0) { 216 if (map != null) { 217 map.remove(part); 218 if (map.size() == 0) { 219 animationStateMap.remove(component); 220 } 221 } 222 return; 223 } 224 if (map == null) { 225 map = new EnumMap<Part, AnimationState>(Part.class); 226 animationStateMap.put(component, map); 227 } 228 map.put(part, 229 new AnimationState(startState, millis, isForwardAndReverse)); 230 if (! timer.isRunning()) { 231 timer.start(); 232 } 233 } 234 235 static void paintSkin(JComponent component, Skin skin, 236 Graphics g, int dx, int dy, int dw, int dh, State state) { 237 if (VISTA_ANIMATION_DISABLED) { 238 skin.paintSkinRaw(g, dx, dy, dw, dh, state); 239 return; 240 } 241 triggerAnimation(component, skin.part, state); 242 AnimationController controller = getAnimationController(); 243 synchronized (controller) { 244 AnimationState animationState = null; 245 Map<Part, AnimationState> map = 246 controller.animationStateMap.get(component); 247 if (map != null) { 248 animationState = map.get(skin.part); 249 } 250 if (animationState != null) { 251 animationState.paintSkin(skin, g, dx, dy, dw, dh, state); 252 } else { 253 skin.paintSkinRaw(g, dx, dy, dw, dh, state); 254 } 255 } 256 } 257 258 public synchronized void propertyChange(PropertyChangeEvent e) { 259 if ("lookAndFeel" == e.getPropertyName() 260 && ! (e.getNewValue() instanceof WindowsLookAndFeel) ) { 261 dispose(); 262 } 263 } 264 265 public synchronized void actionPerformed(ActionEvent e) { 266 java.util.List<JComponent> componentsToRemove = null; 267 java.util.List<Part> partsToRemove = null; 268 for (JComponent component : animationStateMap.keySet()) { 269 component.repaint(); 270 if (partsToRemove != null) { 271 partsToRemove.clear(); 272 } 273 Map<Part, AnimationState> map = animationStateMap.get(component); 274 if (! component.isShowing() 275 || map == null 276 || map.size() == 0) { 277 if (componentsToRemove == null) { 278 componentsToRemove = new ArrayList<JComponent>(); 279 } 280 componentsToRemove.add(component); 281 continue; 282 } 283 for (Part part : map.keySet()) { 284 if (map.get(part).isDone()) { 285 if (partsToRemove == null) { 286 partsToRemove = new ArrayList<Part>(); 287 } 288 partsToRemove.add(part); 289 } 290 } 291 if (partsToRemove != null) { 292 if (partsToRemove.size() == map.size()) { 293 //animation is done for the component 294 if (componentsToRemove == null) { 295 componentsToRemove = new ArrayList<JComponent>(); 296 } 297 componentsToRemove.add(component); 298 } else { 299 for (Part part : partsToRemove) { 300 map.remove(part); 301 } 302 } 303 } 304 } 305 if (componentsToRemove != null) { 306 for (JComponent component : componentsToRemove) { 307 animationStateMap.remove(component); 308 } 309 } 310 if (animationStateMap.size() == 0) { 311 timer.stop(); 312 } 313 } 314 315 private synchronized void dispose() { 316 timer.stop(); 317 UIManager.removePropertyChangeListener(this); 318 synchronized (AnimationController.class) { 319 AppContext.getAppContext() 320 .put(ANIMATION_CONTROLLER_KEY, null); 321 } 322 } 323 324 private static class AnimationState { 325 private final State startState; 326 327 //animation duration in nanoseconds 328 private final long duration; 329 330 //animatin start time in nanoseconds 331 private long startTime; 332 333 //direction the alpha value is changing 334 //forward - from 0 to 1 335 //!forward - from 1 to 0 336 private boolean isForward = true; 337 338 //if isForwardAndReverse the animation continually goes 339 //forward and reverse. alpha value is changing from 0 to 1 then 340 //from 1 to 0 and so forth 341 private boolean isForwardAndReverse; 342 343 private float progress; 344 345 AnimationState(final State startState, 346 final long milliseconds, 347 boolean isForwardAndReverse) { 348 assert startState != null && milliseconds > 0; 349 assert SwingUtilities.isEventDispatchThread(); 350 351 this.startState = startState; 352 this.duration = milliseconds * 1000000; 353 this.startTime = System.nanoTime(); 354 this.isForwardAndReverse = isForwardAndReverse; 355 progress = 0f; 356 } 357 private void updateProgress() { 358 assert SwingUtilities.isEventDispatchThread(); 359 360 if (isDone()) { 361 return; 362 } 363 long currentTime = System.nanoTime(); 364 365 progress = ((float) (currentTime - startTime)) 366 / duration; 367 progress = Math.max(progress, 0); //in case time was reset 368 if (progress >= 1) { 369 progress = 1; 370 if (isForwardAndReverse) { 371 startTime = currentTime; 372 progress = 0; 373 isForward = ! isForward; 374 } 375 } 376 } 377 void paintSkin(Skin skin, Graphics _g, 378 int dx, int dy, int dw, int dh, State state) { 379 assert SwingUtilities.isEventDispatchThread(); 380 381 updateProgress(); 382 if (! isDone()) { 383 Graphics2D g = (Graphics2D) _g.create(); 384 skin.paintSkinRaw(g, dx, dy, dw, dh, startState); 385 float alpha; 386 if (isForward) { 387 alpha = progress; 388 } else { 389 alpha = 1 - progress; 390 } 391 g.setComposite(AlphaComposite.SrcOver.derive(alpha)); 392 skin.paintSkinRaw(g, dx, dy, dw, dh, state); 393 g.dispose(); 394 } else { 395 skin.paintSkinRaw(_g, dx, dy, dw, dh, state); 396 } 397 } 398 boolean isDone() { 399 assert SwingUtilities.isEventDispatchThread(); 400 401 return progress >= 1; 402 } 403 } 404 405 private static class PartUIClientPropertyKey 406 implements UIClientPropertyKey { 407 408 private static final Map<Part, PartUIClientPropertyKey> map = 409 new EnumMap<Part, PartUIClientPropertyKey>(Part.class); 410 411 static synchronized PartUIClientPropertyKey getKey(Part part) { 412 PartUIClientPropertyKey rv = map.get(part); 413 if (rv == null) { 414 rv = new PartUIClientPropertyKey(part); 415 map.put(part, rv); 416 } 417 return rv; 418 } 419 420 private final Part part; 421 private PartUIClientPropertyKey(Part part) { 422 this.part = part; 423 } 424 public String toString() { 425 return part.toString(); 426 } 427 } 428 }