source: trunk/src/org/expeditee/gui/DisplayController.java@ 1171

Last change on this file since 1171 was 1171, checked in by bln4, 6 years ago
File size: 43.8 KB
Line 
1/**
2 * DisplayIO.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.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.HashSet;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Stack;
28import java.util.function.BooleanSupplier;
29
30import org.expeditee.core.Clip;
31import org.expeditee.core.Colour;
32import org.expeditee.core.Cursor;
33import org.expeditee.core.DamageAreas;
34import org.expeditee.core.Dimension;
35import org.expeditee.core.EnforcedClipStack.EnforcedClipKey;
36import org.expeditee.core.Image;
37import org.expeditee.core.InOutReference;
38import org.expeditee.core.Line;
39import org.expeditee.core.Point;
40import org.expeditee.core.Stroke;
41import org.expeditee.core.bounds.AxisAlignedBoxBounds;
42import org.expeditee.core.bounds.Bounds;
43import org.expeditee.gio.EcosystemManager;
44import org.expeditee.gio.GraphicsManager;
45import org.expeditee.gio.gesture.StandardGestureActions;
46import org.expeditee.items.Item;
47import org.expeditee.items.ItemParentStateChangedEvent;
48import org.expeditee.items.ItemUtils;
49import org.expeditee.items.Picture;
50import org.expeditee.items.Text;
51import org.expeditee.items.MagneticConstraint.MagneticConstraints;
52import org.expeditee.settings.UserSettings;
53import org.expeditee.stats.SessionStats;
54import org.expeditee.taskmanagement.EntitySaveManager;
55
56/**
57 * Controls the layout of the frames inside the display.
58 *
59 * @author jdm18
60 * @author cts16
61 */
62public final class DisplayController {
63
64 /** Enumeration of the two sides of twin-frames mode. */
65 public static enum TwinFramesSide {
66 LEFT,
67 RIGHT
68 }
69
70 /** Convenience definition of TwinFramesSide.LEFT. */
71 public static final TwinFramesSide LEFT = TwinFramesSide.LEFT;
72 /** Convenience definition of TwinFramesSide.RIGHT. */
73 public static final TwinFramesSide RIGHT = TwinFramesSide.RIGHT;
74
75 /** The side-length of a small cursor (in pixels). */
76 public static final int SMALL_CURSOR_SIZE = 16;
77 /** The side-length of a medium cursor (in pixels). */
78 public static final int MEDIUM_CURSOR_SIZE = 32;
79 /** The side-length of a large cursor (in pixels). */
80 public static final int LARGE_CURSOR_SIZE = 64;
81
82 /** The height of the message bay at program start. */
83 public static final int INITIAL_MESSAGE_BAY_HEIGHT = 105;
84
85 /** The colour to be used to highlight the linked parent item, when the user navigates backwards. */
86 public static final Colour BACK_HIGHLIGHT_COLOR = Colour.MAGENTA;
87
88 /** The stroke used to draw the separating lines between the display areas. */
89 protected static final Stroke SEPARATOR_STROKE = new Stroke(1);
90 /** The colour used to draw the separating lines between the display areas. */
91 protected static final Colour SEPARATOR_COLOUR = Colour.BLACK;
92
93 /** The title to display in the Title bar. */
94 public static final String TITLE = "Expeditee";
95
96 /** The image to use as the window icon. */
97 public static final String ICON_IMAGE = "org/expeditee/assets/icons/expediteeicon128.png";
98
99 /** The current frame being displayed on each side of the window. */
100 private static Frame[] _currentFrames = new Frame[2];
101
102 /** The transitions to use on each side when changing frame. */
103 private static FrameTransition[] _transitions = new FrameTransition[2];
104
105 /** Maintains the list of frames visited thus-far for back-tracking. */
106 @SuppressWarnings("unchecked")
107 private static Stack<String>[] _visitedFrames = new Stack[2];
108
109 /** TODO: Comment. cts16 */
110 @SuppressWarnings("unchecked")
111 private static Stack<String>[] _backedUpFrames = new Stack[2];
112
113 /** Whether we are currently in twin-frames mode. */
114 private static boolean _twinFramesMode = false;
115
116 /** Whether we are currently displaying in audience mode. */
117 private static boolean _audienceMode = false;
118
119 /** Whether we are currently displaying in x-ray mode. */
120 private static boolean _xrayMode = false;
121
122 /** Notified whenever the frame changes. */
123 private static HashSet<DisplayObserver> _displayObservers = new HashSet<DisplayObserver>();
124
125 /** What type of cursor we are using. */
126 private static Cursor.CursorType _cursorType = Item.DEFAULT_CURSOR;
127
128 /** The size of the window which this class controls. */
129 private static Dimension _windowSize = null;
130
131 /** The area in the window where the left frame should be painted in twin-frames mode. */
132 private static AxisAlignedBoxBounds _leftFramePaintArea = null;
133 /** The area in the window where the right frame should be painted in twin-frames mode. */
134 private static AxisAlignedBoxBounds _rightFramePaintArea = null;
135 /** The area in the window where the frame should be painted in single-frame mode. */
136 private static AxisAlignedBoxBounds _framePaintArea = null;
137 /** The area in the window where the message bay should be painted. */
138 private static AxisAlignedBoxBounds _messageBayPaintArea = null;
139
140 /** The height of the message bay. */
141 private static int _messageBayHeight = INITIAL_MESSAGE_BAY_HEIGHT;
142 /** The percentage of the display width allocated to the left frame. */
143 private static float _twinFramesLeftWidthProportion = 0.5f;
144
145
146 /** The damage areas accumulated by item changes. */
147 private static DamageAreas _damagedAreas = new DamageAreas();
148
149 /** The buffered display image for preventing flickering during rendering. */
150 private static Image _refreshBuffer = null;
151
152 /** Static-only class. */
153 private DisplayController()
154 {
155 }
156
157 /** Initialises the display controller. */
158 public static void Init()
159 {
160 _visitedFrames[0] = new Stack<String>();
161 _visitedFrames[1] = new Stack<String>();
162 _backedUpFrames[0] = new Stack<String>();
163 _backedUpFrames[1] = new Stack<String>();
164
165 refreshCursor();
166 refreshWindowSize();
167 }
168
169 /** Notifies observers that the frame has changed. */
170 private static void fireFrameChanged()
171 {
172 for (DisplayObserver observer : _displayObservers) {
173 observer.frameChanged();
174 }
175 }
176
177 /**
178 * Adds a DisplayObserver to the display controller. DisplayObserver's are
179 * notified when frame changes.
180 *
181 * @see #removeDisplayObserver(DisplayObserver)
182 *
183 * @param observer
184 * The observer to add
185 *
186 * @throws NullPointerException
187 * If observer is null.
188 */
189 public static void addDisplayObserver(DisplayObserver observer)
190 {
191 if (observer == null) {
192 throw new NullPointerException("observer");
193 }
194
195 _displayObservers.add(observer);
196 }
197
198 /**
199 * Removes a DisplayObserver from the display controller.
200 *
201 * @see #addDisplayObserver(DisplayObserver)
202 *
203 * @param observer
204 * The observer to add
205 *
206 * @throws NullPointerException
207 * If observer is null.
208 */
209 public static void removeDisplayObserver(DisplayObserver observer)
210 {
211 if (observer == null) {
212 throw new NullPointerException("observer");
213 }
214
215 _displayObservers.remove(observer);
216 }
217
218 /**
219 * TODO: Comment. cts16
220 * TODO: Move. Doesn't belong here. cts16
221 */
222 public static void setTextCursor(Text text, int cursorMovement)
223 {
224 setTextCursor(text, cursorMovement, false, false, false, false);
225 }
226
227 /**
228 * TODO: Comment. cts16
229 * TODO: Refactor. Too monolithic. cts16
230 * TODO: Move. Doesn't belong here. cts16
231 */
232 public static void setTextCursor(Text text, int cursorMovement,
233 boolean newSize, boolean isShiftDown, boolean isCtrlDown,
234 boolean allowClearSelection)
235 {
236
237 int size = Math.round(text.getSize());
238
239 if (allowClearSelection && !isShiftDown && text.hasSelection()) {
240 text.clearSelection();
241 }
242
243 Point newMouse = text.moveCursor(cursorMovement, DisplayController.getFloatMouseX(), EcosystemManager.getInputManager().getCursorPosition().getY(), isShiftDown, isCtrlDown);
244
245 if( isCtrlDown ||
246 (DisplayController.getFloatMouseX() <= newMouse.getX() && newMouse.getX() <= DisplayController.getFloatMouseX() + 1) ||
247 (DisplayController.getFloatMouseX() > newMouse.getX() && cursorMovement == Text.RIGHT))
248 {
249 if(cursorMovement == Text.RIGHT && !MagneticConstraints.getInstance().rightBorderHit(text)) {
250 MagneticConstraints.getInstance().endOfLineHit(text);
251 } else {
252 if(cursorMovement == Text.LEFT && !MagneticConstraints.getInstance().leftBorderHit(text)) {
253 MagneticConstraints.getInstance().startOfLineHit(text);
254 }
255 }
256 newMouse.setX((int) DisplayController.getFloatMouseX());
257 newMouse.setY((int) DisplayController.getFloatMouseY());
258 } else if(cursorMovement == Text.UP && MagneticConstraints.getInstance().topBorderHit(text)) {
259 newMouse.setX((int) DisplayController.getFloatMouseX());
260 newMouse.setY((int) DisplayController.getFloatMouseY());
261 } else if(cursorMovement == Text.DOWN && MagneticConstraints.getInstance().bottomBorderHit(text)) {
262 newMouse.setX((int) DisplayController.getFloatMouseX());
263 newMouse.setY((int) DisplayController.getFloatMouseY());
264 }
265
266 if (!newSize && _cursorType == Item.TEXT_CURSOR) {
267 if (cursorMovement != 0) {
268 DisplayController.setCursorPosition(newMouse, false);
269 }
270 return;
271 }
272
273 _cursorType = Item.TEXT_CURSOR;
274
275 // Do some stuff to adjust the cursor size based on the font size
276 final int MEDIUM_CURSOR_CUTOFF = 31;
277 final int LARGE_CURSOR_CUTOFF = 62;
278
279 int cursorSize = LARGE_CURSOR_SIZE;
280 int hotspotPos = 0;
281 int start = 0;
282
283 Dimension best_cursor_dim = EcosystemManager.getGraphicsManager().getBestCursorSize(new Dimension(cursorSize,cursorSize));
284 int best_cursor_height = best_cursor_dim.height;
285
286 if (best_cursor_height < cursorSize) {
287 // not able to provide the size of cursor Expeditee wants to
288 // => lock on to 'best_cursor_height' and use this to generate dependent values
289 cursorSize = best_cursor_height; // OS + Java version dependent: most likely MEDIUM_CURSOR_SIZE
290 if (size < best_cursor_height) {
291 start = cursorSize - size - 1;
292 hotspotPos = cursorSize - (size + 1) / 4;
293 }
294 else {
295 start = size - best_cursor_height;
296 hotspotPos = cursorSize -1;
297 }
298
299 }
300 else if (size < MEDIUM_CURSOR_CUTOFF) {
301 cursorSize = MEDIUM_CURSOR_SIZE;
302 start = cursorSize - size - 1;
303 hotspotPos = cursorSize - (size + 1) / 4;
304 } else if (size < LARGE_CURSOR_CUTOFF) {
305 hotspotPos = cursorSize - (size - 5) / 4;
306 start = cursorSize - size - 2;
307 } else {
308 int FIXED_CURSOR_MIN = 77;
309 if (size >= FIXED_CURSOR_MIN) {
310 hotspotPos = cursorSize - 2;
311 } else {
312 hotspotPos = size - (FIXED_CURSOR_MIN - cursorSize);
313 }
314 }
315
316 int[] pixels = new int[cursorSize * cursorSize];
317
318 for (int i = start; i < cursorSize; i++) {
319 pixels[i * cursorSize] = pixels[i * cursorSize + 1] = 0xFF000000;
320 }
321
322 Image image = Image.createImage(cursorSize, cursorSize, pixels);
323
324 EcosystemManager.getGraphicsManager().setCursor(new Cursor(image, new Point(0, hotspotPos), "textcursor"));
325
326 if (cursorMovement != Text.NONE) {
327 DisplayController.setCursorPosition(newMouse, false);
328 }
329 }
330
331 /**
332 * Sets the type of cursor the display should be using
333 *
334 * @param type
335 * The type of cursor to display, using constants defined in the
336 * Cursor class.
337 */
338 public static void setCursor(Cursor.CursorType type)
339 {
340 // Avoid flicker when not changing
341 if (type == _cursorType) {
342 return;
343 }
344
345 _cursorType = type;
346
347 refreshCursor();
348 }
349
350 /** Gets the type of cursor the display is currently using. */
351 public static Cursor.CursorType getCursor()
352 {
353 return _cursorType;
354 }
355
356 /** Updates the cursor with the graphics manager. */
357 private static void refreshCursor()
358 {
359 if (_cursorType == Item.HIDDEN_CURSOR || (FreeItems.hasCursor() && _cursorType == Item.DEFAULT_CURSOR)) {
360 Cursor invisibleCursor = Cursor.createInvisibleCursor();
361 EcosystemManager.getGraphicsManager().setCursor(invisibleCursor);
362 } else {
363 EcosystemManager.getGraphicsManager().setCursor(new Cursor(_cursorType));
364 }
365 }
366
367 /**
368 * Moves the mouse cursor to the given x,y coordinates on the screen
369 *
370 * @param x
371 * The x coordinate
372 * @param y
373 * The y coordinate
374 */
375 public static void setCursorPosition(float x, float y)
376 {
377 setCursorPosition(x, y, true);
378 }
379
380 /** TODO: Comment. cts16 */
381 public static void setCursorPosition(float x, float y, boolean forceArrow)
382 {
383 // Adjust the position to move the mouse to to account for being in
384 // TwinFramesMode
385 if (isTwinFramesOn()) {
386 if (getCurrentSide() == RIGHT) {
387 int middle = getTwinFramesSeparatorX();
388 x += middle;
389 }
390 }
391
392 if (FreeItems.hasItemsAttachedToCursor()) {
393 Point mousePos = EcosystemManager.getInputManager().getCursorPosition();
394 float deltax = x - mousePos.getX();
395 float deltay = y - mousePos.getY();
396 List<Item> toMove = FreeItems.getInstance();
397 for (Item move : toMove) {
398 move.setPosition(move.getX() + deltax, move.getY() + deltay);
399 }
400 }
401
402 // cheat
403 StandardGestureActions.setForceArrow(forceArrow);
404 EcosystemManager.getInputManager().setCursorPosition(new Point(x, y));
405 }
406
407 public static void resetCursorOffset()
408 {
409 StandardGestureActions.resetOffset();
410 }
411
412 /**
413 * Sets the current cursor position in the current frame
414 *
415 * @param pos
416 */
417 public static void setCursorPosition(Point pos)
418 {
419 setCursorPosition(pos.getX(), pos.getY());
420 }
421
422 public static void setCursorPosition(Point pos, boolean forceArrow)
423 {
424 setCursorPosition(pos.getX(), pos.getY(), forceArrow);
425 }
426
427 /**
428 * Returns the top item (last added) of the Back-Stack (which is popped off)
429 *
430 * @return The name of the last Frame added to the back-stack
431 */
432 public static String getLastFrame()
433 {
434 TwinFramesSide side = getCurrentSide();
435 Stack<String> visitedFrames = getVisitedFrames(side);
436
437 if (visitedFrames.size() > 0) {
438 return visitedFrames.pop();
439 } else {
440 return null;
441 }
442 }
443
444 /**
445 * Adds the given Frame to the back-stack
446 *
447 * @param frame
448 * The Frame to add
449 */
450 public static void addToBack(Frame toAdd)
451 {
452 TwinFramesSide side = getCurrentSide();
453 Stack<String> visitedFrames = getVisitedFrames(side);
454
455 visitedFrames.push(toAdd.getName());
456 }
457
458 public static String removeFromBack()
459 {
460 return getLastFrame();
461 }
462
463 /**
464 * Returns a 'peek' at the end element on the back-stack of the current
465 * side. If the back-stack is empty, null is returned.
466 *
467 * @return The name of the most recent Frame added to the back-stack, or
468 * null if the back-stack is empty.
469 */
470 public static String peekFromBackUpStack()
471 {
472 TwinFramesSide side = getCurrentSide();
473 Stack<String> visitedFrames = getVisitedFrames(side);
474
475 // check that the stack is not empty
476 if (visitedFrames.size() > 0) {
477 return visitedFrames.peek();
478 }
479
480 // if the stack is empty, return null
481 return null;
482 }
483
484 /** Sets the transition to use when leaving the given frame. */
485 public static void setTransition(Frame frame, FrameTransition transition)
486 {
487 if (frame == null) {
488 return;
489 }
490 TwinFramesSide side = getSideFrameIsOn(frame);
491 setTransition(side, transition);
492 }
493
494 /** Sets the transition to use when changing frame on the given side. */
495 private static void setTransition(TwinFramesSide side, FrameTransition transition)
496 {
497 if (side == null) {
498 return;
499 }
500
501 _transitions[side.ordinal()] = transition;
502 }
503
504 /** Gets the transition to use when changing frame on the given side. */
505 private static FrameTransition getTransition(TwinFramesSide side)
506 {
507 if (side == null) {
508 return null;
509 }
510
511 return _transitions[side.ordinal()];
512 }
513
514 /**
515 * TODO: Comment. cts16
516 * TODO: Refactor. Too monolithic. cts16
517 */
518 public static void setCurrentFrame(Frame frame, boolean incrementStats)
519 {
520 if (frame == null) {
521 return;
522 }
523
524 // If one of the sides doesn't have a frame yet, give it this one
525 if (isTwinFramesOn()) {
526 for (TwinFramesSide side : TwinFramesSide.values()) {
527 if (!sideHasFrame(side)) {
528 setFrameOnSide(frame, side);
529 fireFrameChanged();
530 return;
531 }
532 }
533 }
534
535 // if this is already the current frame
536 if (frame == getCurrentFrame()) {
537 requestRefresh(false);
538 MessageBay.displayMessage(frame.getName() + " is already the current frame.");
539 return;
540 }
541
542 // Update stats
543 if (incrementStats) {
544 SessionStats.AccessedFrame();
545 }
546
547 // Invalidate free items
548 if (!FreeItems.getInstance().isEmpty() && getCurrentFrame() != null) {
549
550 // Empty free items temporarily so that the old frames buffer is repainted without the free items.
551 ArrayList<Item> tmp = FreeItems.getInstance().clone();
552 FreeItems.getInstance().clear(); // NOTE: This will invalidate all the cleared free items
553 requestRefresh(true);
554 FreeItems.getInstance().addAll(tmp);
555
556 }
557
558 // Changing frames is a Save point for saveable entities:
559 EntitySaveManager.getInstance().saveAll();
560
561 if (_twinFramesMode) {
562 // if the same frame is being shown in both sides, load a fresh
563 // copy from disk
564 if (getOppositeFrame() == frame || getOppositeFrame().hasOverlay(frame)) {
565 FrameIO.SuspendCache();
566 frame = FrameIO.LoadFrame(frame.getName());
567 FrameIO.ResumeCache();
568 }
569
570 // If the frames are the same then the items for the
571 // frame that is just about to hide will still be in view
572 // so only notify items that they are hidden if the
573 // frames differ.
574 if (getCurrentFrame() != null && !bothSidesHaveSameFrame()) {
575 for (Item i : getCurrentFrame().getItems()) {
576 i.onParentStateChanged(new ItemParentStateChangedEvent(getCurrentFrame(), ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN));
577 }
578 }
579 setFrameOnSide(frame, getCurrentSide());
580
581 // BROOK : TODO... overlays and loadable widgets
582 for (Item i : getCurrentFrame().getItems()) {
583 i.onParentStateChanged(new ItemParentStateChangedEvent(getCurrentFrame(), ItemParentStateChangedEvent.EVENT_TYPE_SHOWN));
584 }
585 } else {
586
587 // Notifying items on the frame being hidden that they
588 // are about to be hidden.
589 // ie. Widgets use this method to remove themselves from the JPanel
590 List<Frame> currentOnlyOverlays = new LinkedList<Frame>();
591 List<Frame> nextOnlyOverlays = new LinkedList<Frame>();
592 List<Frame> sharedOverlays = new LinkedList<Frame>();
593
594 // Get all overlayed frames seen by the next frame
595 for (Overlay o : frame.getOverlays()) {
596 if (!nextOnlyOverlays.contains(o.Frame)) {
597 nextOnlyOverlays.add(o.Frame);
598 }
599 }
600
601 // Get all overlayed frames seen by the current frame
602 if (getCurrentFrame() != null) {
603 for (Overlay o : getCurrentFrame().getOverlays()) {
604 if (!currentOnlyOverlays.contains(o.Frame)) {
605 currentOnlyOverlays.add(o.Frame);
606 }
607 }
608 }
609
610 // Extract shared overlays between the current and next frame
611 for (Frame of : currentOnlyOverlays) {
612 if (nextOnlyOverlays.contains(of)) {
613 sharedOverlays.add(of);
614 }
615 }
616
617 // The first set, currentOnlyOverlays, must be notified that they
618 // are hidden
619 Collection<Item> items = new LinkedList<Item>();
620
621 // Notify items that will not be in view any more
622 if (getCurrentFrame() != null) {
623 List<Frame> seen = new LinkedList<Frame>();
624 seen.addAll(sharedOverlays); // Signify that seen all shared
625 // overlays
626 seen.remove(getCurrentFrame()); // must ensure
627 // excluded
628
629 // Get all items seen from the current frame - including all
630 // possible non-shared overlays
631 items = getCurrentFrame().getAllItems();
632 for (Frame f : seen) {
633 items.removeAll(f.getAllItems());
634 }
635
636 // Notify items that they are hidden
637 for (Item i : items) {
638 i.onParentStateChanged(new ItemParentStateChangedEvent(getCurrentFrame(), ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN));
639 }
640 }
641
642 // Set the new frame
643 setFrameOnSide(frame, getCurrentSide());
644 frame.refreshSize();
645
646 // Notify items on the frame being displayed that they are in view
647 // ie. widgets use this method to add themselves to the content pane
648 items.clear();
649
650 // Notify overlay items that they are shown
651 for (Item i : frame.getOverlayItems()) {
652 Overlay owner = frame.getOverlayOwner(i);
653 // if (owner == null) i.onParentFameShown(false, 0);
654 // else ...
655 assert (owner != null);
656 i
657 .onParentStateChanged(new ItemParentStateChangedEvent(
658 frame,
659 ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY,
660 owner.permission));
661 }
662
663 for (Item i : frame.getItems()) {
664 i.onParentStateChanged(new ItemParentStateChangedEvent(frame,
665 ItemParentStateChangedEvent.EVENT_TYPE_SHOWN));
666 }
667 }
668
669 frame.reset();
670
671 // fix text items
672 ItemUtils.Justify(frame);
673
674 StandardGestureActions.refreshHighlights();
675 requestRefresh(false);
676 fireFrameChanged();
677 }
678
679 /** Updates the window title bar to show which mode Expeditee is in. */
680 public static void updateTitle()
681 {
682 StringBuffer title = new StringBuffer(TITLE);
683
684 if (isAudienceMode()) {
685 title.append(" - Audience Mode");
686 } else if (isXRayMode()) {
687 title.append(" - X-Ray Mode");
688 } else {
689 title.append(" [").append(SessionStats.getShortStats()).append(']');
690 }
691
692 EcosystemManager.getGraphicsManager().setWindowTitle(title.toString());
693 }
694
695 /** Whether the given side has a frame. */
696 public static boolean sideHasFrame(TwinFramesSide side)
697 {
698 return getFrameOnSide(side) != null;
699 }
700
701 /** Gets the side of the twin-frames display that the mouse is over. */
702 public static TwinFramesSide getCurrentSide()
703 {
704
705 // If the mouse is over the right side of the window and there's a valid frame,
706 // we are on the right side.
707 if(isTwinFramesOn()) {
708 int mouseX = EcosystemManager.getInputManager().getCursorPosition().getX();
709 if(mouseX >= getTwinFramesSeparatorX() && sideHasFrame(RIGHT)) {
710 return RIGHT;
711 }
712 }
713
714 // If there's only a right frame, that's the current side
715 if (!sideHasFrame(LEFT) && sideHasFrame(RIGHT)) {
716 return RIGHT;
717 }
718
719 // In any other case the left side is the current side
720 return LEFT;
721 }
722
723 /** Gets the opposite side to the current side of the twin-frames. */
724 private static TwinFramesSide getOppositeSide()
725 {
726 if (getCurrentSide() == LEFT) {
727 return RIGHT;
728 }
729
730 return LEFT;
731 }
732
733 /** Returns the side that the given frame is on, or null if it's not on either side. */
734 public static TwinFramesSide getSideFrameIsOn(Frame frame)
735 {
736 // Loop through both sides
737 for (TwinFramesSide side : TwinFramesSide.values()) {
738 if (getFrameOnSide(side) == frame) {
739 return side;
740 }
741 }
742
743 return null;
744 }
745
746 /** Gets the frame that is on the given side of the display. */
747 public static Frame getFrameOnSide(TwinFramesSide side)
748 {
749 if (side == null) {
750 return null;
751 }
752
753 return _currentFrames[side.ordinal()];
754 }
755
756 /** Returns the frame on the current side of the display. */
757 public static Frame getCurrentFrame()
758 {
759 return getFrameOnSide(getCurrentSide());
760 }
761
762 /** Returns the frame on the opposite side of the display. */
763 public static Frame getOppositeFrame()
764 {
765 return getFrameOnSide(getOppositeSide());
766 }
767
768 /** Gets the two frames being displayed. */
769 public static Frame[] getFrames()
770 {
771 return _currentFrames;
772 }
773
774 /** Gets the x-coordinate of the division between the left and right sides. */
775 public static int getTwinFramesSeparatorX()
776 {
777 return getLeftFramePaintArea().getWidth() + (int) (SEPARATOR_STROKE.thickness / 2);
778 }
779
780 /** Gets the line that separates the two sides of the twin-frames display. */
781 public static Line getTwinFramesSeparatorLine()
782 {
783 if (!isTwinFramesOn()) {
784 return null;
785 }
786
787 int x = getTwinFramesSeparatorX();
788 int bottom = getFramePaintArea().getMaxY();
789
790 return new Line(x, 0, x, bottom);
791
792 }
793
794 /** Gets the line that separates the message bay from the frame area. */
795 public static Line getMessageBaySeparatorLine()
796 {
797 // No message bay in audience mode
798 if (isAudienceMode()) {
799 return null;
800 }
801
802 int separatorThickness = (int) SEPARATOR_STROKE.thickness;
803 int y = getFramePaintArea().getMaxY() + separatorThickness / 2 + separatorThickness % 2;
804 int right = getMessageBayPaintArea().getMaxX();
805
806 return new Line(0, y, right, y);
807 }
808
809 /**
810 * Returns the current mouse X coordinate. This coordinate is relative to
811 * the left edge of the frame the mouse is in. It takes into account the
812 * user being in twin frames mode.
813 *
814 * @return The X coordinate of the mouse.
815 */
816 public static float getFloatMouseX()
817 {
818 return getMouseX();
819 }
820
821
822 /**
823 * Returns the current mouse X coordinate. This coordinate is relative to
824 * the left edge of the frame the mouse is in. It takes into account the
825 * user being in twin frames mode.
826 *
827 * @return The X coordinate of the mouse.
828 */
829 public static int getMouseX()
830 {
831 return getMousePosition().getX();
832 }
833
834 /**
835 * Returns the current mouse position. This coordinate is relative to
836 * the upper-left edge of the frame the mouse is in. It takes into account the
837 * user being in twin frames mode.
838 *
839 * @return The position of the mouse.
840 */
841 public static Point getMousePosition()
842 {
843 Point mousePos = EcosystemManager.getInputManager().getCursorPosition();
844
845 if (isTwinFramesOn() && getRightFramePaintArea().contains(mousePos)) {
846 mousePos.setX(mousePos.getX() - getRightFramePaintArea().getMinX());
847 }
848
849 return mousePos;
850 }
851
852 /**
853 * Returns the current mouse Y coordinate. This coordinate is relative to
854 * the top edge of the frame the mouse is in.
855 *
856 * @return The Y coordinate of the mouse.
857 */
858 public static float getFloatMouseY()
859 {
860 return getMouseY();
861 }
862
863 /**
864 * Returns the current mouse Y coordinate. This coordinate is relative to
865 * the top edge of the frame the mouse is in.
866 *
867 * @return The Y coordinate of the mouse.
868 */
869 public static int getMouseY()
870 {
871 return getMousePosition().getY();
872 }
873
874 /**
875 * TODO: Comment. cts16
876 * @return
877 */
878 public static boolean Back()
879 {
880 TwinFramesSide side = getCurrentSide();
881 Stack<String> visitedFrames = getVisitedFrames(side);
882
883 // there must be a frame to go back to
884 if (visitedFrames.size() < 1) {
885 MessageBay.displayMessageOnce("You are already on the home frame");
886 return false;
887 }
888
889 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
890 MessageBay.displayMessage("Error navigating back");
891 return false;
892 }
893
894 String oldFrame = getCurrentFrame().getName().toLowerCase();
895
896 // do not get a cached version (in case it is in the other window)
897 if (isTwinFramesOn()) {
898 FrameIO.SuspendCache();
899 }
900
901 Frame frame = FrameIO.LoadFrame(removeFromBack());
902 // If the top frame on the backup stack is the current frame go back
903 // again... or if it has been deleted
904 // Recursively backup the stack
905 if (frame == null || frame.equals(getCurrentFrame())) {
906 Back();
907 return false;
908 }
909
910 if (isTwinFramesOn()) {
911 FrameIO.ResumeCache();
912 }
913 getBackedUpFrames(side).push(oldFrame);
914 FrameUtils.DisplayFrame(frame, false, true);
915 StandardGestureActions.setHighlightHold(true);
916
917 for (Item i : frame.getItems()) {
918 if (i.getLink() != null && i.getAbsoluteLink().toLowerCase().equals(oldFrame)) {
919 if (i.getHighlightMode() != Item.HighlightMode.Normal) {
920 i.setHighlightModeAndColour(Item.HighlightMode.Normal,
921 BACK_HIGHLIGHT_COLOR);
922 }
923 // check if its an @f item and if so update the buffer
924 if (i instanceof Picture) {
925 Picture p = (Picture) i;
926 p.refresh();
927 }
928 }
929 }
930 requestRefresh(true);
931 return true;
932 }
933
934 /**
935 * TODO: Comment. cts16
936 * @return
937 */
938 public static boolean Forward()
939 {
940 TwinFramesSide side = getCurrentSide();
941
942 // there must be a frame to go back to
943 if (getBackedUpFrames(side).size() == 0) {
944 return false;
945 }
946
947 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
948 MessageBay.displayMessage("Error navigating forward");
949 return false;
950 }
951
952 String oldFrame = getCurrentFrame().getName().toLowerCase();
953
954 // do not get a cached version (in case it is in the other window)
955 if (isTwinFramesOn()) {
956 FrameIO.SuspendCache();
957 }
958 Frame frame = FrameIO.LoadFrame(getBackedUpFrames(side).pop());
959 // If the top frame on the backup stack is the current frame go back
960 // again... or if it has been deleted
961 // Recursively backup the stack
962 if (frame == null || frame.equals(getCurrentFrame())) {
963 Forward();
964 return false;
965 }
966
967 if (isTwinFramesOn()) {
968 FrameIO.ResumeCache();
969 }
970 getVisitedFrames(side).push(oldFrame);
971 FrameUtils.DisplayFrame(frame, false, true);
972 requestRefresh(true);
973 return true;
974 }
975
976 /** Toggles the display of frames between TwinFrames mode and Single frame mode. */
977 public static void toggleTwinFrames()
978 {
979 // determine which side is the active side
980 TwinFramesSide opposite = getOppositeSide();
981 _twinFramesMode = !_twinFramesMode;
982
983 // if TwinFrames is being turned on
984 if (_twinFramesMode) {
985 // if this is the first time TwinFrames has been toggled on,
986 // load the user's first frame
987 if (getVisitedFrames(opposite).size() == 0) {
988 FrameIO.SuspendCache();
989 setCurrentFrame(FrameIO.LoadFrame(UserSettings.HomeFrame.get()), true);
990 FrameIO.ResumeCache();
991 } else {
992 // otherwise, restore the frame from the side's back-stack
993 setCurrentFrame(FrameIO.LoadFrame(getVisitedFrames(opposite).pop()), true);
994 }
995
996 // else, TwinFrames is being turned off
997 } else {
998 // add the frame to the back-stack
999 Frame hiding = getOppositeFrame();
1000 FrameUtils.LeavingFrame(hiding);
1001 getVisitedFrames(opposite).add(hiding.getName());
1002 setFrameOnSide(null, opposite);
1003 getCurrentFrame().refreshSize();
1004 }
1005
1006 // Update the sizes of the displayed frames
1007 if (getCurrentFrame() != null) {
1008 getCurrentFrame().refreshSize();
1009 }
1010 if (getOppositeFrame() != null) {
1011 getOppositeFrame().refreshSize();
1012 }
1013
1014 requestRefresh(false);
1015 }
1016
1017 /** Whether the display is currently in twin-frames mode. */
1018 public static boolean isTwinFramesOn()
1019 {
1020 return _twinFramesMode;
1021 }
1022
1023 /** TODO: Comment. cts16 */
1024 public static void Reload(TwinFramesSide side)
1025 {
1026 if (side == null) {
1027 return;
1028 }
1029
1030 FrameIO.SuspendCache();
1031 Frame frame = FrameIO.LoadFrame(getFrameOnSide(side).getName());
1032 setFrameOnSide(frame, side);
1033 FrameIO.ResumeCache();
1034 }
1035
1036 /**
1037 * Moves the cursor the end of this item.
1038 *
1039 * @param i
1040 */
1041 public static void MoveCursorToEndOfItem(Item i)
1042 {
1043 setTextCursor((Text) i, Text.END, true, false, false, false);
1044 }
1045
1046 public static void clearBackedUpFrames()
1047 {
1048 getBackedUpFrames(getCurrentSide()).clear();
1049 }
1050
1051 /**
1052 * @param secondsDelay
1053 * @param s
1054 * @throws InterruptedException
1055 */
1056 public static void typeStringDirect(double secondsDelay, String s) throws InterruptedException
1057 {
1058 for (int i = 0; i < s.length(); i++) {
1059 StandardGestureActions.processChar(s.charAt(i), false);
1060 Thread.sleep((int) (secondsDelay * 1000));
1061 }
1062 }
1063
1064 public static List<String> getUnmodifiableVisitedList()
1065 {
1066 return Collections.unmodifiableList(getVisitedFrames(getCurrentSide()));
1067 }
1068
1069 /** If both sides have the same frame (returns false if both sides have no frame). */
1070 public static boolean bothSidesHaveSameFrame()
1071 {
1072 return (getCurrentFrame() != null && getCurrentFrame() == getOppositeFrame());
1073 }
1074
1075 /** Sets the given side to the given frame. */
1076 private static void setFrameOnSide(Frame frame, TwinFramesSide side)
1077 {
1078 if (side == null) {
1079 return;
1080 }
1081
1082 _currentFrames[side.ordinal()] = frame;
1083 }
1084
1085 /** Gets the stack of visited frames for the given side. */
1086 private static Stack<String> getVisitedFrames(TwinFramesSide side)
1087 {
1088 if (side == null) {
1089 return null;
1090 }
1091
1092 return _visitedFrames[side.ordinal()];
1093 }
1094
1095 /** Gets the stack of backed-up frames for the given side. */
1096 private static Stack<String> getBackedUpFrames(TwinFramesSide side)
1097 {
1098 if (side == null) {
1099 return null;
1100 }
1101
1102 return _backedUpFrames[side.ordinal()];
1103 }
1104
1105 /**
1106 * Rotates through normal -> audience -> audience + fullscreen modes and back again.
1107 */
1108 public static void rotateAudienceModes()
1109 {
1110 Frame current = DisplayController.getCurrentFrame();
1111 GraphicsManager g = EcosystemManager.getGraphicsManager();
1112 if (_audienceMode && g.isFullscreen()) {
1113 ToggleAudienceMode();
1114 g.exitFullscreen();
1115 } else if (_audienceMode) {
1116 if (g.canGoFullscreen()) {
1117 g.goFullscreen();
1118 } else {
1119 ToggleAudienceMode();
1120 }
1121 } else {
1122 ToggleAudienceMode();
1123 ItemUtils.UpdateConnectedToAnnotations(current.getItems());
1124 for (Overlay o : current.getOverlays()) {
1125 ItemUtils.UpdateConnectedToAnnotations(o.Frame.getItems());
1126 }
1127 for (Vector v : current.getVectorsDeep()) {
1128 ItemUtils.UpdateConnectedToAnnotations(v.Frame.getItems());
1129 }
1130 }
1131 }
1132
1133 /**
1134 * If Audience Mode is on this method will toggle it to be off, or
1135 * vice-versa. This results in the Frame being re-parsed and repainted.
1136 */
1137 public static void ToggleAudienceMode()
1138 {
1139 Frame current = DisplayController.getCurrentFrame();
1140
1141 // Turn off x-ray mode if it's on
1142 if (_xrayMode) {
1143 ToggleXRayMode();
1144 }
1145
1146 // Toggle audience mode
1147 _audienceMode = !_audienceMode;
1148
1149 refreshPaintAreas();
1150 FrameUtils.Parse(current);
1151 updateTitle();
1152 requestRefresh(false);
1153 }
1154
1155 /**
1156 * If X-Ray Mode is on this method will toggle it to be off, or vice-versa.
1157 * This results in the Frame being re-parsed and repainted.
1158 */
1159 public static void ToggleXRayMode()
1160 {
1161 // Turn off x-ray mode if it is on
1162 if (_audienceMode) {
1163 ToggleAudienceMode();
1164 }
1165
1166 _xrayMode = !_xrayMode;
1167
1168 if(_xrayMode) {
1169 //Entering XRay Mode is a Save point for saveable entities
1170 EntitySaveManager.getInstance().saveAll();
1171 }
1172
1173 getCurrentFrame().parse();
1174 getCurrentFrame().refreshSize();
1175 updateTitle();
1176 StandardGestureActions.refreshHighlights();
1177 StandardGestureActions.updateCursor();
1178 requestRefresh(false);
1179 }
1180
1181 /** Whether audience mode is currently on. */
1182 public static boolean isAudienceMode()
1183 {
1184 return _audienceMode;
1185 }
1186
1187 /** Whether x-ray mode is currently on. */
1188 public static boolean isXRayMode()
1189 {
1190 return _xrayMode;
1191 }
1192
1193 /** Tells the display controller to get the current window size. */
1194 public static void refreshWindowSize()
1195 {
1196 _windowSize = EcosystemManager.getGraphicsManager().getWindowSize();
1197 _refreshBuffer = Image.createImage(_windowSize, true);
1198 refreshPaintAreas();
1199 }
1200
1201 /** Recalculates the paint areas for the frames and message bay. */
1202 public static void refreshPaintAreas()
1203 {
1204 // Calculate the width of each frame in twin-frames mode
1205 int availableWindowWidth = _windowSize.width - (int) SEPARATOR_STROKE.thickness;
1206 int leftFrameWidth = (int) (availableWindowWidth * _twinFramesLeftWidthProportion);
1207 int rightFrameWidth = availableWindowWidth - leftFrameWidth;
1208
1209 // The height of each frame is the window height, minus the message bay if visible
1210 int frameHeight = _windowSize.height;
1211 if (!isAudienceMode()) {
1212 frameHeight -= _messageBayHeight + (int) SEPARATOR_STROKE.thickness;
1213 }
1214
1215 int rightFrameX = _windowSize.width - rightFrameWidth;
1216 int messageBayY = _windowSize.height - _messageBayHeight;
1217
1218 _leftFramePaintArea = new AxisAlignedBoxBounds(0, 0, leftFrameWidth, frameHeight);
1219 _rightFramePaintArea = new AxisAlignedBoxBounds(rightFrameX, 0, rightFrameWidth, frameHeight);
1220 _framePaintArea = new AxisAlignedBoxBounds(0, 0, _windowSize.width, frameHeight);
1221 _messageBayPaintArea = new AxisAlignedBoxBounds(0, messageBayY, _windowSize.width, _messageBayHeight);
1222 }
1223
1224 public static AxisAlignedBoxBounds getLeftFramePaintArea()
1225 {
1226 return _leftFramePaintArea;
1227 }
1228
1229 public static AxisAlignedBoxBounds getRightFramePaintArea()
1230 {
1231 return _rightFramePaintArea;
1232 }
1233
1234 public static AxisAlignedBoxBounds getFramePaintArea()
1235 {
1236 return _framePaintArea;
1237 }
1238
1239 public static AxisAlignedBoxBounds getMessageBayPaintArea()
1240 {
1241 return _messageBayPaintArea;
1242 }
1243
1244 /**
1245 * Checks that the item is visible (on current frame && overlays) - if
1246 * visible then damaged area will be re-rendered on the next refresh.
1247 *
1248 * @param damagedItem
1249 * @param toRepaint
1250 */
1251 public static void invalidateItem(Item damagedItem, Bounds toRepaint)
1252 {
1253 if (toRepaint == null) {
1254 return;
1255 }
1256
1257 // Only add area to repaint if item is visible...
1258 if (ItemUtils.isVisible(damagedItem)) {
1259 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint));
1260 } else if (MessageBay.isMessageItem(damagedItem)) {
1261 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint).translate(0, getMessageBayPaintArea().getMinY()));
1262 }
1263 }
1264
1265 /**
1266 * The given area will be re-rendered in the next refresh. This is the
1267 * quicker version and is more useful for re-rendering animated areas.
1268 */
1269 public static void invalidateArea(AxisAlignedBoxBounds toRepaint)
1270 {
1271 _damagedAreas.addArea(toRepaint);
1272 }
1273
1274 public static void clearInvalidAreas()
1275 {
1276 _damagedAreas.clear();
1277 }
1278
1279 /*
1280 *
1281 * Thread-safe rendering.
1282 *
1283 */
1284
1285 /**
1286 * Redraws the entire window. Shouldn't be called directly, instead use
1287 * requestRefresh(boolean) to ensure no infinite refresh call loops are created.
1288 */
1289 private static boolean refresh(boolean useInvalidation)
1290 {
1291 Frame currentFrame = getCurrentFrame();
1292 if(currentFrame == null) {
1293 return false;
1294 }
1295
1296 // Always get the clip as it clears at the same time
1297 Clip clip = _damagedAreas.getClip();
1298
1299 // No damaged areas
1300 if (useInvalidation && clip.isFullyClipped()) {
1301 return true;
1302 }
1303
1304 GraphicsManager g = EcosystemManager.getGraphicsManager();
1305 g.pushDrawingSurface(_refreshBuffer);
1306
1307 if (isTwinFramesOn()) {
1308 Clip leftClip = null;
1309 Clip rightClip = null;
1310
1311 if (useInvalidation) {
1312 leftClip = clip.clone().intersectWith(getLeftFramePaintArea());
1313 rightClip = clip.clone().intersectWith(getRightFramePaintArea());
1314 if (!rightClip.isFullyClipped()) {
1315 rightClip = new Clip(rightClip.getBounds().translate(-getRightFramePaintArea().getMinX(), 0));
1316 }
1317 }
1318
1319 Image left = null;
1320 if (!useInvalidation || !leftClip.isFullyClipped()) {
1321 left = FrameGraphics.getFrameImage(getFrameOnSide(LEFT), leftClip, getLeftFramePaintArea().getSize());
1322 }
1323
1324 Image right = null;
1325 if (!useInvalidation || !rightClip.isFullyClipped()) {
1326 right = FrameGraphics.getFrameImage(getFrameOnSide(RIGHT), rightClip, getRightFramePaintArea().getSize());
1327 }
1328
1329 paintTwinFrames(left, right);
1330 } else {
1331 Clip frameClip = null;
1332
1333 if (useInvalidation) {
1334 frameClip = clip.clone().intersectWith(getFramePaintArea());
1335 }
1336
1337 Image image = null;
1338 if (currentFrame != null && (!useInvalidation || !frameClip.isFullyClipped())) {
1339 image = FrameGraphics.getFrameImage(currentFrame, frameClip, getFramePaintArea().getSize());
1340 }
1341
1342 paintSingleFrame(image);
1343 }
1344
1345 // Paint message bay
1346 if (!isAudienceMode()) {
1347 Clip messageBayClip = null;
1348
1349 AxisAlignedBoxBounds messageBayPaintArea = getMessageBayPaintArea();
1350 if (useInvalidation) {
1351 messageBayClip = clip.clone().intersectWith(messageBayPaintArea);
1352 if (!messageBayClip.isFullyClipped()) {
1353 messageBayClip = new Clip(messageBayClip.getBounds().translate(0, -messageBayPaintArea.getMinY()));
1354 }
1355 }
1356
1357 Image image = null;
1358 if (!useInvalidation || !messageBayClip.isFullyClipped()) {
1359 image = MessageBay.getImage(messageBayClip, messageBayPaintArea.getSize());
1360 }
1361
1362 if (image != null) {
1363 g.drawImage(image, messageBayPaintArea.getTopLeft(), messageBayPaintArea.getSize());
1364 }
1365 }
1366
1367 // Draw any separator lines
1368 g.drawLine(getMessageBaySeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1369 g.drawLine(getTwinFramesSeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1370
1371 // Paint any popups
1372 if (PopupManager.getInstance() != null) {
1373 PopupManager.getInstance().paint();
1374 }
1375
1376 g.popDrawingSurface();
1377 g.drawImage(_refreshBuffer, Point.ORIGIN);
1378 return true;
1379 }
1380
1381 /** Draws the two frame images to the display and puts the separator line between them. */
1382 private static void paintTwinFrames(Image leftFrameImage, Image rightFrameImage)
1383 {
1384 if (leftFrameImage == null && rightFrameImage == null) {
1385 return;
1386 }
1387
1388 InOutReference<FrameTransition> leftTransition = new InOutReference<FrameTransition>(getTransition(LEFT));
1389 InOutReference<FrameTransition> rightTransition = new InOutReference<FrameTransition>(getTransition(RIGHT));
1390
1391 paintFrameIntoArea(leftFrameImage, getLeftFramePaintArea(), leftTransition);
1392 paintFrameIntoArea(rightFrameImage, getRightFramePaintArea(), rightTransition);
1393
1394 setTransition(LEFT, leftTransition.get());
1395 setTransition(RIGHT, rightTransition.get());
1396 }
1397
1398 /** Draws the single frame image to the screen. */
1399 private static void paintSingleFrame(Image frameImage)
1400 {
1401 if (frameImage == null) {
1402 return;
1403 }
1404
1405 InOutReference<FrameTransition> transition = new InOutReference<FrameTransition>(getTransition(getCurrentSide()));
1406
1407 paintFrameIntoArea(frameImage, getFramePaintArea(), transition);
1408
1409 setTransition(getCurrentSide(), transition.get());
1410 }
1411
1412 /** Draws the given frame image into the given area, using a transition if one is provided. */
1413 private static void paintFrameIntoArea(Image frameImage, AxisAlignedBoxBounds area, InOutReference<FrameTransition> transition)
1414 {
1415 if (frameImage == null || area == null) {
1416 return;
1417 }
1418
1419 GraphicsManager g = EcosystemManager.getGraphicsManager();
1420 EnforcedClipKey key = g.pushClip(new Clip(area));
1421
1422 FrameTransition t = null;
1423 if (transition != null) {
1424 t = transition.get();
1425 }
1426
1427 // Attempt to draw the transition if there is one
1428 if (t != null) {
1429 if (!t.drawTransition(frameImage, area)) {
1430 // If drawing failed, throw the transition away
1431 t = null;
1432 } else {
1433 // Schedule the next frame to be drawn
1434 invalidateArea(area);
1435 requestRefresh(true);
1436 }
1437 }
1438
1439 // If t == null at this stage, no transition has been drawn, so just draw the image
1440 if (t == null) {
1441 g.drawImage(frameImage, area.getTopLeft(), area.getSize());
1442 }
1443
1444 // Discard the transition if drawing failed or it is finished
1445 if (t == null || t.isCompleted()) {
1446 transition.set(null);
1447 }
1448
1449 g.popClip(key);
1450 }
1451
1452 /** Runnable which ensures overlapping refresh requests are joined. */
1453 private static RenderRequestMarsheller _requestMarsheller = new RenderRequestMarsheller();
1454
1455 /**
1456 * If wanting to refresh from another thread - other than the main thread
1457 * that handles the expeditee datamodel (modifying / accessing / rendering).
1458 * Use this method for thread safety.
1459 */
1460 public static synchronized void requestRefresh(boolean useInvalidation) {
1461 requestRefresh(useInvalidation, () -> false);
1462 }
1463
1464 public static synchronized void requestRefresh(final boolean useInvalidation, final BooleanSupplier callback) {
1465 try {
1466 _requestMarsheller.enqueue(useInvalidation, callback);
1467 } catch (Throwable e) {
1468 e.printStackTrace();
1469 }
1470 }
1471
1472 /**
1473 * Used for marshelling render requests from foreign threads to the event
1474 * dispatcher thread... (AWT)
1475 *
1476 * @author Brook Novak
1477 */
1478 private static class RenderRequestMarsheller implements Runnable {
1479
1480 private boolean _useInvalidation = true;
1481 private boolean _enqueued = false;
1482 private Boolean _requeueWithInvalidation = null;
1483 private Object _lock = new Object();
1484 private BooleanSupplier callback;
1485
1486 /** Enqueues a redraw on the GIO thread, or remembers to do so once the current redraw finishes. */
1487 public void enqueue(final boolean useInvalidation, final BooleanSupplier callback)
1488 {
1489 synchronized (_lock) {
1490 if (!_enqueued) {
1491 _enqueued = true;
1492 this.callback = callback;
1493 _useInvalidation = useInvalidation;
1494 EcosystemManager.getMiscManager().runOnGIOThread(this);
1495 } else if (_requeueWithInvalidation == null || _requeueWithInvalidation == true) {
1496 _requeueWithInvalidation = useInvalidation;
1497 }
1498 }
1499 }
1500
1501 @Override
1502 public void run()
1503 {
1504 try {
1505 if(refresh(_useInvalidation) && callback != null) {
1506 callback.getAsBoolean();
1507 callback = null;
1508 }
1509 } catch (Throwable e) {
1510 e.printStackTrace();
1511 }
1512
1513 // Do another redraw if we received a new request while doing this one.
1514 synchronized (_lock) {
1515 if (_requeueWithInvalidation != null) {
1516 _useInvalidation = _requeueWithInvalidation.booleanValue();
1517 _requeueWithInvalidation = null;
1518 EcosystemManager.getMiscManager().runOnGIOThread(this);
1519 } else {
1520 _enqueued = false;
1521 }
1522 }
1523 }
1524 }
1525}
Note: See TracBrowser for help on using the repository browser.