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

Last change on this file since 980 was 980, checked in by davidb, 9 years ago

Support for frame transitions added

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