1 | /**
|
---|
2 | * PopupManager.java
|
---|
3 | * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
|
---|
4 | *
|
---|
5 | * This program is free software: you can redistribute it and/or modify
|
---|
6 | * it under the terms of the GNU General Public License as published by
|
---|
7 | * the Free Software Foundation, either version 3 of the License, or
|
---|
8 | * (at your option) any later version.
|
---|
9 | *
|
---|
10 | * This program is distributed in the hope that it will be useful,
|
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
13 | * GNU General Public License for more details.
|
---|
14 | *
|
---|
15 | * You should have received a copy of the GNU General Public License
|
---|
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
---|
17 | */
|
---|
18 |
|
---|
19 | package org.expeditee.gui;
|
---|
20 |
|
---|
21 | import java.awt.BasicStroke;
|
---|
22 | import java.awt.Color;
|
---|
23 | import java.awt.Component;
|
---|
24 | import java.awt.Graphics;
|
---|
25 | import java.awt.Graphics2D;
|
---|
26 | import java.awt.Point;
|
---|
27 | import java.awt.Rectangle;
|
---|
28 | import java.awt.Stroke;
|
---|
29 | import java.awt.geom.Area;
|
---|
30 | import java.util.HashMap;
|
---|
31 | import java.util.HashSet;
|
---|
32 | import java.util.LinkedList;
|
---|
33 |
|
---|
34 | import javax.swing.JLayeredPane;
|
---|
35 | import javax.swing.SwingUtilities;
|
---|
36 |
|
---|
37 | /**
|
---|
38 | * A centralized container for all custom popups in expeditee.
|
---|
39 | *
|
---|
40 | * @author Brook Novak
|
---|
41 | */
|
---|
42 | public final class PopupManager implements DisplayIOObserver {
|
---|
43 |
|
---|
44 | /** Singleton */
|
---|
45 | private PopupManager() {}
|
---|
46 |
|
---|
47 | /** Singleton */
|
---|
48 | private static PopupManager _instance = new PopupManager();
|
---|
49 |
|
---|
50 | /**
|
---|
51 | * @return The singleton instance.
|
---|
52 | */
|
---|
53 | public static PopupManager getInstance() {
|
---|
54 | return _instance;
|
---|
55 | }
|
---|
56 |
|
---|
57 | // popup->invoker
|
---|
58 | private HashMap<Popup, Component> _popups = new HashMap<Popup, Component>();
|
---|
59 | // quick ref to invokers
|
---|
60 | private HashSet<Component> _invokers = new HashSet<Component>();
|
---|
61 |
|
---|
62 | private LinkedList<AnimatedPopup> _animatingPopups = new LinkedList<AnimatedPopup>();
|
---|
63 | private AnimationThread _animationThread = null;
|
---|
64 |
|
---|
65 | private final int ANIMATION_DURATION = 180; // Tume its takes for a maximize . minimize to animate. In ms.
|
---|
66 | private final int ANIMATION_RATE = 30; // in ms
|
---|
67 |
|
---|
68 | /**
|
---|
69 | * Determines whether a given point is over a popup.
|
---|
70 | *
|
---|
71 | * @param p
|
---|
72 | *
|
---|
73 | * @return True if p is over a popup
|
---|
74 | *
|
---|
75 | * @throws NullPointerException
|
---|
76 | * If p is null
|
---|
77 | */
|
---|
78 | public boolean isPointOverPopup(Point p) {
|
---|
79 | if (p == null) throw new NullPointerException("p");
|
---|
80 | for (Popup pp : _popups.keySet()) {
|
---|
81 | if (pp.getBounds().contains(p)) {
|
---|
82 | return true;
|
---|
83 | }
|
---|
84 | }
|
---|
85 |
|
---|
86 | return false;
|
---|
87 | }
|
---|
88 |
|
---|
89 | /**
|
---|
90 | * Tests a component to see if it is in invoker of an existing popup.
|
---|
91 | *
|
---|
92 | * @param c
|
---|
93 | * Must not be null.
|
---|
94 | *
|
---|
95 | * @return
|
---|
96 | * True if c is an invoker
|
---|
97 | *
|
---|
98 | * @throws NullPointerException
|
---|
99 | * If c is null
|
---|
100 | */
|
---|
101 | public boolean isInvoker(Component c) {
|
---|
102 | if (c == null) throw new NullPointerException("c");
|
---|
103 | return _invokers.contains(c);
|
---|
104 | }
|
---|
105 |
|
---|
106 | /**
|
---|
107 | * Gets an invoker for a popup
|
---|
108 | *
|
---|
109 | * @param p
|
---|
110 | * The popup to get the invoker for.
|
---|
111 | *
|
---|
112 | * @return
|
---|
113 | * The invoker for the given popup.
|
---|
114 | * Null if popup does not exist.
|
---|
115 | */
|
---|
116 | public Component getInvoker(Popup p) {
|
---|
117 | if (p == null) throw new NullPointerException("p");
|
---|
118 | return _popups.get(p);
|
---|
119 | }
|
---|
120 |
|
---|
121 | /**
|
---|
122 | * Use this instead of isVisible to determine if the popup is showing or not,
|
---|
123 | * since it considers animation.
|
---|
124 | *
|
---|
125 | * @return
|
---|
126 | * True if this popup is showing. <b>IMPORTANT:</b> This includes
|
---|
127 | * if the popup is not yet visible, but in an animation sequence for showing...
|
---|
128 | *
|
---|
129 | * @throws NullPointerException
|
---|
130 | * If p is null
|
---|
131 | */
|
---|
132 | public boolean isShowing(Popup p) {
|
---|
133 | if (p == null) throw new NullPointerException("p");
|
---|
134 | return _popups.containsKey(p);
|
---|
135 | }
|
---|
136 |
|
---|
137 | /**
|
---|
138 | * @return
|
---|
139 | * True if a poup is showing. False otherwise.
|
---|
140 | */
|
---|
141 | public boolean isAnyPopupsShowing () {
|
---|
142 | return !_popups.isEmpty();
|
---|
143 | }
|
---|
144 |
|
---|
145 | /**
|
---|
146 | * @return
|
---|
147 | * True if the mouse click event for going back a frame should be consumed
|
---|
148 | * Due to a popup requesting this event to be consumed currently showing.
|
---|
149 | */
|
---|
150 | public boolean shouldConsumeBackClick() {
|
---|
151 | for (Popup p : _popups.keySet()) {
|
---|
152 | if (p.shouldConsumeBackClick())
|
---|
153 | return true;
|
---|
154 | }
|
---|
155 |
|
---|
156 | return false;
|
---|
157 | }
|
---|
158 |
|
---|
159 | /**
|
---|
160 | * Clears all popups from the browser that are autohidden.
|
---|
161 | * Stops animations.
|
---|
162 | */
|
---|
163 | public void hideAutohidePopups() {
|
---|
164 |
|
---|
165 |
|
---|
166 | // Get rid of all animations that are not non-auto-hidden pops that are expanding
|
---|
167 | synchronized (_animatingPopups) {
|
---|
168 |
|
---|
169 | LinkedList<AnimatedPopup> animationsToClear = new LinkedList<AnimatedPopup>();
|
---|
170 |
|
---|
171 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
172 | if (!(ap.popup != null && !ap.popup.doesAutoHide())) {
|
---|
173 | animationsToClear.add(ap);
|
---|
174 | }
|
---|
175 | }
|
---|
176 |
|
---|
177 | _animatingPopups.removeAll(animationsToClear);
|
---|
178 |
|
---|
179 | }
|
---|
180 |
|
---|
181 | LinkedList<Popup> popupsToClear = new LinkedList<Popup>();
|
---|
182 | LinkedList<Component> invokersToClear = new LinkedList<Component>();
|
---|
183 |
|
---|
184 | // Get ride of the actual popups
|
---|
185 | for (Popup p : _popups.keySet()) {
|
---|
186 | if (p.doesAutoHide()) {
|
---|
187 |
|
---|
188 | popupsToClear.add(p);
|
---|
189 | invokersToClear.add(_popups.get(p));
|
---|
190 |
|
---|
191 | p.invalidateAppearance();
|
---|
192 | p.setVisible(false);
|
---|
193 | Browser._theBrowser.getLayeredPane().remove(p);
|
---|
194 | p.onHide();
|
---|
195 | }
|
---|
196 | }
|
---|
197 |
|
---|
198 | assert (popupsToClear.size() == invokersToClear.size());
|
---|
199 |
|
---|
200 | for (int i = 0; i < popupsToClear.size(); i++) {
|
---|
201 | _popups.remove(popupsToClear.get(i));
|
---|
202 | _invokers.remove(invokersToClear.get(i));
|
---|
203 | }
|
---|
204 |
|
---|
205 |
|
---|
206 |
|
---|
207 | }
|
---|
208 |
|
---|
209 |
|
---|
210 |
|
---|
211 | // public void hideAllPopups() {
|
---|
212 | //
|
---|
213 | // for (Popup p : _popups.keySet()) {
|
---|
214 | // invalidatePopup(p);
|
---|
215 | // p.setVisible(false);
|
---|
216 | // Browser._theBrowser.getLayeredPane().remove(p);
|
---|
217 | // p.onHide();
|
---|
218 | // }
|
---|
219 | // _popups.clear();
|
---|
220 | // _invokers.clear();
|
---|
221 | //
|
---|
222 | // // Get rid of all animations
|
---|
223 | // synchronized (_animatingPopups) {
|
---|
224 | // _animatingPopups.clear();
|
---|
225 | // }
|
---|
226 | // }
|
---|
227 |
|
---|
228 | public void frameChanged() {
|
---|
229 | // Remove any popups that are showing on the current frame
|
---|
230 | hideAutohidePopups();
|
---|
231 | }
|
---|
232 |
|
---|
233 | /**
|
---|
234 | * Hides a popup - if not already hidden.
|
---|
235 | *
|
---|
236 | * @param p
|
---|
237 | * Must not be null.
|
---|
238 | *
|
---|
239 | * @throws NullPointerException
|
---|
240 | * If p is null
|
---|
241 | */
|
---|
242 | public void hidePopup(Popup p) {
|
---|
243 | if (p == null) throw new NullPointerException("p");
|
---|
244 |
|
---|
245 | if (!isShowing(p) && (!p.isVisible() || p.getParent() == null)) return;
|
---|
246 |
|
---|
247 |
|
---|
248 | // Cancel any showing animations
|
---|
249 | synchronized (_animatingPopups) {
|
---|
250 | AnimatedPopup toRemove = null;
|
---|
251 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
252 | if (ap.popup == p) {
|
---|
253 | toRemove = ap;
|
---|
254 | break;
|
---|
255 | }
|
---|
256 | }
|
---|
257 | if (toRemove != null)
|
---|
258 | _animatingPopups.remove(toRemove);
|
---|
259 | }
|
---|
260 |
|
---|
261 | p.invalidateAppearance();
|
---|
262 | p.setVisible(false);
|
---|
263 | Browser._theBrowser.getLayeredPane().remove(p);
|
---|
264 | Component invoker = _popups.remove(p);
|
---|
265 | if (invoker != null) _invokers.remove(invoker);
|
---|
266 | p.onHide();
|
---|
267 |
|
---|
268 | }
|
---|
269 |
|
---|
270 | /**
|
---|
271 | * Hides a popup - with animation. - if not already hidden.
|
---|
272 | *
|
---|
273 | * @param p
|
---|
274 | * Must not be null.
|
---|
275 | * @param animator
|
---|
276 | * Must not be null.
|
---|
277 | *
|
---|
278 | * @throws NullPointerException
|
---|
279 | * If p or animator is null
|
---|
280 | *
|
---|
281 | */
|
---|
282 | public void hidePopup(Popup p, PopupAnimator animator) {
|
---|
283 |
|
---|
284 | if (p == null) throw new NullPointerException("p");
|
---|
285 | if (animator == null) throw new NullPointerException("animator");
|
---|
286 |
|
---|
287 | if (!isShowing(p) && (!p.isVisible() || p.getParent() == null)) return;
|
---|
288 |
|
---|
289 | hidePopup(p);
|
---|
290 | AnimatedPopup ap = new AnimatedPopup(
|
---|
291 | animator,
|
---|
292 | System.currentTimeMillis(),
|
---|
293 | null,
|
---|
294 | false,
|
---|
295 | p.getLocation());
|
---|
296 |
|
---|
297 | animator.starting(false, p.getBounds());
|
---|
298 |
|
---|
299 | synchronized (_animatingPopups) {
|
---|
300 | _animatingPopups.add(ap);
|
---|
301 | }
|
---|
302 |
|
---|
303 | if (_animationThread == null || !_animationThread.isAlive() || _animationThread.willDie) {
|
---|
304 | _animationThread = new AnimationThread();
|
---|
305 | _animationThread.start();
|
---|
306 | }
|
---|
307 | }
|
---|
308 |
|
---|
309 |
|
---|
310 |
|
---|
311 | /**
|
---|
312 | * Displays a popup at a specific location.
|
---|
313 | *
|
---|
314 | * @param p
|
---|
315 | * Must not be null.
|
---|
316 | *
|
---|
317 | * @param invoker
|
---|
318 | * The component responsible for showing the popup. can be null.
|
---|
319 | * Used such that when invoker pressed, the popup will not auto hide.
|
---|
320 | *
|
---|
321 | * @param loc
|
---|
322 | * Must not be null.
|
---|
323 | *
|
---|
324 | * @throws NullPointerException
|
---|
325 | * If p or loc is null
|
---|
326 | *
|
---|
327 | */
|
---|
328 | public void showPopup(Popup p, Point loc, Component invoker) {
|
---|
329 | if (p == null) throw new NullPointerException("p");
|
---|
330 | if (loc == null) throw new NullPointerException("animator");
|
---|
331 |
|
---|
332 |
|
---|
333 | if (_popups.containsKey(p)) return;
|
---|
334 |
|
---|
335 | p.prepareToPaint();
|
---|
336 | p.setLocation(loc);
|
---|
337 | p.setVisible(true);
|
---|
338 |
|
---|
339 | Browser._theBrowser.getLayeredPane().add(p, JLayeredPane.POPUP_LAYER, 0);
|
---|
340 |
|
---|
341 | _popups.put(p, invoker);
|
---|
342 | if (invoker != null) _invokers.add(invoker);
|
---|
343 |
|
---|
344 | p.onShowing();
|
---|
345 | p.onShow();
|
---|
346 |
|
---|
347 | // Invalidate the popup border
|
---|
348 | if (p.getBorderThickness() > 0.0f) {
|
---|
349 | p.invalidateAppearance();
|
---|
350 | }
|
---|
351 | }
|
---|
352 |
|
---|
353 | /**
|
---|
354 | * Displays a popup at a specific location - with animation.
|
---|
355 | *
|
---|
356 | * @param p
|
---|
357 | * Must not be null.
|
---|
358 | *
|
---|
359 | * @param invoker
|
---|
360 | * The component responsible for showing the popup. can be null.
|
---|
361 | * Used such that when invoker pressed, the popup will not auto hide.
|
---|
362 | *
|
---|
363 | * @param loc
|
---|
364 | * Must not be null.
|
---|
365 | *
|
---|
366 | * @param animator
|
---|
367 | * Must not be null.
|
---|
368 | *
|
---|
369 | * @throws NullPointerException
|
---|
370 | * If p, animator or loc is null
|
---|
371 | */
|
---|
372 | public void showPopup(Popup p, Point loc, Component invoker, PopupAnimator animator) {
|
---|
373 | if (animator == null)
|
---|
374 | throw new NullPointerException("animator");
|
---|
375 | if (p == null)
|
---|
376 | throw new NullPointerException("p");
|
---|
377 | if (loc == null)
|
---|
378 | throw new NullPointerException("loc");
|
---|
379 |
|
---|
380 | if (_popups.containsKey(p)) return;
|
---|
381 |
|
---|
382 | _popups.put(p, invoker);
|
---|
383 | if (invoker != null) _invokers.add(invoker);
|
---|
384 |
|
---|
385 |
|
---|
386 | AnimatedPopup ap = new AnimatedPopup(
|
---|
387 | animator,
|
---|
388 | System.currentTimeMillis(),
|
---|
389 | p,
|
---|
390 | true,
|
---|
391 | loc);
|
---|
392 |
|
---|
393 |
|
---|
394 | animator.starting(true, new Rectangle(loc.x, loc.y, p.getWidth(), p.getHeight()));
|
---|
395 |
|
---|
396 | p.onShowing();
|
---|
397 |
|
---|
398 | synchronized (_animatingPopups) {
|
---|
399 | _animatingPopups.add(ap);
|
---|
400 | }
|
---|
401 |
|
---|
402 | if (_animationThread == null || !_animationThread.isAlive() || _animationThread.willDie) {
|
---|
403 | _animationThread = new AnimationThread();
|
---|
404 | _animationThread.start();
|
---|
405 | }
|
---|
406 |
|
---|
407 | }
|
---|
408 |
|
---|
409 | /**
|
---|
410 | * Does a pure asynch animation with no popups involved.
|
---|
411 | *
|
---|
412 | * For example you may want to have an effect such that an item is expanding
|
---|
413 | * or moving into a new location on the screen.
|
---|
414 | *
|
---|
415 | * @param animator
|
---|
416 | * Must not be null.
|
---|
417 | *
|
---|
418 | * @param target
|
---|
419 | * Must not be null.
|
---|
420 | *
|
---|
421 | * @throws NullPointerException
|
---|
422 | * If animator or target is null
|
---|
423 | *
|
---|
424 | */
|
---|
425 | public void doPureAnimation(PopupAnimator animator, Rectangle target) {
|
---|
426 |
|
---|
427 | if (animator == null)
|
---|
428 | throw new NullPointerException("animator");
|
---|
429 | if (target == null)
|
---|
430 | throw new NullPointerException("target");
|
---|
431 |
|
---|
432 | AnimatedPopup ap = new AnimatedPopup(
|
---|
433 | animator,
|
---|
434 | System.currentTimeMillis(),
|
---|
435 | null,
|
---|
436 | false,
|
---|
437 | target.getLocation());
|
---|
438 |
|
---|
439 |
|
---|
440 | animator.starting(true, target);
|
---|
441 |
|
---|
442 | synchronized (_animatingPopups) {
|
---|
443 | _animatingPopups.add(ap);
|
---|
444 | }
|
---|
445 |
|
---|
446 | if (_animationThread == null || !_animationThread.isAlive() || _animationThread.willDie) {
|
---|
447 | _animationThread = new AnimationThread();
|
---|
448 | _animationThread.start();
|
---|
449 | }
|
---|
450 |
|
---|
451 | }
|
---|
452 |
|
---|
453 |
|
---|
454 | /**
|
---|
455 | * Paints all popups in the browsers popup pane with the given graphics.
|
---|
456 | *
|
---|
457 | * @param g
|
---|
458 | * Where to paint to.
|
---|
459 | *
|
---|
460 | * @param clip
|
---|
461 | */
|
---|
462 | void paintLayeredPane(Graphics g, Area clip) {
|
---|
463 | if (Browser._theBrowser == null) return;
|
---|
464 |
|
---|
465 | Component[] compsOnPopup = Browser._theBrowser.getLayeredPane().getComponentsInLayer(JLayeredPane.POPUP_LAYER);
|
---|
466 |
|
---|
467 | for (int i = 0; i < compsOnPopup.length; i++) {
|
---|
468 | Component c = compsOnPopup[i];
|
---|
469 |
|
---|
470 | Point p = c.getLocation();
|
---|
471 |
|
---|
472 | if (clip == null || clip.intersects(c.getBounds())) {
|
---|
473 | g.translate(p.x, p.y);
|
---|
474 | c.paint(g);
|
---|
475 | g.translate(-p.x, -p.y);
|
---|
476 | }
|
---|
477 |
|
---|
478 | }
|
---|
479 | }
|
---|
480 |
|
---|
481 | /**
|
---|
482 | * Paints current popup animations to the expeditee browser content pane.
|
---|
483 | */
|
---|
484 | void paintAnimations() {
|
---|
485 |
|
---|
486 | if (Browser._theBrowser == null
|
---|
487 | || Browser._theBrowser.g == null) return;
|
---|
488 |
|
---|
489 | Graphics g = Browser._theBrowser.g;
|
---|
490 |
|
---|
491 | synchronized (_animatingPopups) {
|
---|
492 |
|
---|
493 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
494 | ap.animator.paint(g);
|
---|
495 | }
|
---|
496 |
|
---|
497 | }
|
---|
498 | }
|
---|
499 |
|
---|
500 | /**
|
---|
501 | * Proccesses animation on a dedicated thread.
|
---|
502 | *
|
---|
503 | * @author Brook Novak
|
---|
504 | *
|
---|
505 | */
|
---|
506 | private class AnimationThread extends Thread {
|
---|
507 |
|
---|
508 | private boolean willDie = false;
|
---|
509 |
|
---|
510 | @Override
|
---|
511 | public void run() {
|
---|
512 |
|
---|
513 |
|
---|
514 | LinkedList<AnimatedPopup> finishedAnimations;
|
---|
515 |
|
---|
516 | while (true) {
|
---|
517 |
|
---|
518 | // Perform animation logic
|
---|
519 | finishedAnimations = animate();
|
---|
520 |
|
---|
521 | // Check if finished all animations
|
---|
522 | if (finishedAnimations == null) return; // done
|
---|
523 |
|
---|
524 | // Check for finalization of animation. That is, adding the popups to the layered pane
|
---|
525 | boolean needsFinalization = false;
|
---|
526 | for (AnimatedPopup ap : finishedAnimations) {
|
---|
527 | if (ap.isShowing) {
|
---|
528 | needsFinalization = true;
|
---|
529 | break;
|
---|
530 | }
|
---|
531 |
|
---|
532 | // FInal invalidation
|
---|
533 | FrameGraphics.invalidateArea(ap.animator.getCurrentDrawingArea());
|
---|
534 |
|
---|
535 | }
|
---|
536 |
|
---|
537 | if (needsFinalization) {
|
---|
538 | SwingUtilities.invokeLater(new AnimationFinalizor(finishedAnimations));
|
---|
539 | // Will repaint when a popup becomes anchored...
|
---|
540 | } else {
|
---|
541 | FrameGraphics.requestRefresh(true);
|
---|
542 | }
|
---|
543 |
|
---|
544 | // Limit animation rate
|
---|
545 | try {
|
---|
546 | sleep(ANIMATION_RATE);
|
---|
547 | } catch (InterruptedException e) {
|
---|
548 | e.printStackTrace();
|
---|
549 | }
|
---|
550 |
|
---|
551 | }
|
---|
552 |
|
---|
553 | }
|
---|
554 |
|
---|
555 | /**
|
---|
556 | * Performs animations
|
---|
557 | * @return
|
---|
558 | */
|
---|
559 | private LinkedList<AnimatedPopup> animate() {
|
---|
560 |
|
---|
561 | LinkedList<AnimatedPopup> finishedPopups = null;
|
---|
562 |
|
---|
563 | synchronized (_animatingPopups) {
|
---|
564 |
|
---|
565 | if (_animatingPopups.isEmpty()) {
|
---|
566 | willDie = true;
|
---|
567 | return null;
|
---|
568 | }
|
---|
569 |
|
---|
570 | long currentTime = System.currentTimeMillis();
|
---|
571 |
|
---|
572 | finishedPopups = new LinkedList<AnimatedPopup>();
|
---|
573 |
|
---|
574 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
575 |
|
---|
576 | long duration = currentTime - ap.startTime;
|
---|
577 |
|
---|
578 | if (duration >= ANIMATION_DURATION) { // check if complete
|
---|
579 |
|
---|
580 | finishedPopups.add(ap);
|
---|
581 |
|
---|
582 | } else {
|
---|
583 |
|
---|
584 | float percent = ((float)duration / (float)ANIMATION_DURATION);
|
---|
585 | assert (percent >= 0.0f);
|
---|
586 | assert (percent < 1.0f);
|
---|
587 |
|
---|
588 | Rectangle dirty = ap.animator.update(percent);
|
---|
589 | if (dirty != null)
|
---|
590 | FrameGraphics.invalidateArea(dirty);
|
---|
591 |
|
---|
592 | }
|
---|
593 |
|
---|
594 | }
|
---|
595 |
|
---|
596 | _animatingPopups.removeAll(finishedPopups);
|
---|
597 |
|
---|
598 | }
|
---|
599 |
|
---|
600 | return finishedPopups;
|
---|
601 |
|
---|
602 | }
|
---|
603 |
|
---|
604 | /**
|
---|
605 | * Adds popups to layered pane
|
---|
606 | * @author Brook Novak
|
---|
607 | *
|
---|
608 | */
|
---|
609 | private class AnimationFinalizor implements Runnable {
|
---|
610 |
|
---|
611 | private LinkedList<AnimatedPopup> finished;
|
---|
612 |
|
---|
613 | AnimationFinalizor(LinkedList<AnimatedPopup> finished) {
|
---|
614 | this.finished = finished;
|
---|
615 | }
|
---|
616 |
|
---|
617 | public void run() {
|
---|
618 |
|
---|
619 | for (AnimatedPopup ap : finished) {
|
---|
620 |
|
---|
621 | if (ap.isShowing && _popups.containsKey(ap.popup)) {
|
---|
622 |
|
---|
623 | ap.popup.prepareToPaint();
|
---|
624 | ap.popup.setLocation(ap.popupLocation);
|
---|
625 | ap.popup.setVisible(true);
|
---|
626 |
|
---|
627 | Browser._theBrowser.getLayeredPane().add(ap.popup, JLayeredPane.POPUP_LAYER, 0);
|
---|
628 |
|
---|
629 | ap.popup.onShow();
|
---|
630 |
|
---|
631 | // Invalidate the popup border
|
---|
632 | if (ap.popup.getBorderThickness() > 0.0f) {
|
---|
633 | ap.popup.invalidateAppearance();
|
---|
634 | }
|
---|
635 | }
|
---|
636 |
|
---|
637 | }
|
---|
638 |
|
---|
639 | }
|
---|
640 | }
|
---|
641 | }
|
---|
642 |
|
---|
643 | private class AnimatedPopup {
|
---|
644 |
|
---|
645 | PopupAnimator animator;
|
---|
646 | long startTime;
|
---|
647 | Popup popup = null;
|
---|
648 | boolean isShowing;
|
---|
649 | Point popupLocation;
|
---|
650 |
|
---|
651 | public AnimatedPopup(PopupAnimator animator, long startTime, Popup popup,
|
---|
652 | boolean isShowing, Point popupLocation) {
|
---|
653 |
|
---|
654 | assert(animator != null);
|
---|
655 | assert(popupLocation != null);
|
---|
656 |
|
---|
657 | // Only have popup if showing
|
---|
658 | assert (!isShowing && popup == null || (isShowing && popup != null));
|
---|
659 |
|
---|
660 |
|
---|
661 | this.animator = animator;
|
---|
662 | this.startTime = startTime;
|
---|
663 | this.popup = popup;
|
---|
664 | this.isShowing = isShowing;
|
---|
665 | this.popupLocation = popupLocation;
|
---|
666 |
|
---|
667 | }
|
---|
668 |
|
---|
669 | }
|
---|
670 |
|
---|
671 | /**
|
---|
672 | * Provides animations for a popup when hiding or when showing.
|
---|
673 | *
|
---|
674 | * Note that {@link PopupAnimator#paint(Graphics)} and {@link PopupAnimator#update(float)}
|
---|
675 | * will always be invoked at seperate times - so do not have to worry about thread-saefty.
|
---|
676 | *
|
---|
677 | * These should only be used once... one per popup at a time.
|
---|
678 | *
|
---|
679 | * @author Brook Novak
|
---|
680 | *
|
---|
681 | */
|
---|
682 | public interface PopupAnimator {
|
---|
683 |
|
---|
684 | /**
|
---|
685 | * Invoked before showing. Any prepaations are done here.
|
---|
686 | *
|
---|
687 | * @param isShowing
|
---|
688 | * True if this animation will be for a popup that is showing.
|
---|
689 | * False if this animation will be for a popup that is hiding.
|
---|
690 | *
|
---|
691 | * @param popupBounds
|
---|
692 | * The location of the popup. I.E. where it is, or where it will be.
|
---|
693 | *
|
---|
694 | */
|
---|
695 | void starting(boolean isShowing, Rectangle popupBounds);
|
---|
696 |
|
---|
697 | /**
|
---|
698 | *
|
---|
699 | * Called on an animation thread.
|
---|
700 | *
|
---|
701 | * @param percent
|
---|
702 | * The percent complete of the animations.
|
---|
703 | * Rangles from 0 to 1.
|
---|
704 | *
|
---|
705 | * @return dirty area that needs painting for last update... Null for no invalidation
|
---|
706 | *
|
---|
707 | *
|
---|
708 | */
|
---|
709 | Rectangle update(float percent);
|
---|
710 |
|
---|
711 | /**
|
---|
712 | * Paints the animation - on the swing thread.
|
---|
713 | * Note that this is always on the content pane - not the expeditee frame buffer.
|
---|
714 | *
|
---|
715 | * @param g
|
---|
716 | *
|
---|
717 | */
|
---|
718 | void paint(Graphics g);
|
---|
719 |
|
---|
720 |
|
---|
721 | /**
|
---|
722 | *
|
---|
723 | * @return
|
---|
724 | * The area which the animation is drawn. Used for final invalidation. Null for no final invaliation
|
---|
725 | */
|
---|
726 | Rectangle getCurrentDrawingArea();
|
---|
727 |
|
---|
728 | }
|
---|
729 |
|
---|
730 |
|
---|
731 | public class ExpandShrinkAnimator implements PopupAnimator {
|
---|
732 |
|
---|
733 | private boolean isShowing;
|
---|
734 | private Rectangle popupBounds;
|
---|
735 | private Rectangle sourceRectangle;
|
---|
736 | private Rectangle currentRectangle;
|
---|
737 | private Color fillColor;
|
---|
738 |
|
---|
739 | private final Stroke stroke = new BasicStroke(2.0f);
|
---|
740 |
|
---|
741 | /**
|
---|
742 | *
|
---|
743 | * @param sourceRectangle
|
---|
744 | * @param fillColor
|
---|
745 | * The fill color of the animated rectangle. Null for no fill.
|
---|
746 | */
|
---|
747 | public ExpandShrinkAnimator(Rectangle sourceRectangle, Color fillColor) {
|
---|
748 | if (sourceRectangle == null) throw new NullPointerException("sourceRectangle");
|
---|
749 |
|
---|
750 | this.fillColor = fillColor;
|
---|
751 | this.sourceRectangle = (Rectangle)sourceRectangle.clone();
|
---|
752 | this.currentRectangle = (Rectangle)sourceRectangle.clone();
|
---|
753 | }
|
---|
754 |
|
---|
755 | public void paint(Graphics g) {
|
---|
756 |
|
---|
757 | if (fillColor != null) {
|
---|
758 | g.setColor(fillColor);
|
---|
759 | g.fillRect(currentRectangle.x, currentRectangle.y, currentRectangle.width, currentRectangle.height);
|
---|
760 | }
|
---|
761 |
|
---|
762 | g.setColor(Color.BLACK);
|
---|
763 | ((Graphics2D)g).setStroke(stroke);
|
---|
764 | g.drawRect(currentRectangle.x, currentRectangle.y, currentRectangle.width, currentRectangle.height);
|
---|
765 | }
|
---|
766 |
|
---|
767 | public void starting(boolean isShowing, Rectangle popupBounds) {
|
---|
768 | this.isShowing = isShowing;
|
---|
769 | this.popupBounds = popupBounds;
|
---|
770 |
|
---|
771 | if (isShowing) {
|
---|
772 | this.currentRectangle = (Rectangle)sourceRectangle.clone();
|
---|
773 | } else {
|
---|
774 | this.currentRectangle = (Rectangle)sourceRectangle.clone();
|
---|
775 | }
|
---|
776 |
|
---|
777 | }
|
---|
778 |
|
---|
779 | public Rectangle update(float percent) {
|
---|
780 |
|
---|
781 | Rectangle oldBounds = currentRectangle;
|
---|
782 |
|
---|
783 | if (!isShowing) { // if minimizing just reverse percent
|
---|
784 | percent = 1 - percent;
|
---|
785 | }
|
---|
786 |
|
---|
787 | // update X
|
---|
788 | currentRectangle.x = sourceRectangle.x +
|
---|
789 | (int)((popupBounds.x - sourceRectangle.x) * percent);
|
---|
790 |
|
---|
791 | // update Y
|
---|
792 | currentRectangle.y = sourceRectangle.y +
|
---|
793 | (int)((popupBounds.y - sourceRectangle.y) * percent);
|
---|
794 |
|
---|
795 | // update width
|
---|
796 | currentRectangle.width = sourceRectangle.width +
|
---|
797 | (int)((popupBounds.width - sourceRectangle.width) * percent);
|
---|
798 |
|
---|
799 | // update height
|
---|
800 | currentRectangle.height = sourceRectangle.height +
|
---|
801 | (int)((popupBounds.height - sourceRectangle.height) * percent);
|
---|
802 |
|
---|
803 | int x = Math.min(oldBounds.x, currentRectangle.x);
|
---|
804 | int y = Math.min(oldBounds.y, currentRectangle.y);
|
---|
805 | int width = Math.min(oldBounds.x + oldBounds.width, currentRectangle.x + currentRectangle.width) - x;
|
---|
806 | int height = Math.min(oldBounds.y + oldBounds.height, currentRectangle.y + currentRectangle.height) - y;
|
---|
807 |
|
---|
808 | return new Rectangle(x, y, width, height);
|
---|
809 |
|
---|
810 | }
|
---|
811 |
|
---|
812 | public Rectangle getCurrentDrawingArea() {
|
---|
813 | return currentRectangle;
|
---|
814 | }
|
---|
815 |
|
---|
816 |
|
---|
817 | }
|
---|
818 |
|
---|
819 |
|
---|
820 | }
|
---|