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