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

Last change on this file since 1157 was 1157, checked in by bln4, 6 years ago

org.expeditee.gui.DisplayController ->

Entering XRay Mode is a Save point for saveable entities.

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