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

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

Fixed bug with surrogate inheritance.

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