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

Last change on this file since 616 was 616, checked in by jts21, 11 years ago

Invert line deletion behaviour (now the default is back to the original default), and update highlighting to provide better visual feedback (now hovering over a polygon with shift pressed displays hollow rectangles at it's points, showing that only the outline will be deleted and not the internal items)

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)
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.