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

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

Switch to using specialised objects for settings so they make more a bit more sense (now each setting is a single object instead of multiple, and setter functions and default values are less hackish)
Also added tooltips (help strings) to settings, will need to add a way of displaying these (maybe add the idea of a tooltip which is a text item which only appears when hovering over another item?)
Converted all settings over to new format, everything seems to be working fine

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