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

Last change on this file since 919 was 919, checked in by jts21, 10 years ago

Added license headers to all files, added full GPL3 license file, moved license header generator script to dev/bin/scripts

File size: 32.3 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 }
547
548 if (FreeItems.hasCursor()
549 && DisplayIO.getCursor() == Item.DEFAULT_CURSOR)
550 PaintNonLinesNonPicture(bg, FreeItems.getCursor());
551 }
552
553 // creates a new line so that lines are shown correctly when spanning
554 // across frames in TwinFrames mode
555 // private static Line TransposeLine(Line line, Item d, Frame toPaint,
556 // int base, int adj) {
557 // Line nl = null;
558 //
559 // if (toPaint != DisplayIO.getCurrentFrame() && d.getParent() == null
560 // && line.getOppositeEnd(d).getParent() == toPaint) {
561 // nl = line.copy();
562 // if (d == line.getStartItem())
563 // d = nl.getStartItem();
564 // else
565 // d = nl.getEndItem();
566 //
567 // if (DisplayIO.FrameOnSide(toPaint) == 0)
568 // d.setX(base);
569 // else
570 // d.setX(base + adj);
571 //
572 // } else if (toPaint == DisplayIO.getCurrentFrame()
573 // && d.getParent() == null
574 // && line.getOppositeEnd(d).getParent() != toPaint) {
575 // nl = line.copy();
576 //
577 // if (d == line.getStartItem())
578 // d = nl.getEndItem();
579 // else
580 // d = nl.getStartItem();
581 //
582 // if (DisplayIO.FrameOnSide(toPaint) == 1)
583 // d.setX(d.getX() - DisplayIO.getMiddle());
584 // else
585 // d.setX(d.getX() + DisplayIO.getMiddle());
586 // }
587 // if (nl != null) {
588 // nl.invalidateAll();
589 // FrameGraphics.requestRefresh(true);
590 // }
591 // return nl;
592 // }
593
594 private static void Paint(Graphics g, Image left, Image right,
595 Color background) {
596
597 // if TwinFrames mode is on, then clipping etc has to be set
598 if (DisplayIO.isTwinFramesOn()) {
599 // draw the two lines down the middle of the window
600 if (left != null)
601 left.getGraphics().drawLine(DisplayIO.getMiddle() - 2, 0,
602 DisplayIO.getMiddle() - 2, _MaxSize.height);
603
604 if (right != null)
605 right.getGraphics().drawLine(0, 0, 0, _MaxSize.height);
606
607 // set the clipping area
608 ((Graphics2D) g).setClip(0, 0, DisplayIO.getMiddle() - 1,
609 _MaxSize.height);
610 g.drawImage(left, 0, 0, Item.DEFAULT_BACKGROUND, null);
611 ((Graphics2D) g).setClip(null);
612 g.drawImage(right, DisplayIO.getMiddle() + 1, 0,
613 Item.DEFAULT_BACKGROUND, null);
614
615 // otherwise, just draw whichever side is active
616 } else {
617 if (DisplayIO.getCurrentSide() == 0)
618 g.drawImage(left, 0, 0, Item.DEFAULT_BACKGROUND, null);
619 else
620 g.drawImage(right, 0, 0, Item.DEFAULT_BACKGROUND, null);
621 }
622
623 }
624
625 public static void Clear() {
626 Graphics g = _DisplayGraphics.create();
627 g.setColor(Color.WHITE);
628 g.fillRect(0, 0, _MaxSize.width, _MaxSize.height);
629 g.dispose();
630 }
631
632 private static void PaintNonLinesNonPicture(Graphics2D g, List<Item> toPaint) {
633 for (Item i : toPaint)
634 if (!(i instanceof Line) && !(i instanceof XRayable))
635 PaintItem(g, i);
636 }
637
638 /**
639 * Paint the lines that are not part of an enclosure.
640 *
641 * @param g
642 * @param toPaint
643 */
644 private static void PaintLines(Graphics2D g, List<Item> toPaint) {
645 // Use this set to keep track of the items that have been painted
646 Collection<Item> done = new HashSet<Item>();
647 for (Item i : toPaint)
648 if (i instanceof Line) {
649 Line l = (Line) i;
650 if (done.contains(l)) {
651 l.paintArrows(g);
652 } else {
653 // When painting a line all connected lines are painted too
654 done.addAll(l.getAllConnected());
655 if (l.getStartItem().getEnclosedArea() == 0)
656 PaintItem(g, i);
657 }
658 }
659 }
660
661 /**
662 * Paint filled areas and their surrounding lines as well as pictures. Note:
663 * floating widgets are painted as fills
664 *
665 * @param g
666 * @param toPaint
667 */
668 private static void PaintPictures(Graphics2D g, List<Item> toPaint,
669 HashSet<Item> fillOnlyItems, HashSet<Item> done) {
670
671 List<Item> toFill = new LinkedList<Item>();
672 for (Item i : toPaint) {
673 // Ignore items that have already been done!
674 // Also ignore invisible items..
675 // TODO possibly ignore invisible items before coming to this
676 // method?
677 if (done.contains(i))
678 continue;
679 if (i instanceof XRayable) {
680 toFill.add(i);
681 done.addAll(i.getConnected());
682 } else if (i.hasEnclosures()) {
683 for (Item enclosure : i.getEnclosures()) {
684 if (!toFill.contains(enclosure))
685 toFill.add(enclosure);
686 }
687 done.addAll(i.getConnected());
688 } else if (i.isLineEnd()
689 && (!isAudienceMode() || !i.isConnectedToAnnotation())) {
690 toFill.add(i);
691 done.addAll(i.getAllConnected());
692 }
693 }
694
695 if (fillOnlyItems != null) {
696 for (Item i : fillOnlyItems) {
697 if (done.contains(i))
698 continue;
699 else if (!isAudienceMode() || !i.isConnectedToAnnotation()) {
700 toFill.add(i);
701 }
702 done.addAll(i.getAllConnected());
703 }
704 }
705
706 // Sort the items to fill
707 Collections.sort(toFill, new Comparator<Item>() {
708 public int compare(Item a, Item b) {
709 Double aArea = a.getEnclosedArea();
710 Double bArea = b.getEnclosedArea();
711 int cmp = aArea.compareTo(bArea);
712 if (cmp == 0) {
713 // System.out.println(a.getEnclosureID() + " " +
714 // b.getID());\
715 // Shapes to the left go underneath
716 Polygon pA = a.getEnclosedShape();
717 Polygon pB = b.getEnclosedShape();
718 if (pA == null || pB == null)
719 return 0;
720 return new Integer(pA.getBounds().x).compareTo(pB
721 .getBounds().x);
722 }
723 return cmp * -1;
724 }
725 });
726 for (Item i : toFill) {
727 if (i instanceof XRayable) {
728 PaintItem(g, i);
729 } else {
730 // Paint the fill and lines
731 i.paintFill(g);
732 List<Line> lines = i.getLines();
733 if (lines.size() > 0)
734 PaintItem(g, lines.get(0));
735 }
736 }
737 }
738
739 /**
740 * Highlights an item on the screen Note: All graphics are handled by the
741 * Item itself.
742 *
743 * @param i
744 * The Item to highlight.
745 * @param val
746 * True if the highlighting is being shown, false if it is being
747 * erased.
748 * @return the item that was highlighted
749 */
750 public static Item Highlight(Item i) {
751 if ((i instanceof Line)) {
752 // Check if within 20% of the end of the line
753 Line l = (Line) i;
754 Item toDisconnect = l.getEndPointToDisconnect(Math
755 .round(FrameMouseActions.MouseX), Math
756 .round(FrameMouseActions.MouseY));
757
758 // Brook: Widget Edges do not have such a context
759 if (toDisconnect != null && !(i instanceof WidgetEdge)) {
760 Item.HighlightMode newMode = toDisconnect.getHighlightMode();
761 if (FreeItems.itemsAttachedToCursor())
762 newMode = Item.HighlightMode.Normal;
763 // unhighlight all the other dots
764 for (Item conn : toDisconnect.getAllConnected()) {
765 conn.setHighlightMode(Item.HighlightMode.None);
766 }
767 l.setHighlightMode(newMode);
768 // highlight the dot that will be in disconnect mode
769 toDisconnect.setHighlightMode(newMode);
770 i = toDisconnect;
771 } else {
772 if(FrameMouseActions.isShiftDown()) {
773 for(Item j : i.getAllConnected()) {
774 if(j instanceof Dot && !j.equals(i)) {
775 j.setHighlightMode(HighlightMode.None);
776 }
777 }
778 l.getStartItem().setHighlightMode(HighlightMode.Connected);
779 l.getEndItem().setHighlightMode(HighlightMode.Connected);
780 } else {
781 for(Item j : i.getAllConnected()) {
782 if(j instanceof Dot && !j.equals(i)) {
783 j.setHighlightMode(HighlightMode.Connected);
784 }
785 }
786 }
787// Collection<Item> connected = i.getAllConnected();
788// for (Item conn : connected) {
789// conn.setHighlightMode(Item.HighlightMode.Connected);
790// }
791 }
792 } else if (i instanceof Circle) {
793 i.setHighlightMode(Item.HighlightMode.Connected);
794 } else if (!i.isVisible()) {
795 changeHighlightMode(i, Item.HighlightMode.Connected, null);
796 } else if (i instanceof Dot) {
797 // highlight the dot
798 if (i.hasPermission(UserAppliedPermission.full)) {
799 changeHighlightMode(i, Item.HighlightMode.Normal, Item.HighlightMode.None);
800 } else {
801 changeHighlightMode(i, Item.HighlightMode.Connected, Item.HighlightMode.Connected);
802 }
803 // highlight connected dots, but only if there aren't items being carried on the cursor
804 if(FreeItems.getInstance().size() == 0) {
805 if(FrameMouseActions.isShiftDown()) {
806 for(Item j : i.getAllConnected()) {
807 if(j instanceof Dot && !j.equals(i)) {
808 j.setHighlightMode(HighlightMode.Connected);
809 }
810 }
811 } else {
812 for(Item j : i.getAllConnected()) {
813 if(j instanceof Dot && !j.equals(i)) {
814 j.setHighlightMode(HighlightMode.None);
815 }
816 }
817 for(Line l : i.getLines()) {
818 Item j = l.getOppositeEnd(i);
819 j.setHighlightMode(HighlightMode.Connected);
820 }
821 }
822 }
823 } else {
824 // FrameGraphics.ChangeSelectionMode(i,
825 // Item.SelectedMode.Normal);
826 // For polygons need to make sure all other endpoints are
827 // unHighlighted
828 if (i.hasPermission(UserAppliedPermission.full))
829 changeHighlightMode(i, Item.HighlightMode.Normal,
830 Item.HighlightMode.None);
831 else
832 changeHighlightMode(i, Item.HighlightMode.Connected,
833 Item.HighlightMode.Connected);
834 }
835 Repaint();
836 return i;
837 }
838
839 public static void changeHighlightMode(Item item, Item.HighlightMode newMode) {
840 changeHighlightMode(item, newMode, newMode);
841 }
842
843 public static void changeHighlightMode(Item item,
844 Item.HighlightMode newMode, Item.HighlightMode connectedNewMode) {
845 if (item == null)
846 return;
847
848 if (item.hasVector()) {
849 for (Item i : item.getParentOrCurrentFrame().getVectorItems()) {
850 if (i.getEditTarget() == item) {
851 i.setHighlightMode(newMode);
852 }
853 }
854 item.setHighlightMode(newMode);
855 } else {
856 // Mike: TODO comment on why the line below is used!!
857 // I forgot already!!Opps
858 boolean freeItem = FreeItems.getInstance().contains(item);
859 for (Item i : item.getAllConnected()) {
860 if (/* freeItem || */!FreeItems.getInstance().contains(i)) {
861 i.setHighlightMode(connectedNewMode);
862 }
863 }
864 if (!freeItem && newMode != connectedNewMode)
865 item.setHighlightMode(newMode);
866 }
867 Repaint();
868 }
869
870 /**
871 * Repaints the buffer of the given Frame.
872 *
873 * @param toUpdate
874 * The Frame whose buffer is to be repainted.
875 */
876
877 public static void UpdateBuffer(Frame toUpdate, boolean paintOverlays,
878 boolean useVolitile) {
879 toUpdate.setBuffer(getBuffer(toUpdate, paintOverlays, useVolitile));
880 }
881
882 public static Image getBuffer(Frame toUpdate, boolean paintOverlays,
883 boolean useVolitile) {
884 if (toUpdate == null)
885 return null;
886
887 return Paint(toUpdate, null, paintOverlays, useVolitile);
888 }
889
890 public static int getMode() {
891 return _Mode;
892 }
893
894 public static Graphics createGraphics() {
895 // Error messages on start up will call this message before
896 // _DisplayGraphics has been initialised
897 if (_DisplayGraphics == null)
898 return null;
899 return _DisplayGraphics.create();
900 }
901
902 // Damaged areas pending to render. Accessessed by multiple threads
903 private static HashSet<Rectangle> damagedAreas = new HashSet<Rectangle>();
904
905 /** The clip used while paiting */
906 private static Area currentClip;
907
908 /**
909 * The current clip that is used for painting at this instant.
910 *
911 * Intention: for extra clipping within an items paint method - the clip is
912 * lost in the graphics object for which can be regained via this method.
913 *
914 * @return The current clip. Null if no clip (e.g. full screen render).
915 */
916 public static Area getCurrentClip() {
917 return (currentClip != null) ? (Area) currentClip.clone() : null;
918 }
919
920 /**
921 * Checks that the item is visible (on current frame && overlays) - if
922 * visible then damaged area will be re-rendered on the next refresh.
923 *
924 * @param damagedItem
925 * @param toRepaint
926 */
927 public static void invalidateItem(Item damagedItem, Rectangle toRepaint) {
928 // Only add area to repaint if item is visible...
929 if (ItemUtils.isVisible(damagedItem)) {
930 synchronized (damagedAreas) {
931 damagedAreas.add(toRepaint);
932 }
933 } else if (MessageBay.isMessageItem(damagedItem)) {
934 MessageBay.addDirtyArea(toRepaint);
935 }
936 }
937
938 /**
939 * The given area will be re-rendered in the next refresh. This is the
940 * quicker version and is more useful for re-rendering animated areas.
941 *
942 * @param toRepaint
943 */
944 public static void invalidateArea(Rectangle toRepaint) {
945 synchronized (damagedAreas) {
946 damagedAreas.add(toRepaint);
947 }
948 }
949
950 public static void clearInvalidAreas() {
951 synchronized (damagedAreas) {
952 damagedAreas.clear();
953 }
954 }
955
956 /**
957 * Invalidates the buffered image of the current Frame and forces it to be
958 * repainted on to the screen. Repaints all items. This is more expensive
959 * than refresh.
960 */
961 public static void ForceRepaint() { // TODO: TEMP: Use refresh
962 Frame current = DisplayIO.getCurrentFrame();
963
964 if (current == null)
965 return;
966 refresh(false);
967 }
968
969 public static void Repaint() { // TODO: TEMP: Use refresh
970 refresh(true);
971 }
972
973 /**
974 * Called to refresh the display screen. Thread safe.
975 */
976 public static void refresh(boolean useInvalidation) {
977
978 if (_DisplayGraphics == null || _MaxSize.width <= 0
979 || _MaxSize.height <= 0)
980 return;
981
982 currentClip = null;
983 if (useInvalidation) { // build clip
984
985 synchronized (damagedAreas) {
986 if (!damagedAreas.isEmpty()) {
987
988 for (Rectangle r : damagedAreas) {
989 if (currentClip == null)
990 currentClip = new Area(r);
991 else
992 currentClip.add(new Area(r));
993 }
994 damagedAreas.clear();
995
996 } else if (MessageBay.isDirty()) {
997 // Paint dirty message bay
998 Graphics dg = _DisplayGraphics.create();
999 MessageBay.refresh(true, dg, Item.DEFAULT_BACKGROUND);
1000 return;
1001
1002 } else
1003 return; // nothing to render
1004 }
1005
1006 } else {
1007 synchronized (damagedAreas) {
1008 damagedAreas.clear();
1009 }
1010 // System.out.println("FULLSCREEN REFRESH"); // TODO: REMOVE
1011 }
1012
1013 Frame[] toPaint = DisplayIO.getFrames();
1014 Image left = Paint(toPaint[0], currentClip);
1015 Image right = Paint(toPaint[1], currentClip);
1016
1017 Graphics dg = _DisplayGraphics.create();
1018
1019 // Paint frame to window
1020 Paint(dg, left, right, Item.DEFAULT_BACKGROUND);
1021
1022 // Paint any animations
1023 PopupManager.getInstance().paintAnimations();
1024
1025 // Paint message bay
1026 MessageBay.refresh(useInvalidation, dg, Item.DEFAULT_BACKGROUND);
1027
1028 dg.dispose();
1029 }
1030
1031 /**
1032 * If wanting to refresh from another thread - other than the main thread
1033 * that handles the expeditee datamodel (modifying / accessing / rendering).
1034 * Use this method for thread safety.
1035 */
1036 public static synchronized void requestRefresh(boolean useInvalidation) {
1037
1038 _requestMarsheller._useInvalidation = useInvalidation;
1039
1040 if (_requestMarsheller._isQueued) {
1041 return;
1042 }
1043
1044 _requestMarsheller._isQueued = true;
1045 EventQueue.invokeLater(_requestMarsheller); // Render on AWT thread
1046 }
1047
1048 private static RenderRequestMarsheller _requestMarsheller = new FrameGraphics().new RenderRequestMarsheller();
1049
1050 /**
1051 * Used for marshelling render requests from foreign threads to the event
1052 * dispatcher thread... (AWT)
1053 *
1054 * @author Brook Novak
1055 */
1056 private class RenderRequestMarsheller implements Runnable {
1057
1058 boolean _useInvalidation = true;
1059
1060 boolean _isQueued = false;
1061
1062 public void run() {
1063 refresh(_useInvalidation);
1064 _isQueued = false;
1065 _useInvalidation = true;
1066 }
1067
1068 }
1069
1070 /**
1071 * Adds a FinalFrameRenderPass to the frame-render pipeline...
1072 *
1073 * Note that the last added FinalFrameRenderPass will be rendered at the
1074 * very top.
1075 *
1076 * @param pass
1077 * The pass to add. If already added then nothing results in the
1078 * call.
1079 *
1080 * @throws NullPointerException
1081 * If pass is null.
1082 */
1083 public static void addFrameRenderPass(FrameRenderPass pass) {
1084 if (pass == null)
1085 throw new NullPointerException("pass");
1086
1087 if (!_frameRenderPasses.contains(pass))
1088 _frameRenderPasses.add(pass);
1089 }
1090
1091 /**
1092 * Adds a FinalFrameRenderPass to the frame-render pipeline...
1093 *
1094 * Note that the last added FinalFrameRenderPass will be rendered at the
1095 * very top.
1096 *
1097 * @param pass
1098 * The pass to remove
1099 *
1100 */
1101 public static void removeFrameRenderPass(FrameRenderPass pass) {
1102 _frameRenderPasses.remove(pass);
1103 }
1104
1105 /**
1106 * A FinalFrameRenderPass is invoked at the very final stages for rendering
1107 * a frame: that is, after the popups are drawn.
1108 *
1109 * There can be many applications for FinalFrameRenderPass. Such as tool
1110 * tips, balloons, or drawing items at the highest Z-order in special
1111 * situations.
1112 *
1113 * Although if there are multiples FinalFrameRenderPasses attatach to the
1114 * frame painter then it is not garaunteed to be rendered very last.
1115 *
1116 * @see FrameGraphics#addFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
1117 * @see FrameGraphics#removeFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
1118 *
1119 * @author Brook Novak
1120 */
1121 public interface FrameRenderPass {
1122
1123 /**
1124 *
1125 * @param currentClip
1126 *
1127 * @return The clip that the pass should use instead. i.e. if there are
1128 * any effects that cannot invladate prior to paint call.
1129 */
1130 Area paintStarted(Area currentClip);
1131
1132 void paintFinalPass(Graphics g);
1133
1134 void paintPreLayeredPanePass(Graphics g);
1135 }
1136
1137}
Note: See TracBrowser for help on using the repository browser.