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

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

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