source: trunk/src/org/expeditee/gui/FrameGraphics.java@ 1064

Last change on this file since 1064 was 1064, checked in by davidb, 8 years ago

Addition of new type of frame transition, with off-screen buffering to make use of alpha-values in transition more seamless; Enhancement of display-mode to be full screen; Enahcements to mouse interactions over images -- now if the mouse is over a pixel in the image that is the same colour as the background-color of the frame, it is treated as equivalent to being over the background, rather than the image item. Overall has a natural feel. You now need to click on 'non-trival' parts of an image to get image-related interactive Expteditee behaviour. (note: this is how text already behaves)

File size: 35.7 KB
Line 
1/**
2 * FrameGraphics.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
19package org.expeditee.gui;
20
21import java.awt.Color;
22import java.awt.Dimension;
23import java.awt.EventQueue;
24import java.awt.Graphics;
25import java.awt.Graphics2D;
26import java.awt.GraphicsDevice;
27import java.awt.GraphicsEnvironment;
28import java.awt.Image;
29import java.awt.Polygon;
30import java.awt.Rectangle;
31import java.awt.RenderingHints;
32import java.awt.geom.Area;
33import java.lang.reflect.Method;
34import java.awt.image.BufferedImage;
35import java.util.Collection;
36import java.util.Collections;
37import java.util.Comparator;
38import java.util.HashSet;
39import java.util.LinkedList;
40import java.util.List;
41import java.util.ListIterator;
42
43import org.expeditee.items.Circle;
44import org.expeditee.items.Dot;
45import org.expeditee.items.Item;
46import org.expeditee.items.ItemUtils;
47import org.expeditee.items.Line;
48import org.expeditee.items.UserAppliedPermission;
49import org.expeditee.items.XRayable;
50import org.expeditee.items.Item.HighlightMode;
51import org.expeditee.items.widgets.InteractiveWidget;
52import org.expeditee.items.widgets.WidgetEdge;
53import org.expeditee.settings.UserSettings;
54
55public class FrameGraphics {
56
57 // the graphics used to paint with
58 private static Graphics2D _DisplayGraphics;
59
60 // the maximum size that can be used to paint on
61 private static Dimension _MaxSize = null;
62
63 // Final passes to rendering the current frame
64 private static LinkedList<FrameRenderPass> _frameRenderPasses = new LinkedList<FrameRenderPass>();
65
66 // modes
67 protected static final int MODE_NORMAL = 0;
68
69 protected static final int MODE_AUDIENCE = 1;
70
71 protected static final int MODE_XRAY = 2;
72
73 protected static final int MODE_AUDIENCE_FULLSCREEN = 4;
74
75
76 // The following used to be true in the past:
77 // Start in XRay mode so that errors aren't thrown when parsing the profile
78 // frame if it has images on it
79 // More recently, so Exploratory Search starts up correctly with images
80 // rendered (rather than their @i form) the following assignment was changed
81 // to MODE_NORMAL. No issue of parse errors has not been seen
82 private static int _Mode = MODE_NORMAL;
83
84 private FrameGraphics() {
85 // util constructor
86 }
87
88 /**
89 * If Audience Mode is on this method will toggle it to be off, or
90 * vice-versa. This results in the Frame being re-parsed and repainted.
91 */
92 public static void ToggleAudienceMode() {
93 Frame current = DisplayIO.getCurrentFrame();
94 if (_Mode == MODE_XRAY) {
95 ToggleXRayMode();
96 }
97
98
99 if (_Mode == MODE_AUDIENCE_FULLSCREEN) {
100 _Mode = MODE_NORMAL;
101 DisplayIO.leaveFullScreenMode();
102 }
103 else if (_Mode == MODE_AUDIENCE) {
104 if (DisplayIO.fullScreenCapable()) {
105 // Don't need to worry about UpdateConnectedToAnnotations, as this
106 // will have already been taken care of going to the 'first stage' of
107 // audience mode (MODE_AUDIENCE)
108 _Mode = MODE_AUDIENCE_FULLSCREEN;
109 DisplayIO.enterFullScreenMode();
110 }
111 else {
112 // return to normal mode
113 _Mode = MODE_NORMAL;
114 }
115 }
116 else { // must currently be in regular mode => move to audience mode
117 _Mode = MODE_AUDIENCE;
118 ItemUtils.UpdateConnectedToAnnotations(current.getItems());
119 for (Overlay o : current.getOverlays()) {
120 ItemUtils.UpdateConnectedToAnnotations(o.Frame.getItems());
121 }
122 for (Vector v : current.getVectorsDeep()) {
123 ItemUtils.UpdateConnectedToAnnotations(v.Frame.getItems());
124 }
125 }
126 FrameUtils.Parse(current);
127 DisplayIO.UpdateTitle();
128 setMaxSize(new Dimension(_MaxSize.width, MessageBay
129 .getMessageBufferHeight()
130 + _MaxSize.height));
131 refresh(false);
132 }
133
134 /**
135 * If X-Ray Mode is on this method will toggle it to be off, or vice-versa.
136 * This results in the Frame being re-parsed and repainted.
137 */
138 public static void ToggleXRayMode() {
139 if (_Mode == MODE_AUDIENCE) {
140 ToggleAudienceMode();
141 }
142
143 if (_Mode == MODE_XRAY) {
144 setMode(MODE_NORMAL, true);
145 } else {
146 setMode(MODE_XRAY, true);
147 }
148 DisplayIO.getCurrentFrame().refreshSize();
149 DisplayIO.UpdateTitle();
150 FrameMouseActions.getInstance().refreshHighlights();
151 FrameMouseActions.updateCursor();
152 refresh(false);
153 }
154
155 public static void setMode(int mode, boolean parse) {
156 if (_Mode == mode)
157 return;
158 _Mode = mode;
159 if (parse) {
160 Frame current = DisplayIO.getCurrentFrame();
161 current.parse();
162 }
163 }
164
165 public static void forceXRayMode(boolean parse) {
166 setMode(MODE_XRAY,parse);
167 }
168
169 /**
170 * @return True if Audience Mode is currently on, False otherwise.
171 */
172 public static boolean isAudienceMode() {
173 return (_Mode == MODE_AUDIENCE) || (_Mode == MODE_AUDIENCE_FULLSCREEN);
174 }
175
176 /**
177 * @return True if X-Ray Mode is currently on, False otherwise.
178 */
179 public static boolean isXRayMode() {
180 return _Mode == MODE_XRAY;
181 }
182
183 public static void setMaxSize(Dimension max) {
184 if (_MaxSize == null)
185 _MaxSize = max;
186
187 // Hide the message buffer if in audience mode
188 int newMaxHeight = max.height
189 - (isAudienceMode() ? 0 : MessageBay.MESSAGE_BUFFER_HEIGHT);
190 if (newMaxHeight > 0) {
191 _MaxSize.setSize(max.width, newMaxHeight);
192 }
193 Frame current = DisplayIO.getCurrentFrame();
194 if (current != null) {
195 current.setBuffer(null);
196 current.refreshSize();
197 if (DisplayIO.isTwinFramesOn()) {
198 Frame opposite = DisplayIO.getOppositeFrame();
199
200 /* When running the test suite opposite may be null! */
201 if (opposite != null) {
202 opposite.setBuffer(null);
203 opposite.refreshSize();
204 }
205 }
206 }
207
208 if (newMaxHeight > 0) {
209 MessageBay.updateSize();
210 }
211 }
212
213 public static Dimension getMaxSize() {
214 return _MaxSize;
215 }
216
217 public static Dimension getMaxFrameSize() {
218 if (DisplayIO.isTwinFramesOn()) {
219 return new Dimension((_MaxSize.width / 2), _MaxSize.height);
220 } else
221 return _MaxSize;
222 }
223
224 /**
225 * Sets the Graphics2D object that should be used for all painting tasks.
226 * Note: Actual painting is done by using g.create() to create temporary
227 * instances that are then disposed of using g.dispose().
228 *
229 * @param g
230 * The Graphics2D object to use for all painting
231 */
232 public static void setDisplayGraphics(Graphics2D g) {
233 _DisplayGraphics = g;
234 }
235
236 /*
237 * Displays the given Item on the screen
238 */
239 static void PaintItem(Graphics2D g, Item i) {
240 if (i == null || g == null)
241 return;
242
243 // do not paint annotation items in audience mode
244 if (!isAudienceMode()
245 || (isAudienceMode() && !i.isConnectedToAnnotation() && !i
246 .isAnnotation()) || i == FrameUtils.getLastEdited()) {
247
248 Graphics2D tg = (Graphics2D) g.create();
249 i.paint(tg);
250 tg.dispose();
251 }
252 }
253
254 /**
255 * Adds all the scaled vector items for a frame into a list. It uses
256 * recursion to get the items from nested vector frames.
257 *
258 * @param items
259 * the list into which the vector items will be placed.
260 * @param vector
261 * the frame containing vecor items.
262 * @param seenVectors
263 * the vectors which should be ignored to prevent infinate loops.
264 * @param origin
265 * start point for this frame or null if it is a top level frame.
266 * @param scale
267 * the factor by which the item on the vector frame are to be
268 * scaled.
269 */
270 // public static void AddAllVectorItems(List<Item> items, Vector vector,
271 // Collection<Frame> seenVectors) {
272 // // Check all the vector items and add the items on the vectors
273 // if (seenVectors.contains(vector))
274 // return;
275 // seenVectors.add(vector);
276 //
277 // float originX = origin == null ? 0 : origin.x;
278 // float originY = origin == null ? 0 : origin.y;
279 //
280 // for (Vector o : vector.getVectors())
281 // AddAllVectorItems(items, o.Frame, new HashSet<Frame>(seenVectors),
282 // new Point2D.Float(originX + o.Origin.x * scale, originY
283 // + o.Origin.y * scale), o.Scale * scale,
284 // o.Foreground, o.Background);
285 // // if its the original frame then were done
286 // if (origin == null) {
287 // ItemUtils.EnclosedCheck(items);
288 // return;
289 // }
290 // // Put copies of the items shifted to the origin of the VectorTag
291 // items.addAll(ItemUtils
292 // .CopyItems(vector.getNonAnnotationItems(), vector));
293 //
294 // }
295 /**
296 * Recursive function similar to AddAllOverlayItems.
297 *
298 * @param widgets
299 * The collection the widgets will be added to
300 * @param overlay
301 * An "overlay" frame - this intially will be the parent frame
302 * @param seenOverlays
303 * Used for state in the recursion stack. Pass as an empty
304 * (non-null) list.
305 */
306 public static void AddAllOverlayWidgets(List<InteractiveWidget> widgets,
307 Frame overlay, List<Frame> seenOverlays) {
308 if (seenOverlays.contains(overlay))
309 return;
310
311 seenOverlays.add(overlay);
312
313 for (Overlay o : overlay.getOverlays())
314 AddAllOverlayWidgets(widgets, o.Frame, seenOverlays);
315
316 widgets.addAll(overlay.getInteractiveWidgets());
317 }
318
319 private static Image Paint(Frame toPaint, Area clip) {
320 return Paint(toPaint, clip, true, true);
321 }
322
323 /**
324 *
325 * @param toPaint
326 * @param clip
327 * If null, then no clip applied.
328 * @param isActualFrame
329 * @return
330 */
331 private static Image Paint(Frame toPaint, Area clip, boolean isActualFrame,
332 boolean createVolitile) {
333 if (toPaint == null)
334 return null;
335
336 // the buffer is not valid, so it must be recreated
337 if (!toPaint.isBufferValid()) {
338 Image buffer = toPaint.getBuffer();
339 if (buffer == null) {
340 GraphicsEnvironment ge = GraphicsEnvironment
341 .getLocalGraphicsEnvironment();
342 if (createVolitile) {
343 buffer = ge.getDefaultScreenDevice()
344 .getDefaultConfiguration()
345 .createCompatibleVolatileImage(_MaxSize.width,
346 _MaxSize.height);
347 } else {
348 buffer = new BufferedImage(_MaxSize.width, _MaxSize.height,
349 BufferedImage.TYPE_INT_ARGB);
350 }
351 toPaint.setBuffer(buffer);
352 }
353
354 Graphics2D bg = (Graphics2D) buffer.getGraphics();
355 paintFrame(toPaint, clip, isActualFrame, createVolitile, bg);
356
357 bg.dispose();
358 }
359
360 return toPaint.getBuffer();
361 }
362
363 /**
364 * @param toPaint
365 * @param clip
366 * @param isActualFrame
367 * @param createVolitile
368 * @param bg
369 */
370 public static void paintFrame(Frame toPaint, Area clip,
371 boolean isActualFrame, boolean createVolitile, Graphics2D bg) {
372
373 // Prepare render passes
374 if (isActualFrame) {
375 currentClip = clip;
376 for (FrameRenderPass pass : _frameRenderPasses) {
377 currentClip = pass.paintStarted(currentClip);
378 clip = currentClip;
379 }
380 }
381
382 bg.setClip(clip);
383
384 // TODO: Revise images and clip - VERY IMPORTANT
385
386 // Nicer looking lines, but may be too jerky while
387 // rubber-banding on older machines
388 if (UserSettings.AntiAlias.get())
389 bg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
390 RenderingHints.VALUE_ANTIALIAS_ON);
391 // If we are doing @f etc... then have a clear background if its the
392 // default background color
393 Color backgroundColor = null;
394 // Need to allow transparency for frameImages
395 if (createVolitile) {
396 backgroundColor = toPaint.getPaintBackgroundColor();
397 } else {
398 backgroundColor = toPaint.getBackgroundColor();
399 if (backgroundColor == null)
400 backgroundColor = Item.TRANSPARENT;
401 }
402
403 // if(isActual)
404 bg.setColor(backgroundColor);
405 if (isActualFrame) {
406 // bg.setColor(Color.white); // TODO: TEMP FOR DEBUGGING
407
408 // System.out.println("paintPic");
409 }
410 bg.fillRect(0, 0, _MaxSize.width, _MaxSize.height);
411
412 List<Item> visibleItems = new LinkedList<Item>();
413 List<InteractiveWidget> paintWidgets;
414
415 if (isActualFrame) {
416 // Add all the items for this frame and any other from other
417 // frames
418 visibleItems.addAll(toPaint.getAllItems());
419 paintWidgets = new LinkedList<InteractiveWidget>();
420 AddAllOverlayWidgets(paintWidgets, toPaint, new LinkedList<Frame>());
421 } else {
422 visibleItems.addAll(toPaint.getVisibleItems());
423 visibleItems.addAll(toPaint.getVectorItems());
424 paintWidgets = toPaint.getInteractiveWidgets();
425 }
426
427
428 HashSet<Item> paintedFillsAndLines = new HashSet<Item>();
429 // FIRST: Paint widgets swing gui (not expeditee gui) .
430 // Note that these are the anchored widgets
431 ListIterator<InteractiveWidget> widgetItor = paintWidgets
432 .listIterator(paintWidgets.size());
433 while (widgetItor.hasPrevious()) {
434 // Paint first-in-last-serve ordering - like swing
435 // If it is done the other way around then widgets are covered up by
436 // the box that is supposed to be underneath
437 InteractiveWidget iw = widgetItor.previous();
438 if (clip == null || clip.intersects(iw.getComponant().getBounds())) {
439 paintedFillsAndLines.addAll(iw.getItems());
440 //iw.paint(bg);
441 //PaintItem(bg, iw.getItems().get(4));
442
443 }
444 }
445
446
447 // Filter out items that do not need to be painted
448 List<Item> paintItems;
449 HashSet<Item> fillOnlyItems = null; // only contains items that do
450 // not need drawing but fills
451 // might
452
453 if (clip == null) {
454 paintItems = visibleItems;
455 } else {
456 fillOnlyItems = new HashSet<Item>();
457 paintItems = new LinkedList<Item>();
458 for (Item i : visibleItems) {
459 if (i.isInDrawingArea(clip)) {
460 paintItems.add(i);
461 } else if (i.isEnclosed()) {
462 // just add all fill items despite possibility of fills
463 // not being in clip
464 // because it will be faster than having to test twice
465 // for fills that do need
466 // repainting.
467 fillOnlyItems.add(i);
468 }
469 }
470 }
471 // Only paint files and lines once ... between anchored AND free
472 // items
473 PaintPictures(bg, paintItems, fillOnlyItems, paintedFillsAndLines);
474 PaintLines(bg, visibleItems);
475
476
477 widgetItor = paintWidgets.listIterator(paintWidgets.size());
478 while (widgetItor.hasPrevious()) {
479 // Paint first-in-last-serve ordering - like swing
480 // If it is done the other way around then widgets are covered up by
481 // the box that is supposed to be underneath
482 InteractiveWidget iw = widgetItor.previous();
483 if (clip == null || clip.intersects(iw.getComponant().getBounds())) {
484 iw.paint(bg);
485 PaintItem(bg, iw.getItems().get(4));
486 }
487 }
488
489
490 // Filter out free items that do not need to be painted
491 // This is efficient in cases with animation while free items exist
492
493 List<Item> freeItemsToPaint = new LinkedList<Item>();
494 // Dont paint the free items for the other frame in twin frames mode
495 // if (toPaint == DisplayIO.getCurrentFrame()) {
496 if (clip == null) {
497 freeItemsToPaint = FreeItems.getInstance();
498 } else {
499 freeItemsToPaint = new LinkedList<Item>();
500 fillOnlyItems.clear();
501 for (Item i : FreeItems.getInstance()) {
502 if (i.isInDrawingArea(clip)) {
503 freeItemsToPaint.add(i);
504 } else if (i.isEnclosed()) {
505 fillOnlyItems.add(i);
506 }
507 }
508 }
509 // }
510
511 if (isActualFrame && toPaint == DisplayIO.getCurrentFrame())
512 PaintPictures(bg, freeItemsToPaint, fillOnlyItems,
513 paintedFillsAndLines);
514 // TODO if we can get transparency with FreeItems.getInstance()...
515 // then text can be done before freeItems
516 PaintNonLinesNonPicture(bg, paintItems);
517
518 // toPaint.setBufferValid(true);
519
520 if (isActualFrame && !isAudienceMode()) {
521 PaintItem(bg, toPaint.getNameItem());
522 }
523
524 if (DisplayIO.isTwinFramesOn()) {
525 List<Item> lines = new LinkedList<Item>();
526 for (Item i : freeItemsToPaint) {
527 if (i instanceof Line) {
528 Line line = (Line) i;
529
530 if (toPaint == DisplayIO.getCurrentFrame()) {
531 // If exactly one end of the line is floating...
532
533 if (line.getEndItem().isFloating()
534 ^ line.getStartItem().isFloating()) {
535 // Line l = TransposeLine(line,
536 // line.getEndItem(),
537 // toPaint, 0, 0);
538 // if (l == null)
539 // l = TransposeLine(line,
540 // line.getStartItem(), toPaint, 0, 0);
541 // if (l == null)
542 // l = line;
543 // lines.add(l);
544 } else {
545 // lines.add(line);
546 }
547 } else {
548 // if (line.getEndItem().isFloating()
549 // ^ line.getStartItem().isFloating()) {
550 // lines.add(TransposeLine(line,
551 // line.getEndItem(), toPaint,
552 // FrameMouseActions.getY(), -DisplayIO
553 // .getMiddle()));
554 // lines.add(TransposeLine(line, line
555 // .getStartItem(), toPaint,
556 // FrameMouseActions.getY(), -DisplayIO
557 // .getMiddle()));
558 // }
559 }
560 }
561 }
562 if (isActualFrame)
563 PaintLines(bg, lines);
564 } else {
565 // Don't paint the
566 if (isActualFrame)
567 PaintLines(bg, freeItemsToPaint);
568 }
569
570 if (isActualFrame && toPaint == DisplayIO.getCurrentFrame())
571 PaintNonLinesNonPicture(bg, freeItemsToPaint);
572
573 // Repaint popups / drags... As well as final passes
574 if (isActualFrame) {
575 for (FrameRenderPass pass : _frameRenderPasses) {
576 pass.paintPreLayeredPanePass(bg);
577 }
578 PopupManager.getInstance().paintLayeredPane(bg, clip);
579 for (FrameRenderPass pass : _frameRenderPasses) {
580 pass.paintFinalPass(bg);
581 }
582 }
583
584 // paint tooltip
585 if(!FreeItems.itemsAttachedToCursor()) {
586 Item current = FrameUtils.getCurrentItem();
587 if(current != null) {
588 current.paintTooltip(bg);
589 } //else {
590// Item.clearTooltipOwner();
591// }
592 if(previous != null) previous.clearTooltips();
593 previous = current;
594 }
595
596 if (FreeItems.hasCursor()
597 && DisplayIO.getCursor() == Item.DEFAULT_CURSOR)
598 PaintNonLinesNonPicture(bg, FreeItems.getCursor());
599 }
600
601 private static Item previous = null;
602
603 // creates a new line so that lines are shown correctly when spanning
604 // across frames in TwinFrames mode
605 // private static Line TransposeLine(Line line, Item d, Frame toPaint,
606 // int base, int adj) {
607 // Line nl = null;
608 //
609 // if (toPaint != DisplayIO.getCurrentFrame() && d.getParent() == null
610 // && line.getOppositeEnd(d).getParent() == toPaint) {
611 // nl = line.copy();
612 // if (d == line.getStartItem())
613 // d = nl.getStartItem();
614 // else
615 // d = nl.getEndItem();
616 //
617 // if (DisplayIO.FrameOnSide(toPaint) == 0)
618 // d.setX(base);
619 // else
620 // d.setX(base + adj);
621 //
622 // } else if (toPaint == DisplayIO.getCurrentFrame()
623 // && d.getParent() == null
624 // && line.getOppositeEnd(d).getParent() != toPaint) {
625 // nl = line.copy();
626 //
627 // if (d == line.getStartItem())
628 // d = nl.getEndItem();
629 // else
630 // d = nl.getStartItem();
631 //
632 // if (DisplayIO.FrameOnSide(toPaint) == 1)
633 // d.setX(d.getX() - DisplayIO.getMiddle());
634 // else
635 // d.setX(d.getX() + DisplayIO.getMiddle());
636 // }
637 // if (nl != null) {
638 // nl.invalidateAll();
639 // FrameGraphics.requestRefresh(true);
640 // }
641 // return nl;
642 // }
643
644 private static void Paint(Graphics g, Image left, Image right,
645 Color background) {
646
647 // Triggers a FrameTransition is this has been signalled
648 // through an '@frameTransition: xxxx' on the frame. The type of
649 // transition is specified in the 'xxxx' part
650
651
652 // if TwinFrames mode is on, then clipping etc has to be set
653 if (DisplayIO.isTwinFramesOn()) {
654 // draw the two lines down the middle of the window
655 if (left != null)
656 left.getGraphics().drawLine(DisplayIO.getMiddle() - 2, 0,
657 DisplayIO.getMiddle() - 2, _MaxSize.height);
658
659 if (right != null)
660 right.getGraphics().drawLine(0, 0, 0, _MaxSize.height);
661
662 // set the clipping area
663 ((Graphics2D) g).setClip(0, 0, DisplayIO.getMiddle() - 1,
664 _MaxSize.height);
665 g.drawImage(left, 0, 0, Item.DEFAULT_BACKGROUND, null);
666 ((Graphics2D) g).setClip(null);
667 g.drawImage(right, DisplayIO.getMiddle() + 1, 0,
668 Item.DEFAULT_BACKGROUND, null);
669
670
671 }
672 // otherwise, just draw whichever side is active
673 else {
674 if (DisplayIO.getCurrentSide() == 0) {
675 if (!paintTransition(g,left)) {
676 // Nothing fancy occurred, just need to render the next slide to screen
677 g.drawImage(left, 0, 0, Item.DEFAULT_BACKGROUND, null);
678 }
679 }
680 else {
681 if (!paintTransition(g,right)) {
682 // Nothing fancy occurred, just need to render the next slide to screen
683 g.drawImage(right, 0, 0, Item.DEFAULT_BACKGROUND, null);
684 }
685 }
686 }
687
688 }
689
690
691 private static boolean paintTransition(Graphics g, Image image)
692 {
693 boolean performed_transition = false;
694
695 //If we are doing a transition
696 if(FrameTransitions.getSlide() == true){
697
698 String input = "org.expeditee.gui.FrameTransitions";
699 String slide_mode_method = FrameTransitions.getslideModeMethod();
700
701 try {
702 Class<?> c = Class.forName(input);
703
704 Class[] cArg = new Class[3];
705 cArg[0] = Graphics.class;
706 cArg[1] = Image.class;
707 cArg[2] = Dimension.class;
708
709 //Gets the method of transition and calls it
710 Method lMethod = c.getDeclaredMethod(slide_mode_method, cArg);
711
712 if(lMethod != null){
713 // calling static method, so no class parameter needed
714 Object o = lMethod.invoke(null, g, image, _MaxSize);
715 performed_transition = true;
716
717 }
718 else{
719 System.err.println("Unable to locate the transition '" + slide_mode_method + "'");
720 }
721
722 } catch (Exception e) {
723
724 System.err.println("An Reflection Exception occurred trying to invoke '" + slide_mode_method + "'");
725 e.printStackTrace();
726 }
727 }
728 //Tells the frame to only transition once
729 FrameTransitions.setSlideFalse();
730
731 return performed_transition;
732 }
733
734
735 public static void Clear() {
736 Graphics g = _DisplayGraphics.create();
737 g.setColor(Color.WHITE);
738 g.fillRect(0, 0, _MaxSize.width, _MaxSize.height);
739 g.dispose();
740 }
741
742 private static void PaintNonLinesNonPicture(Graphics2D g, List<Item> toPaint) {
743 for (Item i : toPaint)
744 if (!(i instanceof Line) && !(i instanceof XRayable))
745 PaintItem(g, i);
746 }
747
748 /**
749 * Paint the lines that are not part of an enclosure.
750 *
751 * @param g
752 * @param toPaint
753 */
754 private static void PaintLines(Graphics2D g, List<Item> toPaint) {
755 // Use this set to keep track of the items that have been painted
756 Collection<Item> done = new HashSet<Item>();
757 for (Item i : toPaint)
758 if (i instanceof Line) {
759 Line l = (Line) i;
760 if (done.contains(l)) {
761 l.paintArrows(g);
762 } else {
763 // When painting a line all connected lines are painted too
764 done.addAll(l.getAllConnected());
765 if (l.getStartItem().getEnclosedArea() == 0)
766 PaintItem(g, i);
767 }
768 }
769 }
770
771 /**
772 * Paint filled areas and their surrounding lines as well as pictures. Note:
773 * floating widgets are painted as fills
774 *
775 * @param g
776 * @param toPaint
777 */
778 private static void PaintPictures(Graphics2D g, List<Item> toPaint,
779 HashSet<Item> fillOnlyItems, HashSet<Item> done) {
780
781 List<Item> toFill = new LinkedList<Item>();
782 for (Item i : toPaint) {
783 // Ignore items that have already been done!
784 // Also ignore invisible items..
785 // TODO possibly ignore invisible items before coming to this
786 // method?
787 if (done.contains(i))
788 continue;
789 if (i instanceof XRayable) {
790 toFill.add(i);
791 done.addAll(i.getConnected());
792 } else if (i.hasEnclosures()) {
793 for (Item enclosure : i.getEnclosures()) {
794 if (!toFill.contains(enclosure))
795 toFill.add(enclosure);
796 }
797 done.addAll(i.getConnected());
798 } else if (i.isLineEnd()
799 && (!isAudienceMode() || !i.isConnectedToAnnotation())) {
800 toFill.add(i);
801 done.addAll(i.getAllConnected());
802 }
803 }
804
805 if (fillOnlyItems != null) {
806 for (Item i : fillOnlyItems) {
807 if (done.contains(i))
808 continue;
809 else if (!isAudienceMode() || !i.isConnectedToAnnotation()) {
810 toFill.add(i);
811 }
812 done.addAll(i.getAllConnected());
813 }
814 }
815
816 // Sort the items to fill
817 Collections.sort(toFill, new Comparator<Item>() {
818 public int compare(Item a, Item b) {
819 Double aArea = a.getEnclosedArea();
820 Double bArea = b.getEnclosedArea();
821 int cmp = aArea.compareTo(bArea);
822 if (cmp == 0) {
823 // System.out.println(a.getEnclosureID() + " " +
824 // b.getID());\
825 // Shapes to the left go underneath
826 Polygon pA = a.getEnclosedShape();
827 Polygon pB = b.getEnclosedShape();
828 if (pA == null || pB == null)
829 return 0;
830 return new Integer(pA.getBounds().x).compareTo(pB
831 .getBounds().x);
832 }
833 return cmp * -1;
834 }
835 });
836 for (Item i : toFill) {
837 if (i instanceof XRayable) {
838 PaintItem(g, i);
839 } else {
840 // Paint the fill and lines
841 i.paintFill(g);
842 List<Line> lines = i.getLines();
843 if (lines.size() > 0)
844 PaintItem(g, lines.get(0));
845 }
846 }
847 }
848
849 /**
850 * Highlights an item on the screen Note: All graphics are handled by the
851 * Item itself.
852 *
853 * @param i
854 * The Item to highlight.
855 * @param val
856 * True if the highlighting is being shown, false if it is being
857 * erased.
858 * @return the item that was highlighted
859 */
860 public static Item Highlight(Item i) {
861 if ((i instanceof Line)) {
862 // Check if within 20% of the end of the line
863 Line l = (Line) i;
864 Item toDisconnect = l.getEndPointToDisconnect(Math
865 .round(FrameMouseActions.MouseX), Math
866 .round(FrameMouseActions.MouseY));
867
868 // Brook: Widget Edges do not have such a context
869 if (toDisconnect != null && !(i instanceof WidgetEdge)) {
870 Item.HighlightMode newMode = toDisconnect.getHighlightMode();
871 if (FreeItems.itemsAttachedToCursor())
872 newMode = Item.HighlightMode.Normal;
873 // unhighlight all the other dots
874 for (Item conn : toDisconnect.getAllConnected()) {
875 conn.setHighlightMode(Item.HighlightMode.None);
876 }
877 l.setHighlightMode(newMode);
878 // highlight the dot that will be in disconnect mode
879 toDisconnect.setHighlightMode(newMode);
880 i = toDisconnect;
881 } else {
882 if(FrameMouseActions.isShiftDown()) {
883 for(Item j : i.getAllConnected()) {
884 if(j instanceof Dot && !j.equals(i)) {
885 j.setHighlightMode(HighlightMode.None);
886 }
887 }
888 l.getStartItem().setHighlightMode(HighlightMode.Connected);
889 l.getEndItem().setHighlightMode(HighlightMode.Connected);
890 } else {
891 for(Item j : i.getAllConnected()) {
892 if(j instanceof Dot && !j.equals(i)) {
893 j.setHighlightMode(HighlightMode.Connected);
894 }
895 }
896 }
897// Collection<Item> connected = i.getAllConnected();
898// for (Item conn : connected) {
899// conn.setHighlightMode(Item.HighlightMode.Connected);
900// }
901 }
902 } else if (i instanceof Circle) {
903 i.setHighlightMode(Item.HighlightMode.Connected);
904 } else if (!i.isVisible()) {
905 changeHighlightMode(i, Item.HighlightMode.Connected, null);
906 } else if (i instanceof Dot) {
907 // highlight the dot
908 if (i.hasPermission(UserAppliedPermission.full)) {
909 changeHighlightMode(i, Item.HighlightMode.Normal, Item.HighlightMode.None);
910 } else {
911 changeHighlightMode(i, Item.HighlightMode.Connected, Item.HighlightMode.Connected);
912 }
913 // highlight connected dots, but only if there aren't items being carried on the cursor
914 if(FreeItems.getInstance().size() == 0) {
915 if(FrameMouseActions.isShiftDown()) {
916 for(Item j : i.getAllConnected()) {
917 if(j instanceof Dot && !j.equals(i)) {
918 j.setHighlightMode(HighlightMode.Connected);
919 }
920 }
921 } else {
922 for(Item j : i.getAllConnected()) {
923 if(j instanceof Dot && !j.equals(i)) {
924 j.setHighlightMode(HighlightMode.None);
925 }
926 }
927 for(Line l : i.getLines()) {
928 Item j = l.getOppositeEnd(i);
929 j.setHighlightMode(HighlightMode.Connected);
930 }
931 }
932 }
933 } else {
934 // FrameGraphics.ChangeSelectionMode(i,
935 // Item.SelectedMode.Normal);
936 // For polygons need to make sure all other endpoints are
937 // unHighlighted
938 if (i.hasPermission(UserAppliedPermission.full))
939 changeHighlightMode(i, Item.HighlightMode.Normal,
940 Item.HighlightMode.None);
941 else
942 changeHighlightMode(i, Item.HighlightMode.Connected,
943 Item.HighlightMode.Connected);
944 }
945 Repaint();
946 return i;
947 }
948
949 public static void changeHighlightMode(Item item, Item.HighlightMode newMode) {
950 changeHighlightMode(item, newMode, newMode);
951 }
952
953 public static void changeHighlightMode(Item item,
954 Item.HighlightMode newMode, Item.HighlightMode connectedNewMode) {
955 if (item == null)
956 return;
957
958 if (item.hasVector()) {
959 for (Item i : item.getParentOrCurrentFrame().getVectorItems()) {
960 if (i.getEditTarget() == item) {
961 i.setHighlightMode(newMode);
962 }
963 }
964 item.setHighlightMode(newMode);
965 } else {
966 // Mike: TODO comment on why the line below is used!!
967 // I forgot already!!Opps
968 boolean freeItem = FreeItems.getInstance().contains(item);
969 for (Item i : item.getAllConnected()) {
970 if (/* freeItem || */!FreeItems.getInstance().contains(i)) {
971 i.setHighlightMode(connectedNewMode);
972 }
973 }
974 if (!freeItem && newMode != connectedNewMode)
975 item.setHighlightMode(newMode);
976 }
977 Repaint();
978 }
979
980 /**
981 * Repaints the buffer of the given Frame.
982 *
983 * @param toUpdate
984 * The Frame whose buffer is to be repainted.
985 */
986
987 public static void UpdateBuffer(Frame toUpdate, boolean paintOverlays,
988 boolean useVolitile) {
989 toUpdate.setBuffer(getBuffer(toUpdate, paintOverlays, useVolitile));
990 }
991
992 public static Image getBuffer(Frame toUpdate, boolean paintOverlays,
993 boolean useVolitile) {
994 if (toUpdate == null)
995 return null;
996
997 return Paint(toUpdate, null, paintOverlays, useVolitile);
998 }
999
1000 public static int getMode() {
1001 return _Mode;
1002 }
1003
1004 public static Graphics createGraphics() {
1005 // Error messages on start up will call this message before
1006 // _DisplayGraphics has been initialised
1007 if (_DisplayGraphics == null)
1008 return null;
1009 return _DisplayGraphics.create();
1010 }
1011
1012 // Damaged areas pending to render. Accessessed by multiple threads
1013 private static HashSet<Rectangle> damagedAreas = new HashSet<Rectangle>();
1014
1015 /** The clip used while paiting */
1016 private static Area currentClip;
1017
1018 /**
1019 * The current clip that is used for painting at this instant.
1020 *
1021 * Intention: for extra clipping within an items paint method - the clip is
1022 * lost in the graphics object for which can be regained via this method.
1023 *
1024 * @return The current clip. Null if no clip (e.g. full screen render).
1025 */
1026 public static Area getCurrentClip() {
1027 return (currentClip != null) ? (Area) currentClip.clone() : null;
1028 }
1029
1030 /**
1031 * Checks that the item is visible (on current frame && overlays) - if
1032 * visible then damaged area will be re-rendered on the next refresh.
1033 *
1034 * @param damagedItem
1035 * @param toRepaint
1036 */
1037 public static void invalidateItem(Item damagedItem, Rectangle toRepaint) {
1038 // Only add area to repaint if item is visible...
1039 if (ItemUtils.isVisible(damagedItem)) {
1040 synchronized (damagedAreas) {
1041 damagedAreas.add(toRepaint);
1042 }
1043 } else if (MessageBay.isMessageItem(damagedItem)) {
1044 MessageBay.addDirtyArea(toRepaint);
1045 }
1046 }
1047
1048 /**
1049 * The given area will be re-rendered in the next refresh. This is the
1050 * quicker version and is more useful for re-rendering animated areas.
1051 *
1052 * @param toRepaint
1053 */
1054 public static void invalidateArea(Rectangle toRepaint) {
1055 synchronized (damagedAreas) {
1056 damagedAreas.add(toRepaint);
1057 }
1058 }
1059
1060 public static void clearInvalidAreas() {
1061 synchronized (damagedAreas) {
1062 damagedAreas.clear();
1063 }
1064 }
1065
1066 /**
1067 * Invalidates the buffered image of the current Frame and forces it to be
1068 * repainted on to the screen. Repaints all items. This is more expensive
1069 * than refresh.
1070 */
1071 public static void ForceRepaint() { // TODO: TEMP: Use refresh
1072 Frame current = DisplayIO.getCurrentFrame();
1073
1074 if (current == null)
1075 return;
1076 refresh(false);
1077 }
1078
1079 public static void Repaint() { // TODO: TEMP: Use refresh
1080 refresh(true);
1081 }
1082
1083 /**
1084 * Called to refresh the display screen. Thread safe.
1085 */
1086 public static void refresh(boolean useInvalidation) {
1087
1088 if (_DisplayGraphics == null || _MaxSize.width <= 0
1089 || _MaxSize.height <= 0)
1090 return;
1091
1092 currentClip = null;
1093 if (useInvalidation) { // build clip
1094
1095 synchronized (damagedAreas) {
1096 if (!damagedAreas.isEmpty()) {
1097
1098 for (Rectangle r : damagedAreas) {
1099 if (currentClip == null)
1100 currentClip = new Area(r);
1101 else
1102 currentClip.add(new Area(r));
1103 }
1104 damagedAreas.clear();
1105
1106 } else if (MessageBay.isDirty()) {
1107 // Paint dirty message bay
1108 Graphics dg = _DisplayGraphics.create();
1109 MessageBay.refresh(true, dg, Item.DEFAULT_BACKGROUND);
1110 return;
1111
1112 } else
1113 return; // nothing to render
1114 }
1115
1116 } else {
1117 synchronized (damagedAreas) {
1118 damagedAreas.clear();
1119 }
1120 // System.out.println("FULLSCREEN REFRESH"); // TODO: REMOVE
1121 }
1122
1123 Frame[] toPaint = DisplayIO.getFrames();
1124 Image left = Paint(toPaint[0], currentClip);
1125 Image right = Paint(toPaint[1], currentClip);
1126
1127 Graphics dg = _DisplayGraphics.create();
1128
1129 // Paint frame to window
1130 Paint(dg, left, right, Item.DEFAULT_BACKGROUND);
1131
1132 // Paint any animations
1133 PopupManager.getInstance().paintAnimations();
1134
1135 // Paint message bay
1136 MessageBay.refresh(useInvalidation, dg, Item.DEFAULT_BACKGROUND);
1137
1138 dg.dispose();
1139 }
1140
1141 /**
1142 * If wanting to refresh from another thread - other than the main thread
1143 * that handles the expeditee datamodel (modifying / accessing / rendering).
1144 * Use this method for thread safety.
1145 */
1146 public static synchronized void requestRefresh(boolean useInvalidation) {
1147
1148 _requestMarsheller._useInvalidation = useInvalidation;
1149
1150 if (_requestMarsheller._isQueued) {
1151 return;
1152 }
1153
1154 _requestMarsheller._isQueued = true;
1155 EventQueue.invokeLater(_requestMarsheller); // Render on AWT thread
1156 }
1157
1158 private static RenderRequestMarsheller _requestMarsheller = new FrameGraphics().new RenderRequestMarsheller();
1159
1160 /**
1161 * Used for marshelling render requests from foreign threads to the event
1162 * dispatcher thread... (AWT)
1163 *
1164 * @author Brook Novak
1165 */
1166 private class RenderRequestMarsheller implements Runnable {
1167
1168 boolean _useInvalidation = true;
1169
1170 boolean _isQueued = false;
1171
1172 public void run() {
1173 refresh(_useInvalidation);
1174 _isQueued = false;
1175 _useInvalidation = true;
1176 }
1177
1178 }
1179
1180 /**
1181 * Adds a FinalFrameRenderPass to the frame-render pipeline...
1182 *
1183 * Note that the last added FinalFrameRenderPass will be rendered at the
1184 * very top.
1185 *
1186 * @param pass
1187 * The pass to add. If already added then nothing results in the
1188 * call.
1189 *
1190 * @throws NullPointerException
1191 * If pass is null.
1192 */
1193 public static void addFrameRenderPass(FrameRenderPass pass) {
1194 if (pass == null)
1195 throw new NullPointerException("pass");
1196
1197 if (!_frameRenderPasses.contains(pass))
1198 _frameRenderPasses.add(pass);
1199 }
1200
1201 /**
1202 * Adds a FinalFrameRenderPass to the frame-render pipeline...
1203 *
1204 * Note that the last added FinalFrameRenderPass will be rendered at the
1205 * very top.
1206 *
1207 * @param pass
1208 * The pass to remove
1209 *
1210 */
1211 public static void removeFrameRenderPass(FrameRenderPass pass) {
1212 _frameRenderPasses.remove(pass);
1213 }
1214
1215 /**
1216 * A FinalFrameRenderPass is invoked at the very final stages for rendering
1217 * a frame: that is, after the popups are drawn.
1218 *
1219 * There can be many applications for FinalFrameRenderPass. Such as tool
1220 * tips, balloons, or drawing items at the highest Z-order in special
1221 * situations.
1222 *
1223 * Although if there are multiples FinalFrameRenderPasses attatach to the
1224 * frame painter then it is not garaunteed to be rendered very last.
1225 *
1226 * @see FrameGraphics#addFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
1227 * @see FrameGraphics#removeFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
1228 *
1229 * @author Brook Novak
1230 */
1231 public interface FrameRenderPass {
1232
1233 /**
1234 *
1235 * @param currentClip
1236 *
1237 * @return The clip that the pass should use instead. i.e. if there are
1238 * any effects that cannot invladate prior to paint call.
1239 */
1240 Area paintStarted(Area currentClip);
1241
1242 void paintFinalPass(Graphics g);
1243
1244 void paintPreLayeredPanePass(Graphics g);
1245 }
1246
1247}
Note: See TracBrowser for help on using the repository browser.