001: /*
002: * Copyright (c) 2005-2008 Substance Kirill Grouchnikov. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of Substance Kirill Grouchnikov nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030: package org.jvnet.substance.utils;
031:
032: import java.awt.Component;
033: import java.awt.Container;
034: import java.awt.event.ActionEvent;
035: import java.awt.event.ActionListener;
036: import java.lang.ref.WeakReference;
037: import java.util.WeakHashMap;
038:
039: import javax.swing.JButton;
040: import javax.swing.JDialog;
041: import javax.swing.JFrame;
042: import javax.swing.JInternalFrame;
043: import javax.swing.JRootPane;
044: import javax.swing.Timer;
045: import javax.swing.UIManager;
046: import javax.swing.JInternalFrame.JDesktopIcon;
047:
048: import org.jvnet.substance.SubstanceLookAndFeel;
049:
050: /**
051: * Tracker for pulsating (default and focused) <code>JButton</code>s. This
052: * class is <b>for internal use only</b>.
053: *
054: * @author Kirill Grouchnikov
055: */
056: public class PulseTracker implements ActionListener {
057: /**
058: * Map (with weakly-referenced keys) of all trackers. For each default
059: * button which has not been claimed by GC, we have a tracker (with
060: * associated <code>Timer</code>).
061: */
062: private static WeakHashMap<JButton, PulseTracker> trackers = new WeakHashMap<JButton, PulseTracker>();
063:
064: /**
065: * Map (with weakly-referenced keys) of cycle counts. For each default
066: * button which is shown <b>and </b> is in window that owns focus,
067: * <code>this</code> map contains the cycle count (for animation
068: * purposes). On each event of the associated <code>Timer</code> (see
069: * {@link #actionPerformed(ActionEvent)}), the counter is incremented by 1.
070: * For buttons that are in windows that lose focus, the counter is reverted
071: * back to 0 (animation stops).
072: */
073: private static WeakHashMap<JButton, Long> cycles = new WeakHashMap<JButton, Long>();
074:
075: /**
076: * Waek reference to the associated button.
077: */
078: private WeakReference<JButton> buttonRef;
079:
080: /**
081: * The associated timer.
082: */
083: private Timer timer;
084:
085: /**
086: * Simple constructor.
087: *
088: * @param jbutton
089: */
090: private PulseTracker(JButton jbutton) {
091: // Create weak reference.
092: this .buttonRef = new WeakReference<JButton>(jbutton);
093: // Create coalesced timer.
094: this .timer = new Timer(50, this );
095: this .timer.setCoalesce(true);
096: // Store event handler and initial cycle count.
097: PulseTracker.trackers.put(jbutton, this );
098: PulseTracker.cycles.put(jbutton, (long) 0);
099: }
100:
101: /**
102: * Recursively checks whether the specified component or one of its inner
103: * components has focus.
104: *
105: * @param component
106: * Component to check.
107: * @return <code>true</code> if the specified component or one of its
108: * inner components has focus, <code>false</code> otherwise.
109: */
110: private static boolean isInFocusedWindow(Component component) {
111: if (component == null) {
112: return false;
113: }
114:
115: // check if the component itself is focus owner
116: if (component.isFocusOwner()) {
117: return true;
118: }
119:
120: // recursively find if has focus owner component
121: if (component instanceof Container) {
122: for (Component comp : ((Container) component)
123: .getComponents()) {
124: if (isInFocusedWindow(comp)) {
125: return true;
126: }
127: }
128: }
129: return false;
130: }
131:
132: /**
133: * Recursively checks whether the specified component has visible glass
134: * pane.
135: *
136: * @param component
137: * Component to check.
138: * @return <code>true</code> if the specified component has visible glass
139: * pane, <code>false</code> otherwise.
140: */
141: private static boolean hasGlassPane(Component component) {
142: if (component == null) {
143: return false;
144: }
145: // check if the component has visible glass pane
146: Component glassPane = null;
147: if (component instanceof JDialog) {
148: glassPane = ((JDialog) component).getGlassPane();
149: }
150: if (component instanceof JFrame) {
151: glassPane = ((JFrame) component).getGlassPane();
152: }
153: if ((glassPane != null) && (glassPane.isVisible())) {
154: return true;
155: }
156:
157: return false;
158: }
159:
160: /*
161: * (non-Javadoc)
162: *
163: * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
164: */
165: public void actionPerformed(ActionEvent event) {
166: // get the button and check if it wasn't GC'd
167: JButton jButton = this .buttonRef.get();
168: if (jButton == null) {
169: return;
170: }
171: if (PulseTracker.hasGlassPane(jButton.getTopLevelAncestor()))
172: return;
173: if (!isPulsating(jButton)) {
174: // has since lost its default status
175: PulseTracker tracker = trackers.get(jButton);
176: tracker.stopTimer();
177: tracker.buttonRef.clear();
178: trackers.remove(jButton);
179: cycles.remove(jButton);
180: } else {
181: if ((!PulseTracker.isInFocusedWindow(jButton
182: .getTopLevelAncestor()))
183: && (!isAttentionDrawingCloseButton(jButton))) {
184: // no focus in button window - will restore original (not
185: // animated) painting
186: PulseTracker.update(jButton);
187: } else {
188: // check if it's enabled
189: if (jButton.isEnabled()) {
190: // increment cycle count for pulsating buttons.
191: long oldCycle = cycles.get(jButton);
192: if (oldCycle == 20) {
193: oldCycle = isAttentionDrawingCloseButton(jButton) ? -80
194: : 0;
195: }
196: cycles.put(jButton, oldCycle + 1);
197: } else {
198: // revert to 0 if it's not enabled
199: if (cycles.get(jButton) != 0) {
200: cycles.put(jButton, (long) 0);
201: }
202: }
203: }
204: }
205: // maybe LAF has changed
206: if (UIManager.getLookAndFeel() instanceof SubstanceLookAndFeel)
207: jButton.repaint();
208: }
209:
210: /**
211: * Starts the associated timer.
212: */
213: private void startTimer() {
214: if (!this .timer.isRunning()) {
215: this .timer.start();
216: }
217: }
218:
219: /**
220: * Stops the associated timer.
221: */
222: private void stopTimer() {
223: if (this .timer.isRunning()) {
224: this .timer.stop();
225: }
226: }
227:
228: /**
229: * Returns the status of the associated timer.
230: *
231: * @return <code>true</code> is the associated timer is running,
232: * <code>false</code> otherwise.
233: */
234: private boolean isRunning() {
235: return this .timer.isRunning();
236: }
237:
238: /**
239: * Updates the state of the specified button which must be a default button
240: * in some window. The button state is determined based on focus ownership.
241: *
242: * @param jButton
243: * Button.
244: */
245: public static synchronized void update(JButton jButton) {
246: boolean hasFocus = PulseTracker.isInFocusedWindow(jButton
247: .getTopLevelAncestor());
248: PulseTracker tracker = trackers.get(jButton);
249: if (!hasFocus) {
250: // remove
251: if (tracker == null) {
252: return;
253: }
254: if (cycles.get(jButton) == 0) {
255: return;
256: }
257: cycles.put(jButton, (long) 0);
258: // System.out.println("r::" + trackers.size());
259: } else {
260: // add
261: if (tracker != null) {
262: tracker.startTimer();
263: return;
264: }
265: tracker = new PulseTracker(jButton);
266: tracker.startTimer();
267: trackers.put(jButton, tracker);
268: long initialCycle = isAttentionDrawingCloseButton(jButton) ? -80
269: : 0;
270: cycles.put(jButton, initialCycle);
271: // System.out.println("a::" + trackers.size());
272: }
273: }
274:
275: /**
276: * Retrieves the current cycle count for the specified button.
277: *
278: * @param jButton
279: * Button.
280: * @return Current cycle count for the specified button.
281: */
282: public static long getCycles(JButton jButton) {
283: Long cycleCount = cycles.get(jButton);
284: if (cycleCount == null) {
285: return 0;
286: }
287: long result = cycleCount.longValue();
288: if (result < 0)
289: result = 0;
290: return result;
291: }
292:
293: /**
294: * Retrieves the animation state for the specified button.
295: *
296: * @param jButton
297: * Button.
298: * @return <code>true</code> if the specified button is being animated,
299: * <code>false</code> otherwise.
300: */
301: public static boolean isAnimating(JButton jButton) {
302: PulseTracker tracker = trackers.get(jButton);
303: if (tracker == null) {
304: return false;
305: }
306: return tracker.isRunning();
307: }
308:
309: /**
310: * Returns memory usage.
311: *
312: * @return Memory usage string.
313: */
314: static String getMemoryUsage() {
315: StringBuffer sb = new StringBuffer();
316: sb.append("PulseTracker: \n");
317: sb.append("\t" + trackers.size() + " trackers, "
318: + cycles.size() + " cycles");
319: return sb.toString();
320: }
321:
322: /**
323: * Checks whether the specified button is attention-drawing
324: * <code>close</code> button of some internal frame, root pane or desktop
325: * icon.
326: *
327: * @param jButton
328: * Button.
329: * @return <code>true</code> if the specified button is <code>close</code>
330: * button of some internal frame, root pane or desktop icon,
331: * <code>false</code> otherwise.
332: */
333: public static boolean isAttentionDrawingCloseButton(JButton jButton) {
334: if (SubstanceCoreUtilities.isTitleCloseButton(jButton)) {
335: // check if have windowModified property
336: Component comp = jButton;
337: boolean isWindowModified = false;
338: while (comp != null) {
339: if (comp instanceof JInternalFrame) {
340: JInternalFrame jif = (JInternalFrame) comp;
341: isWindowModified = Boolean.TRUE
342: .equals(jif
343: .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
344: break;
345: }
346: if (comp instanceof JRootPane) {
347: JRootPane jrp = (JRootPane) comp;
348: isWindowModified = Boolean.TRUE
349: .equals(jrp
350: .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
351: break;
352: }
353: if (comp instanceof JDesktopIcon) {
354: JDesktopIcon jdi = (JDesktopIcon) comp;
355: JInternalFrame jif = jdi.getInternalFrame();
356: isWindowModified = Boolean.TRUE
357: .equals(jif
358: .getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
359: break;
360: }
361: comp = comp.getParent();
362: }
363: if (isWindowModified) {
364: return true;
365: }
366: }
367: return false;
368: }
369:
370: /**
371: * Checks whether the specified button is pulsating.
372: *
373: * @param jButton
374: * Button.
375: * @return <code>true</code> if the specified button is pulsating,
376: * <code>false</code> otherwise.
377: */
378: public static boolean isPulsating(JButton jButton) {
379: // default button pulsates.
380: boolean isDefault = jButton.isDefaultButton();
381: if (isDefault) {
382: return true;
383: }
384:
385: // attention-drawing close button pulsates.
386: return isAttentionDrawingCloseButton(jButton);
387: }
388:
389: /**
390: * Stops all timers.
391: */
392: public static void stopAllTimers() {
393: for (PulseTracker tracker : trackers.values()) {
394: tracker.stopTimer();
395: }
396: }
397: }
|