source: trunk/src/org/apollo/util/PopupReaper.java@ 1102

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

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