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

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

Main Addition: full-screen audience mode. Also some whitespace and comment tidy-up.

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