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

Last change on this file since 940 was 940, checked in by bln4, 9 years ago

Continued work on update to tooltips to be actual items so that data can be stored.

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