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

Last change on this file since 984 was 984, checked in by davidb, 9 years ago

Code tidy up

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