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

Last change on this file since 1413 was 1413, checked in by bln4, 5 years ago

Changed surrogates to work the way discussed with David. EncryptedExpReader/Writer updated to work with this.

File size: 20.5 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.util.Collection;
22import java.util.Collections;
23import java.util.Comparator;
24import java.util.HashSet;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.ListIterator;
28
29import org.expeditee.core.Clip;
30import org.expeditee.core.Colour;
31import org.expeditee.core.Dimension;
32import org.expeditee.core.Image;
33import org.expeditee.core.bounds.PolygonBounds;
34import org.expeditee.gio.EcosystemManager;
35import org.expeditee.gio.input.KBMInputEvent.Key;
36import org.expeditee.gio.input.StandardInputEventListeners;
37import org.expeditee.items.Circle;
38import org.expeditee.items.Dot;
39import org.expeditee.items.Item;
40import org.expeditee.items.Item.HighlightMode;
41import org.expeditee.items.Line;
42import org.expeditee.items.UserAppliedPermission;
43import org.expeditee.items.XRayable;
44import org.expeditee.items.widgets.Widget;
45import org.expeditee.items.widgets.WidgetEdge;
46import org.expeditee.settings.UserSettings;
47
48public class FrameGraphics {
49
50 // Final passes to rendering the current frame
51 private static LinkedList<FrameRenderPass> _frameRenderPasses = new LinkedList<FrameRenderPass>();
52
53 private static Item _lastToolTippedItem = null;
54
55 /** Static-only class. */
56 private FrameGraphics()
57 {
58 }
59
60 /**
61 * Gets an image of the given frame that has the given dimensions. If clip
62 * is not null, only the areas inside clip are guaranteed to be drawn.
63 */
64 public static Image getFrameImage(Frame toPaint, Clip clip, Dimension size)
65 {
66 return getFrameImage(toPaint, clip, size, true, true);
67 }
68
69 /**
70 * Gets an image of the given frame that has the given dimensions. If clip
71 * is not null, only the areas inside clip are guaranteed to be drawn.
72 */
73 public static Image getFrameImage(Frame toPaint, Clip clip, Dimension size, boolean isActualFrame, boolean createVolatile)
74 {
75 if (toPaint == null) {
76 return null;
77 }
78
79 // the buffer is not valid, so it must be recreated
80 if (!toPaint.isBufferValid()) {
81
82 Image buffer = toPaint.getBuffer();
83 // Get the size if it hasn't been given
84 if (size == null) {
85 // Can't get the size if there is no buffer
86 if (buffer == null) {
87 return null;
88 } else {
89 size = buffer.getSize();
90 }
91 }
92
93 if (buffer == null || !buffer.getSize().equals(size)) {
94 buffer = Image.createImage(size.width, size.height, createVolatile);
95 toPaint.setBuffer(buffer);
96 clip = null;
97 }
98
99 EcosystemManager.getGraphicsManager().pushDrawingSurface(buffer);
100 EcosystemManager.getGraphicsManager().pushClip(clip);
101 paintFrame(toPaint, isActualFrame, createVolatile);
102 EcosystemManager.getGraphicsManager().popDrawingSurface();
103 }
104
105 return toPaint.getBuffer();
106 }
107
108 /** TODO: Comment. cts16 */
109 public static void paintFrame(Frame toPaint, boolean isActualFrame, boolean createVolitile)
110 {
111 Clip clip = EcosystemManager.getGraphicsManager().peekClip();
112
113 // Prepare render passes
114 if (isActualFrame) {
115 for (FrameRenderPass pass : _frameRenderPasses) {
116 clip = pass.paintStarted(clip);
117 }
118 }
119
120 // TODO: Revise images and clip - VERY IMPORTANT
121
122 // Nicer looking lines, but may be too jerky while
123 // rubber-banding on older machines
124 if (UserSettings.AntiAlias.get()) {
125 EcosystemManager.getGraphicsManager().setAntialiasing(true);
126 }
127
128 // If we are doing @f etc... then have a clear background if its the default background color
129 Colour backgroundColor = null;
130
131 // Need to allow transparency for frameImages
132 if (createVolitile) {
133 backgroundColor = toPaint.getPaintBackgroundColor();
134 } else {
135 backgroundColor = toPaint.getBackgroundColor();
136 if (backgroundColor == null) {
137 backgroundColor = Item.TRANSPARENT;
138 }
139 }
140
141 EcosystemManager.getGraphicsManager().clear(backgroundColor);
142
143 List<Item> visibleItems = new LinkedList<Item>();
144 List<Widget> paintWidgets;
145
146 if (isActualFrame) {
147 // Add all the items for this frame and any other from other
148 // frames
149 visibleItems.addAll(toPaint.getAllItems());
150 paintWidgets = toPaint.getAllOverlayWidgets();
151 paintWidgets.addAll(toPaint.getInteractiveWidgets());
152 } else {
153 visibleItems.addAll(toPaint.getVisibleItems());
154 visibleItems.addAll(toPaint.getVectorItems());
155 paintWidgets = toPaint.getInteractiveWidgets();
156 }
157
158 HashSet<Item> paintedFillsAndLines = new HashSet<Item>();
159 // FIRST: Paint widgets swing gui (not expeditee gui) .
160 // Note that these are the anchored widgets
161 ListIterator<Widget> widgetItor = paintWidgets.listIterator(paintWidgets.size());
162 while (widgetItor.hasPrevious()) {
163 // Paint first-in-last-serve ordering - like swing
164 // If it is done the other way around then widgets are covered up by
165 // the box that is supposed to be underneath
166 Widget iw = widgetItor.previous();
167 if (clip == null || clip.getBounds() == null || clip.getBounds().intersects(iw.getBounds())) {
168 paintedFillsAndLines.addAll(iw.getItems());
169 //iw.paint(bg);
170 //PaintItem(bg, iw.getItems().get(4));
171
172 }
173 }
174
175
176 // Filter out items that do not need to be painted
177 List<Item> paintItems;
178 HashSet<Item> fillOnlyItems = null; // only contains items that do
179 // not need drawing but fills
180 // might
181
182 if (clip == null) {
183 paintItems = visibleItems;
184 } else {
185 fillOnlyItems = new HashSet<Item>();
186 paintItems = new LinkedList<Item>();
187 for (Item i : visibleItems) {
188 if (clip == null || i.isInDrawingArea(clip.getBounds())) {
189 paintItems.add(i);
190 } else if (i.isEnclosed()) {
191 // just add all fill items despite possibility of fills
192 // not being in clip
193 // because it will be faster than having to test twice
194 // for fills that do need
195 // repainting.
196 fillOnlyItems.add(i);
197 }
198 }
199 }
200
201 // Only paint files and lines once ... between anchored AND free items
202 PaintPictures(paintItems, fillOnlyItems, paintedFillsAndLines);
203 PaintLines(visibleItems);
204
205
206 widgetItor = paintWidgets.listIterator(paintWidgets.size());
207 while (widgetItor.hasPrevious()) {
208 // Paint first-in-last-serve ordering - like swing
209 // If it is done the other way around then widgets are covered up by
210 // the box that is supposed to be underneath
211 Widget iw = widgetItor.previous();
212 if (clip == null || clip.isNotClipped() || clip.getBounds().intersects(iw.getClip().getBounds())) {
213 iw.paint();
214 PaintItem(iw.getItems().get(4));
215 }
216 }
217
218
219 // Filter out free items that do not need to be painted
220 // This is efficient in cases with animation while free items exist
221
222 List<Item> freeItemsToPaint = new LinkedList<Item>();
223 // Dont paint the free items for the other frame in twin frames mode
224 // if (toPaint == DisplayIO.getCurrentFrame()) {
225 if (clip == null || clip.isNotClipped()) {
226 freeItemsToPaint = FreeItems.getInstance();
227 } else {
228 freeItemsToPaint = new LinkedList<Item>();
229 fillOnlyItems.clear();
230 for (Item i : FreeItems.getInstance()) {
231 if (i.isInDrawingArea(clip.getBounds())) {
232 freeItemsToPaint.add(i);
233 } else if (i.isEnclosed()) {
234 fillOnlyItems.add(i);
235 }
236 }
237 }
238 // }
239
240 if (isActualFrame && toPaint == DisplayController.getCurrentFrame()) {
241 PaintPictures(freeItemsToPaint, fillOnlyItems, paintedFillsAndLines);
242 }
243
244 // TODO if we can get transparency with FreeItems.getInstance()...
245 // then text can be done before freeItems
246 PaintNonLinesNonPicture(paintItems);
247
248 // toPaint.setBufferValid(true);
249
250 if (isActualFrame && !DisplayController.isAudienceMode()) {
251 PaintItem(toPaint.getNameItem());
252 }
253
254 if (DisplayController.isTwinFramesOn()) {
255 List<Item> lines = new LinkedList<Item>();
256 for (Item i : freeItemsToPaint) {
257 if (i instanceof Line) {
258 Line line = (Line) i;
259
260 if (toPaint == DisplayController.getCurrentFrame()) {
261 // If exactly one end of the line is floating...
262
263 if (line.getEndItem().isFloating()
264 ^ line.getStartItem().isFloating()) {
265 // Line l = TransposeLine(line,
266 // line.getEndItem(),
267 // toPaint, 0, 0);
268 // if (l == null)
269 // l = TransposeLine(line,
270 // line.getStartItem(), toPaint, 0, 0);
271 // if (l == null)
272 // l = line;
273 // lines.add(l);
274 } else {
275 // lines.add(line);
276 }
277 } else {
278 // if (line.getEndItem().isFloating()
279 // ^ line.getStartItem().isFloating()) {
280 // lines.add(TransposeLine(line,
281 // line.getEndItem(), toPaint,
282 // FrameMouseActions.getY(), -DisplayIO
283 // .getMiddle()));
284 // lines.add(TransposeLine(line, line
285 // .getStartItem(), toPaint,
286 // FrameMouseActions.getY(), -DisplayIO
287 // .getMiddle()));
288 // }
289 }
290 }
291 }
292
293 if (isActualFrame) {
294 PaintLines(lines);
295 }
296 } else {
297 if (isActualFrame) {
298 PaintLines(freeItemsToPaint);
299 }
300 }
301
302 if (isActualFrame && toPaint == DisplayController.getCurrentFrame()) {
303 PaintNonLinesNonPicture(freeItemsToPaint);
304 }
305
306 // Repaint popups / drags... As well as final passes
307 if (isActualFrame) {
308 for (FrameRenderPass pass : _frameRenderPasses) {
309 pass.paintPreLayeredPanePass();
310 }
311
312 //if (PopupManager.getInstance() != null) PopupManager.getInstance().paintLayeredPane(clip == null ? null : clip.getBounds());
313
314 for (FrameRenderPass pass : _frameRenderPasses) {
315 pass.paintFinalPass();
316 }
317 }
318
319 // paint tooltip
320 if(!FreeItems.hasItemsAttachedToCursor()) {
321 Item current = FrameUtils.getCurrentItem();
322 if(current != null) {
323 current.paintTooltip();
324 }
325 if (_lastToolTippedItem != null) {
326 _lastToolTippedItem.clearTooltips();
327 }
328 _lastToolTippedItem = current;
329 }
330
331 if (FreeItems.hasCursor() && DisplayController.getCursor() == Item.DEFAULT_CURSOR) {
332 PaintNonLinesNonPicture(FreeItems.getCursor());
333 }
334 }
335
336 private static void PaintNonLinesNonPicture(List<Item> toPaint)
337 {
338 for (Item i : toPaint) {
339 if (!(i instanceof Line) && !(i instanceof XRayable)) {
340 PaintItem(i);
341 }
342 }
343 }
344
345 /**
346 * Paint the lines that are not part of an enclosure.
347 *
348 * @param g
349 * @param toPaint
350 */
351 private static void PaintLines(List<Item> toPaint)
352 {
353 // Use this set to keep track of the items that have been painted
354 Collection<Item> done = new HashSet<Item>();
355 for (Item i : toPaint) {
356 if (i instanceof Line) {
357 Line l = (Line) i;
358 if (done.contains(l)) {
359 l.paintArrows();
360 } else {
361 // When painting a line all connected lines are painted too
362 done.addAll(l.getAllConnected());
363 if (l.getStartItem().getEnclosedArea() == 0) {
364 PaintItem(i);
365 }
366 }
367 }
368 }
369 }
370
371 /**
372 * Paint filled areas and their surrounding lines as well as pictures. Note:
373 * floating widgets are painted as fills
374 *
375 * @param g
376 * @param toPaint
377 */
378 private static void PaintPictures(List<Item> toPaint, HashSet<Item> fillOnlyItems, HashSet<Item> done)
379 {
380
381 List<Item> toFill = new LinkedList<Item>();
382 for (Item i : toPaint) {
383 // Ignore items that have already been done!
384 // Also ignore invisible items..
385 // TODO possibly ignore invisible items before coming to this method?
386 if (done.contains(i)) {
387 continue;
388 }
389
390 if (i instanceof XRayable) {
391 toFill.add(i);
392 done.addAll(i.getConnected());
393 } else if (i.hasEnclosures()) {
394 for (Item enclosure : i.getEnclosures()) {
395 if (!toFill.contains(enclosure)) {
396 toFill.add(enclosure);
397 }
398 }
399 done.addAll(i.getConnected());
400 } else if (i.isLineEnd() && (!DisplayController.isAudienceMode() || !i.isConnectedToAnnotation())) {
401 toFill.add(i);
402 done.addAll(i.getAllConnected());
403 }
404 }
405
406 if (fillOnlyItems != null) {
407 for (Item i : fillOnlyItems) {
408 if (done.contains(i)) {
409 continue;
410 } else if (!DisplayController.isAudienceMode() || !i.isConnectedToAnnotation()) {
411 toFill.add(i);
412 }
413 done.addAll(i.getAllConnected());
414 }
415 }
416
417 // Sort the items to fill
418 Collections.sort(toFill, new Comparator<Item>() {
419 @Override
420 public int compare(Item a, Item b) {
421 Double aArea = a.getEnclosedArea();
422 Double bArea = b.getEnclosedArea();
423 int cmp = aArea.compareTo(bArea);
424 if (cmp == 0) {
425 // Shapes to the left go underneath
426 PolygonBounds pA = a.getEnclosedShape();
427 PolygonBounds pB = b.getEnclosedShape();
428 if (pA == null || pB == null) {
429 return 0;
430 }
431 return new Integer(pA.getMinX()).compareTo(pB.getMinX());
432 }
433 return cmp * -1;
434 }
435 });
436
437 for (Item i : toFill) {
438 if (i instanceof XRayable) {
439 PaintItem(i);
440 } else {
441 // Paint the fill and lines
442 i.paintFill();
443 List<Line> lines = i.getLines();
444 if (lines.size() > 0) {
445 PaintItem(lines.get(0));
446 }
447 }
448 }
449 }
450
451 /** Displays the given Item on the screen. */
452 public static void PaintItem(Item i)
453 {
454 if (i == null) {
455 return;
456 }
457
458 // do not paint annotation items in audience mode
459 if (!DisplayController.isAudienceMode() || (!i.isConnectedToAnnotation() && !i.isAnnotation()) || i == FrameUtils.getLastEdited())
460 {
461 i.paint();
462 }
463 }
464
465 /**
466 * Highlights an item on the screen Note: All graphics are handled by the
467 * Item itself.
468 *
469 * @param i
470 * The Item to highlight.
471 * @param val
472 * True if the highlighting is being shown, false if it is being
473 * erased.
474 * @return the item that was highlighted
475 */
476 public static Item Highlight(Item i) {
477 if ((i instanceof Line)) {
478 // Check if within 20% of the end of the line
479 Line l = (Line) i;
480 Item toDisconnect = l.getEndPointToDisconnect(DisplayController.getMousePosition());
481
482 // Brook: Widget Edges do not have such a context
483 if (toDisconnect != null && !(i instanceof WidgetEdge)) {
484 Item.HighlightMode newMode = toDisconnect.getHighlightMode();
485 if (FreeItems.hasItemsAttachedToCursor()) {
486 newMode = Item.HighlightMode.Normal;
487 }
488 // unhighlight all the other dots
489 for (Item conn : toDisconnect.getAllConnected()) {
490 conn.setHighlightMode(Item.HighlightMode.None);
491 conn.setHighlightColorToDefault();
492 }
493 l.setHighlightMode(newMode);
494 l.setHighlightColorToDefault();
495 // highlight the dot that will be in disconnect mode
496 toDisconnect.setHighlightMode(newMode);
497 toDisconnect.setHighlightColorToDefault();
498 i = toDisconnect;
499 } else {
500 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
501 for(Item j : i.getAllConnected()) {
502 if(j instanceof Dot && !j.equals(i)) {
503 j.setHighlightMode(HighlightMode.None);
504 j.setHighlightColorToDefault();
505 }
506 }
507 l.getStartItem().setHighlightMode(HighlightMode.Connected);
508 l.getStartItem().setHighlightColorToDefault();
509 l.getEndItem().setHighlightMode(HighlightMode.Connected);
510 l.getEndItem().setHighlightColorToDefault();
511 } else {
512 for(Item j : i.getAllConnected()) {
513 if(j instanceof Dot && !j.equals(i)) {
514 j.setHighlightMode(HighlightMode.Connected);
515 j.setHighlightColorToDefault();
516 }
517 }
518 }
519// Collection<Item> connected = i.getAllConnected();
520// for (Item conn : connected) {
521// conn.setHighlightMode(Item.HighlightMode.Connected);
522// }
523 }
524 } else if (i instanceof Circle) {
525 i.setHighlightMode(Item.HighlightMode.Connected);
526 i.setHighlightColorToDefault();
527 } else if (!i.isVisible()) {
528 changeHighlightMode(i, Item.HighlightMode.Connected, null);
529 } else if (i instanceof Dot) {
530 // highlight the dot
531 if (i.hasPermission(UserAppliedPermission.full)) {
532 changeHighlightMode(i, Item.HighlightMode.Normal, Item.HighlightMode.None);
533 } else {
534 changeHighlightMode(i, Item.HighlightMode.Connected, Item.HighlightMode.Connected);
535 }
536 // highlight connected dots, but only if there aren't items being carried on the cursor
537 if(FreeItems.getInstance().size() == 0) {
538 if (StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT)) {
539 for(Item j : i.getAllConnected()) {
540 if(j instanceof Dot && !j.equals(i)) {
541 j.setHighlightMode(HighlightMode.Connected);
542 j.setHighlightColorToDefault();
543 }
544 }
545 } else {
546 for(Item j : i.getAllConnected()) {
547 if(j instanceof Dot && !j.equals(i)) {
548 j.setHighlightMode(HighlightMode.None);
549 j.setHighlightColorToDefault();
550 }
551 }
552 for(Line l : i.getLines()) {
553 Item j = l.getOppositeEnd(i);
554 j.setHighlightMode(HighlightMode.Connected);
555 j.setHighlightColorToDefault();
556 }
557 }
558 }
559 } else {
560 // FrameGraphics.ChangeSelectionMode(i,
561 // Item.SelectedMode.Normal);
562 // For polygons need to make sure all other endpoints are
563 // unHighlighted
564 if (i.hasPermission(UserAppliedPermission.full)) {
565 changeHighlightMode(i, Item.HighlightMode.Normal, Item.HighlightMode.None);
566 } else {
567 changeHighlightMode(i, Item.HighlightMode.Connected, Item.HighlightMode.Connected);
568 }
569 }
570 DisplayController.requestRefresh(true);
571 return i;
572 }
573
574 public static void changeHighlightMode(Item item, Item.HighlightMode newMode)
575 {
576 changeHighlightMode(item, newMode, newMode);
577 }
578
579 public static void changeHighlightMode(Item item, Item.HighlightMode newMode, Item.HighlightMode connectedNewMode)
580 {
581 if (item == null) {
582 return;
583 }
584
585 if (item.hasVector()) {
586 for (Item i : item.getParentOrCurrentFrame().getVectorItems()) {
587 if (i.getEditTarget() == item) {
588 i.setHighlightMode(newMode);
589 i.setHighlightColorToDefault();
590 }
591 }
592 item.setHighlightMode(newMode);
593 item.setHighlightColorToDefault();
594 } else {
595 // Mike: TODO comment on why the line below is used!!
596 // I forgot already!! Oops
597 boolean freeItem = FreeItems.getInstance().contains(item);
598 for (Item i : item.getAllConnected()) {
599 if (/* freeItem || */!FreeItems.getInstance().contains(i)) {
600 i.setHighlightMode(connectedNewMode);
601 i.setHighlightColorToDefault();
602 }
603 }
604 if (!freeItem && newMode != connectedNewMode) {
605 item.setHighlightMode(newMode);
606 item.setHighlightColorToDefault();
607 }
608 }
609 DisplayController.requestRefresh(true);
610 }
611
612 /*
613 *
614 * FrameRenderPass stuff. (TODO: Not sure if this is used for anything? In Apollo. cts16)
615 *
616 */
617
618 /**
619 * Adds a FinalFrameRenderPass to the frame-render pipeline...
620 *
621 * Note that the last added FinalFrameRenderPass will be rendered at the
622 * very top.
623 *
624 * @param pass
625 * The pass to add. If already added then nothing results in the
626 * call.
627 *
628 * @throws NullPointerException
629 * If pass is null.
630 */
631 public static void addFrameRenderPass(FrameRenderPass pass) {
632 if (pass == null) {
633 throw new NullPointerException("pass");
634 }
635
636 if (!_frameRenderPasses.contains(pass)) {
637 _frameRenderPasses.add(pass);
638 }
639 }
640
641 /**
642 * Adds a FinalFrameRenderPass to the frame-render pipeline...
643 *
644 * Note that the last added FinalFrameRenderPass will be rendered at the
645 * very top.
646 *
647 * @param pass
648 * The pass to remove
649 *
650 */
651 public static void removeFrameRenderPass(FrameRenderPass pass) {
652 _frameRenderPasses.remove(pass);
653 }
654
655 /**
656 * A FinalFrameRenderPass is invoked at the very final stages for rendering
657 * a frame: that is, after the popups are drawn.
658 *
659 * There can be many applications for FinalFrameRenderPass. Such as tool
660 * tips, balloons, or drawing items at the highest Z-order in special
661 * situations.
662 *
663 * Although if there are multiples FinalFrameRenderPasses attach to the
664 * frame painter then it is not guaranteed to be rendered very last.
665 *
666 * @see FrameGraphics#addFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
667 * @see FrameGraphics#removeFinalFrameRenderPass(org.expeditee.gui.FrameGraphics.FrameRenderPass)
668 *
669 * @author Brook Novak
670 */
671 public interface FrameRenderPass {
672
673 /**
674 *
675 * @param currentClip
676 *
677 * @return The clip that the pass should use instead. i.e. if there are
678 * any effects that cannot invalidate prior to paint call.
679 */
680 Clip paintStarted(Clip currentClip);
681
682 void paintFinalPass();
683
684 void paintPreLayeredPanePass();
685 }
686
687}
Note: See TracBrowser for help on using the repository browser.