source: trunk/src_apollo/org/apollo/util/PopupReaper.java@ 315

Last change on this file since 315 was 315, checked in by bjn8, 16 years ago

Apollo spin-off added

File size: 8.0 KB
Line 
1package org.apollo.util;
2
3import java.awt.Component;
4import java.awt.Point;
5import java.awt.Rectangle;
6import java.awt.event.MouseEvent;
7import java.lang.reflect.InvocationTargetException;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.HashMap;
11import java.util.Map;
12
13import javax.swing.SwingUtilities;
14
15import org.expeditee.gui.MouseEventRouter;
16import org.expeditee.gui.Popup;
17import org.expeditee.gui.PopupManager;
18import org.expeditee.gui.PopupManager.PopupAnimator;
19
20/**
21 * Yet another daemon .. whos job is to hide popups after their lifetimes have passed
22 *
23 * @author Brook Novak
24 */
25
26public class PopupReaper {
27
28 private Map<Popup, TemporaryPopup> tempPopups = new HashMap<Popup, TemporaryPopup>();// SHARED RESOURCE - the locker
29
30 private ReapDaemon reaper = null;
31
32 private static PopupReaper instance = new PopupReaper();
33 private PopupReaper() {
34 }
35 public static PopupReaper getInstance() {
36 return instance;
37 }
38
39 /**
40 * Initializes a popups lifetime so that the popup will eventually hide at a given time period.
41 *
42 * @param p
43 * The popup to hide in <code>lifetime</code> ms. Must not be null.
44 *
45 * @param hideAnimation
46 * The animation to render when the popup hides when its lifetime has passed. Null for
47 * no animation.
48 *
49 * @param lifetime
50 * The new time for the given popup to live for.
51 * Must be larger or equal to zero. In milliseconds.
52 *
53 * @return
54 * True if the given popups lifetime has been re-initialized. False if the popups
55 * lifetime was either never initialized or has already been hidden or is in the proccesses of hiding.
56 *
57 * @throws NullPointerException
58 * If p is null.
59 *
60 * @throws IllegalArgumentException
61 * If lifetime is negative.
62 *
63 */
64 public void initPopupLifetime(Popup p, PopupAnimator hideAnimation, int lifetime) {
65 if (p == null) throw new NullPointerException("p");
66 if (lifetime < 0) throw new IllegalArgumentException("lifetime < 0");
67
68 // Init daemon
69 if (reaper == null) {
70 reaper = new ReapDaemon();
71 reaper.start();
72 }
73
74 synchronized(tempPopups) { // locks _tempPopups and _tempPopupQueue by convention
75
76 TemporaryPopup tp = tempPopups.get(p);
77
78 if (tp != null) {
79
80 tp.setNewLifetime(lifetime);
81
82 } else {
83 tp = new TemporaryPopup(p, hideAnimation, lifetime);
84 tempPopups.put(p, tp);
85
86 // Notify reaper of new popup to reap .. eventually. It will set its new wait time
87 reaper.interrupt();
88 }
89
90
91
92 }
93
94 }
95
96 /**
97 * Resets a popups lifetime to a new value.
98 *
99 * @param p
100 * THe popup to hide in <code>lifetime</code> ms. Must not be null.
101 *
102 * @param lifetime
103 * The new time for the given popup to live for.
104 * Must be larger or equal to zero. In milliseconds.
105 *
106 * @return
107 * True if the given popups lifetime has been re-initialized. False if the popups
108 * lifetime was either never initialized or has already been hidden or is in the proccesses of hiding.
109 *
110 * @throws NullPointerException
111 * If p is null.
112 *
113 * @throws IllegalArgumentException
114 * If lifetime is negative.
115 *
116 */
117 public boolean revivePopup(Popup p, int lifetime) {
118 if (p == null) throw new NullPointerException("p");
119 if (lifetime < 0) throw new IllegalArgumentException("lifetime < 0");
120
121 synchronized(tempPopups) { // locks _tempPopups and _tempPopupQueue by convention
122
123 TemporaryPopup tp = tempPopups.get(p);
124
125 if (tp != null) {
126 tp.setNewLifetime(lifetime);
127 return true;
128 }
129
130 }
131
132 return false;
133 }
134
135
136 private class ReapDaemon extends Thread {
137
138 ReapDaemon() {
139 super("Popup Reaper");
140 super.setPriority(Thread.MIN_PRIORITY);
141 super.setDaemon(true);
142 }
143
144 public void run() {
145
146 while (true) {
147
148 long waitTime = reap();
149
150 try {
151 if (waitTime < 0) {
152 sleep(10000); // some arbitary wait
153 } else if (waitTime > 0) {
154 sleep(waitTime);
155 }
156 } catch (InterruptedException e) {
157 }
158
159 } // keep reaping forever
160
161 }
162
163 /**
164 *
165 * @return
166 * A Positive wait time, or a negative for nothing to to wait on.
167 */
168 private long reap() {
169
170 Collection<TemporaryPopup> snapshot;
171
172 // Grab a temp popup
173 synchronized(tempPopups) {
174 snapshot = new ArrayList<TemporaryPopup>(tempPopups.values());
175 }
176
177 long nextDelay = -1;
178
179 for (TemporaryPopup tp : snapshot) {
180
181 long del = tp.getDelay();
182 if (del <= 0) { // this popups lifetime is up .. maybe - if the mouse is not over it
183
184 DoHide hider = new DoHide(tp);
185
186 try {
187 SwingUtilities.invokeAndWait(hider);
188 } catch (InterruptedException e) {
189 return 0; // retry straight away
190 } catch (InvocationTargetException e) {
191 e.printStackTrace();
192 }
193
194 if (hider.didHide) {
195
196 synchronized(tempPopups) {
197 tempPopups.remove(tp.popup);
198 }
199
200 } else { // did not hide because mouse cursor is over pop / invoker
201 tp.resetLifetime();
202 del = tp.getDelay();
203 }
204
205 }
206
207 if (nextDelay < 0 || (del >= 0 && del < nextDelay)) {
208 // Smallest delay time
209 nextDelay = del;
210 }
211
212 }
213
214 return nextDelay;
215
216 }
217
218 }
219
220 /**
221 * Hides a temp popup on the swing thread.
222 * {@link #didHide} is set to true on hide, false if did not hide because mouse cursor
223 * was over the popup / popup invoker.
224 *
225 * @author Brook Novak
226 *
227 */
228 private class DoHide implements Runnable {
229
230 private TemporaryPopup tempPopup;
231 public boolean didHide = false;
232
233 public DoHide(TemporaryPopup tempPopup) {
234 this.tempPopup = tempPopup;
235 }
236
237 /**
238 *
239 * @param c
240 *
241 * @param screenPoint
242 *
243 * @return
244 * True if screenPoint is over c.
245 */
246 private boolean isPointInComponant(Component c, Point screenPoint) {
247 assert(c != null);
248 assert(screenPoint != null);
249
250 Point compPositoinOnScreen = new Point(0,0);
251 SwingUtilities.convertPointToScreen(compPositoinOnScreen, c);
252 Rectangle bounds = c.getBounds();
253 bounds.x = compPositoinOnScreen.x;
254 bounds.y = compPositoinOnScreen.y;
255
256 return bounds.contains(screenPoint);
257 }
258
259 public void run() {
260 assert(tempPopup != null);
261
262 MouseEvent me = MouseEventRouter.getCurrentMouseEvent();
263 if (me != null) {
264
265 if (isPointInComponant(tempPopup.popup, me.getLocationOnScreen()))
266 return;
267
268 Component invoker = PopupManager.getInstance().getInvoker(tempPopup.popup);
269 if (invoker != null && isPointInComponant(invoker, me.getLocationOnScreen()))
270 return;
271
272 }
273
274 if (tempPopup.hideAnim != null) {
275 PopupManager.getInstance().hidePopup(tempPopup.popup, tempPopup.hideAnim);
276 } else {
277 PopupManager.getInstance().hidePopup(tempPopup.popup);
278 }
279
280 didHide = true;
281
282 }
283 }
284
285 /**
286 * Represents a popup that will eventually hide by the reaper.
287 *
288 * @author Brook Novak
289 *
290 */
291 private class TemporaryPopup implements Comparable {
292
293 private Popup popup;
294 private PopupAnimator hideAnim;
295 private long destructionTime;
296 private int lifetime;
297
298 /**
299 * Constructor.
300 *
301 * @param popup
302 * The encapsulated popup - must not be null.
303 *
304 * @param anim
305 * Null for no anim...
306 *
307 * @param lifetime
308 * The time from now to which the popup should hide. In MS
309 *
310 *
311 */
312 public TemporaryPopup(Popup popup, PopupAnimator anim, int lifetime) {
313 assert(popup != null);
314 assert(lifetime >= 0);
315
316 this.popup = popup;
317 this.hideAnim = anim;
318
319 setNewLifetime(lifetime);
320 }
321
322 public void setNewLifetime(int lifetime) {
323 assert(lifetime >= 0);
324 this.lifetime = lifetime;
325 this.destructionTime = System.currentTimeMillis() + lifetime;
326 }
327
328 public void resetLifetime() {
329 setNewLifetime(lifetime);
330 }
331
332 public long getDelay() {
333 return destructionTime - System.currentTimeMillis();
334 }
335
336 /**
337 * {@inheritDoc}
338 */
339 public int compareTo(Object o) {
340
341 long dt2 = ((TemporaryPopup)o).destructionTime;
342
343 if (destructionTime < dt2)
344 return -1;
345
346 if (destructionTime > dt2)
347 return 1;
348
349 return 0;
350 }
351
352
353 }
354}
Note: See TracBrowser for help on using the repository browser.