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

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

Implement tooltips

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