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

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

Used Eclipse refactoring to encapsulate Point.X and Point.Y

File size: 43.9 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 //System.err.println("setTextCursor DisplayController:: " + DisplayController.getFloatMouseX() + "," + DisplayController.getFloatMouseY());
236 //System.err.println("setTextCursor Ecosystem:: " + EcosystemManager.getInputManager().getCursorPosition());
237 Point newMouse = text.moveCursor(cursorMovement, DisplayController.getFloatMouseX(), EcosystemManager.getInputManager().getCursorPosition().getY(), isShiftDown, isCtrlDown);
238 //System.err.println("setTextCursor New Cursor:: " + newMouse);
239
240 if( isCtrlDown ||
241 (DisplayController.getFloatMouseX() <= newMouse.getX() && newMouse.getX() <= DisplayController.getFloatMouseX() + 1) ||
242 (DisplayController.getFloatMouseX() > newMouse.getX() && cursorMovement == Text.RIGHT))
243 {
244 if(cursorMovement == Text.RIGHT && !MagneticConstraints.getInstance().rightBorderHit(text)) {
245 MagneticConstraints.getInstance().endOfLineHit(text);
246 } else {
247 if(cursorMovement == Text.LEFT && !MagneticConstraints.getInstance().leftBorderHit(text)) {
248 MagneticConstraints.getInstance().startOfLineHit(text);
249 }
250 }
251 newMouse.setX((int) DisplayController.getFloatMouseX());
252 newMouse.setY((int) DisplayController.getFloatMouseY());
253 } else if(cursorMovement == Text.UP && MagneticConstraints.getInstance().topBorderHit(text)) {
254 newMouse.setX((int) DisplayController.getFloatMouseX());
255 newMouse.setY((int) DisplayController.getFloatMouseY());
256 } else if(cursorMovement == Text.DOWN && MagneticConstraints.getInstance().bottomBorderHit(text)) {
257 newMouse.setX((int) DisplayController.getFloatMouseX());
258 newMouse.setY((int) DisplayController.getFloatMouseY());
259 }
260
261 if (!newSize && _cursorType == Item.TEXT_CURSOR) {
262 if (cursorMovement != 0)
263 DisplayController.setCursorPosition(newMouse, false);
264 return;
265 }
266
267 _cursorType = Item.TEXT_CURSOR;
268
269 // Do some stuff to adjust the cursor size based on the font size
270 final int MEDIUM_CURSOR_CUTOFF = 31;
271 final int LARGE_CURSOR_CUTOFF = 62;
272
273 int cursorSize = LARGE_CURSOR_SIZE;
274 int hotspotPos = 0;
275 int start = 0;
276
277 Dimension best_cursor_dim = EcosystemManager.getGraphicsManager().getBestCursorSize(new Dimension(cursorSize,cursorSize));
278 int best_cursor_height = best_cursor_dim.height;
279
280 if (best_cursor_height < cursorSize) {
281 // not able to provide the size of cursor Expeditee wants to
282 // => lock on to 'best_cursor_height' and use this to generate dependent values
283 cursorSize = best_cursor_height; // OS + Java version dependent: most likely MEDIUM_CURSOR_SIZE
284 if (size < best_cursor_height) {
285 start = cursorSize - size - 1;
286 hotspotPos = cursorSize - (size + 1) / 4;
287 }
288 else {
289 start = size - best_cursor_height;
290 hotspotPos = cursorSize -1;
291 }
292
293 }
294 else if (size < MEDIUM_CURSOR_CUTOFF) {
295 cursorSize = MEDIUM_CURSOR_SIZE;
296 start = cursorSize - size - 1;
297 hotspotPos = cursorSize - (size + 1) / 4;
298 } else if (size < LARGE_CURSOR_CUTOFF) {
299 hotspotPos = cursorSize - (size - 5) / 4;
300 start = cursorSize - size - 2;
301 } else {
302 int FIXED_CURSOR_MIN = 77;
303 if (size >= FIXED_CURSOR_MIN) {
304 hotspotPos = cursorSize - 2;
305 } else {
306 hotspotPos = size - (FIXED_CURSOR_MIN - cursorSize);
307 }
308 }
309
310 int[] pixels = new int[cursorSize * cursorSize];
311
312 for (int i = start; i < cursorSize; i++) {
313 pixels[i * cursorSize] = pixels[i * cursorSize + 1] = 0xFF000000;
314 }
315
316 Image image = Image.createImage(cursorSize, cursorSize, pixels);
317
318 EcosystemManager.getGraphicsManager().setCursor(new Cursor(image, new Point(0, hotspotPos), "textcursor"));
319
320 if (cursorMovement != Text.NONE) {
321 DisplayController.setCursorPosition(newMouse, false);
322 }
323 }
324
325 /**
326 * Sets the type of cursor the display should be using
327 *
328 * @param type
329 * The type of cursor to display, using constants defined in the
330 * Cursor class.
331 */
332 public static void setCursor(Cursor.CursorType type)
333 {
334 // Avoid flicker when not changing
335 if (type == _cursorType) return;
336
337 _cursorType = type;
338
339 refreshCursor();
340 }
341
342 /** Gets the type of cursor the display is currently using. */
343 public static Cursor.CursorType getCursor()
344 {
345 return _cursorType;
346 }
347
348 /** Updates the cursor with the graphics manager. */
349 private static void refreshCursor()
350 {
351 if (_cursorType == Item.HIDDEN_CURSOR || (FreeItems.hasCursor() && _cursorType == Item.DEFAULT_CURSOR)) {
352 Cursor invisibleCursor = Cursor.createInvisibleCursor();
353 EcosystemManager.getGraphicsManager().setCursor(invisibleCursor);
354 } else {
355 EcosystemManager.getGraphicsManager().setCursor(new Cursor(_cursorType));
356 }
357 }
358
359 /**
360 * Moves the mouse cursor to the given x,y coordinates on the screen
361 *
362 * @param x
363 * The x coordinate
364 * @param y
365 * The y coordinate
366 */
367 public static void setCursorPosition(float x, float y)
368 {
369 setCursorPosition(x, y, true);
370 }
371
372 /** TODO: Comment. cts16 */
373 public static void setCursorPosition(float x, float y, boolean forceArrow)
374 {
375 // Adjust the position to move the mouse to to account for being in
376 // TwinFramesMode
377 if (isTwinFramesOn()) {
378 if (getCurrentSide() == RIGHT) {
379 int middle = getTwinFramesSeparatorX();
380 x += middle;
381 }
382 }
383
384 if (FreeItems.hasItemsAttachedToCursor()) {
385 Point mousePos = EcosystemManager.getInputManager().getCursorPosition();
386 float deltax = x - mousePos.getX();
387 float deltay = y - mousePos.getY();
388 List<Item> toMove = FreeItems.getInstance();
389 for (Item move : toMove) {
390 move.setPosition(move.getX() + deltax, move.getY() + deltay);
391 }
392 }
393
394 // cheat
395 StandardGestureActions.setForceArrow(forceArrow);
396 EcosystemManager.getInputManager().setCursorPosition(new Point(x, y));
397 }
398
399 public static void resetCursorOffset()
400 {
401 StandardGestureActions.resetOffset();
402 }
403
404 /**
405 * Sets the current cursor position in the current frame
406 *
407 * @param pos
408 */
409 public static void setCursorPosition(Point pos)
410 {
411 setCursorPosition(pos.getX(), pos.getY());
412 }
413
414 public static void setCursorPosition(Point pos, boolean forceArrow)
415 {
416 setCursorPosition(pos.getX(), pos.getY(), forceArrow);
417 }
418
419 /**
420 * Returns the top item (last added) of the Back-Stack (which is popped off)
421 *
422 * @return The name of the last Frame added to the back-stack
423 */
424 public static String getLastFrame()
425 {
426 TwinFramesSide side = getCurrentSide();
427 Stack<String> visitedFrames = getVisitedFrames(side);
428
429 if (visitedFrames.size() > 0) {
430 return visitedFrames.pop();
431 } else {
432 return null;
433 }
434 }
435
436 /**
437 * Adds the given Frame to the back-stack
438 *
439 * @param frame
440 * The Frame to add
441 */
442 public static void addToBack(Frame toAdd)
443 {
444 TwinFramesSide side = getCurrentSide();
445 Stack<String> visitedFrames = getVisitedFrames(side);
446
447 visitedFrames.push(toAdd.getName());
448 }
449
450 public static String removeFromBack()
451 {
452 return getLastFrame();
453 }
454
455 /**
456 * Returns a 'peek' at the end element on the back-stack of the current
457 * side. If the back-stack is empty, null is returned.
458 *
459 * @return The name of the most recent Frame added to the back-stack, or
460 * null if the back-stack is empty.
461 */
462 public static String peekFromBackUpStack()
463 {
464 TwinFramesSide side = getCurrentSide();
465 Stack<String> visitedFrames = getVisitedFrames(side);
466
467 // check that the stack is not empty
468 if (visitedFrames.size() > 0) return visitedFrames.peek();
469
470 // if the stack is empty, return null
471 return null;
472 }
473
474 /** Sets the transition to use when leaving the given frame. */
475 public static void setTransition(Frame frame, FrameTransition transition)
476 {
477 if (frame == null) return;
478 TwinFramesSide side = getSideFrameIsOn(frame);
479 setTransition(side, transition);
480 }
481
482 /** Sets the transition to use when changing frame on the given side. */
483 private static void setTransition(TwinFramesSide side, FrameTransition transition)
484 {
485 if (side == null) return;
486
487 _transitions[side.ordinal()] = transition;
488 }
489
490 /** Gets the transition to use when changing frame on the given side. */
491 private static FrameTransition getTransition(TwinFramesSide side)
492 {
493 if (side == null) return null;
494
495 return _transitions[side.ordinal()];
496 }
497
498 /**
499 * TODO: Comment. cts16
500 * TODO: Refactor. Too monolithic. cts16
501 */
502 public static void setCurrentFrame(Frame frame, boolean incrementStats)
503 {
504 if (frame == null) return;
505
506 // If one of the sides doesn't have a frame yet, give it this one
507 if (isTwinFramesOn()) {
508 for (TwinFramesSide side : TwinFramesSide.values()) {
509 if (!sideHasFrame(side)) {
510 setFrameOnSide(frame, side);
511 fireFrameChanged();
512 return;
513 }
514 }
515 }
516
517 // if this is already the current frame
518 if (frame == getCurrentFrame()) {
519 requestRefresh(false);
520 MessageBay.displayMessage(frame.getName() + " is already the current frame.");
521 return;
522 }
523
524 // Update stats
525 if (incrementStats) SessionStats.AccessedFrame();
526
527 // Invalidate free items
528 if (!FreeItems.getInstance().isEmpty() && getCurrentFrame() != null) {
529
530 // Empty free items temporarily so that the old frames buffer is repainted without the free items.
531 ArrayList<Item> tmp = FreeItems.getInstance().clone();
532 FreeItems.getInstance().clear(); // NOTE: This will invalidate all the cleared free items
533 requestRefresh(true);
534 FreeItems.getInstance().addAll(tmp);
535
536 }
537
538 // Changing frames is a Save point for saveable entities:
539 EntitySaveManager.getInstance().saveAll();
540
541 if (_twinFramesMode) {
542 // if the same frame is being shown in both sides, load a fresh
543 // copy from disk
544 if (getOppositeFrame() == frame || getOppositeFrame().hasOverlay(frame)) {
545 FrameIO.SuspendCache();
546 frame = FrameIO.LoadFrame(frame.getName());
547 FrameIO.ResumeCache();
548 }
549
550 // If the frames are the same then the items for the
551 // frame that is just about to hide will still be in view
552 // so only notify items that they are hidden if the
553 // frames differ.
554 if (getCurrentFrame() != null && !bothSidesHaveSameFrame()) {
555 for (Item i : getCurrentFrame().getItems()) {
556 i.onParentStateChanged(new ItemParentStateChangedEvent(getCurrentFrame(), ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN));
557 }
558 }
559 setFrameOnSide(frame, getCurrentSide());
560
561 // BROOK : TODO... overlays and loadable widgets
562 for (Item i : getCurrentFrame().getItems()) {
563 i.onParentStateChanged(new ItemParentStateChangedEvent(getCurrentFrame(), ItemParentStateChangedEvent.EVENT_TYPE_SHOWN));
564 }
565 } else {
566
567 // Notifying items on the frame being hidden that they
568 // are about to be hidden.
569 // ie. Widgets use this method to remove themselves from the JPanel
570 List<Frame> currentOnlyOverlays = new LinkedList<Frame>();
571 List<Frame> nextOnlyOverlays = new LinkedList<Frame>();
572 List<Frame> sharedOverlays = new LinkedList<Frame>();
573
574 // Get all overlayed frames seen by the next frame
575 for (Overlay o : frame.getOverlays()) {
576 if (!nextOnlyOverlays.contains(o.Frame))
577 nextOnlyOverlays.add(o.Frame);
578 }
579
580 // Get all overlayed frames seen by the current frame
581 if (getCurrentFrame() != null) {
582 for (Overlay o : getCurrentFrame().getOverlays()) {
583 if (!currentOnlyOverlays.contains(o.Frame))
584 currentOnlyOverlays.add(o.Frame);
585 }
586 }
587
588 // Extract shared overlays between the current and next frame
589 for (Frame of : currentOnlyOverlays) {
590 if (nextOnlyOverlays.contains(of)) {
591 sharedOverlays.add(of);
592 }
593 }
594
595 // The first set, currentOnlyOverlays, must be notified that they
596 // are hidden
597 Collection<Item> items = new LinkedList<Item>();
598
599 // Notify items that will not be in view any more
600 if (getCurrentFrame() != null) {
601 List<Frame> seen = new LinkedList<Frame>();
602 seen.addAll(sharedOverlays); // Signify that seen all shared
603 // overlays
604 seen.remove(getCurrentFrame()); // must ensure
605 // excluded
606
607 // Get all items seen from the current frame - including all
608 // possible non-shared overlays
609 items = getCurrentFrame().getAllItems();
610 for (Frame f : seen)
611 items.removeAll(f.getAllItems());
612
613 // Notify items that they are hidden
614 for (Item i : items) {
615 i.onParentStateChanged(new ItemParentStateChangedEvent(getCurrentFrame(), ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN));
616 }
617 }
618
619 // Set the new frame
620 setFrameOnSide(frame, getCurrentSide());
621 frame.refreshSize();
622
623 // Notify items on the frame being displayed that they are in view
624 // ie. widgets use this method to add themselves to the content pane
625 items.clear();
626
627 // Notify overlay items that they are shown
628 for (Item i : frame.getOverlayItems()) {
629 Overlay owner = frame.getOverlayOwner(i);
630 // if (owner == null) i.onParentFameShown(false, 0);
631 // else ...
632 assert (owner != null);
633 i
634 .onParentStateChanged(new ItemParentStateChangedEvent(
635 frame,
636 ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY,
637 owner.permission));
638 }
639
640 for (Item i : frame.getItems()) {
641 i.onParentStateChanged(new ItemParentStateChangedEvent(frame,
642 ItemParentStateChangedEvent.EVENT_TYPE_SHOWN));
643 }
644 }
645
646 frame.reset();
647
648 // fix text items
649 ItemUtils.Justify(frame);
650
651 StandardGestureActions.refreshHighlights();
652 requestRefresh(false);
653 fireFrameChanged();
654 }
655
656 /** Updates the window title bar to show which mode Expeditee is in. */
657 public static void updateTitle()
658 {
659 StringBuffer title = new StringBuffer(TITLE);
660
661 if (isAudienceMode()) {
662 title.append(" - Audience Mode");
663 } else if (isXRayMode()) {
664 title.append(" - X-Ray Mode");
665 } else {
666 title.append(" [").append(SessionStats.getShortStats()).append(']');
667 }
668
669 EcosystemManager.getGraphicsManager().setWindowTitle(title.toString());
670 }
671
672 /** Whether the given side has a frame. */
673 public static boolean sideHasFrame(TwinFramesSide side)
674 {
675 return getFrameOnSide(side) != null;
676 }
677
678 /** Gets the side of the twin-frames display that the mouse is over. */
679 public static TwinFramesSide getCurrentSide()
680 {
681
682 // If the mouse is over the right side of the window and there's a valid frame,
683 // we are on the right side.
684 if(isTwinFramesOn()) {
685 int mouseX = EcosystemManager.getInputManager().getCursorPosition().getX();
686 if(mouseX >= getTwinFramesSeparatorX() && sideHasFrame(RIGHT)) {
687 return RIGHT;
688 }
689 }
690
691 // If there's only a right frame, that's the current side
692 if (!sideHasFrame(LEFT) && sideHasFrame(RIGHT)) return RIGHT;
693
694 // In any other case the left side is the current side
695 return LEFT;
696 }
697
698 /** Gets the opposite side to the current side of the twin-frames. */
699 private static TwinFramesSide getOppositeSide()
700 {
701 if (getCurrentSide() == LEFT) return RIGHT;
702
703 return LEFT;
704 }
705
706 /** Returns the side that the given frame is on, or null if it's not on either side. */
707 public static TwinFramesSide getSideFrameIsOn(Frame frame)
708 {
709 // Loop through both sides
710 for (TwinFramesSide side : TwinFramesSide.values()) {
711 if (getFrameOnSide(side) == frame) return side;
712 }
713
714 return null;
715 }
716
717 /** Gets the frame that is on the given side of the display. */
718 public static Frame getFrameOnSide(TwinFramesSide side)
719 {
720 if (side == null) return null;
721
722 return _currentFrames[side.ordinal()];
723 }
724
725 /** Returns the frame on the current side of the display. */
726 public static Frame getCurrentFrame()
727 {
728 return getFrameOnSide(getCurrentSide());
729 }
730
731 /** Returns the frame on the opposite side of the display. */
732 public static Frame getOppositeFrame()
733 {
734 return getFrameOnSide(getOppositeSide());
735 }
736
737 /** Gets the two frames being displayed. */
738 public static Frame[] getFrames()
739 {
740 return _currentFrames;
741 }
742
743 /** Gets the x-coordinate of the division between the left and right sides. */
744 public static int getTwinFramesSeparatorX()
745 {
746 return getLeftFramePaintArea().getWidth() + (int) (SEPARATOR_STROKE.thickness / 2);
747 }
748
749 /** Gets the line that separates the two sides of the twin-frames display. */
750 public static Line getTwinFramesSeparatorLine()
751 {
752 if (!isTwinFramesOn()) return null;
753
754 int x = getTwinFramesSeparatorX();
755 int bottom = getFramePaintArea().getMaxY();
756
757 return new Line(x, 0, x, bottom);
758
759 }
760
761 /** Gets the line that separates the message bay from the frame area. */
762 public static Line getMessageBaySeparatorLine()
763 {
764 // No message bay in audience mode
765 if (isAudienceMode()) return null;
766
767 int separatorThickness = (int) SEPARATOR_STROKE.thickness;
768 int y = getFramePaintArea().getMaxY() + separatorThickness / 2 + separatorThickness % 2;
769 int right = getMessageBayPaintArea().getMaxX();
770
771 return new Line(0, y, right, y);
772 }
773
774 /**
775 * Returns the current mouse X coordinate. This coordinate is relative to
776 * the left edge of the frame the mouse is in. It takes into account the
777 * user being in twin frames mode.
778 *
779 * @return The X coordinate of the mouse.
780 */
781 public static float getFloatMouseX()
782 {
783 return (float) getMouseX();
784 }
785
786
787 /**
788 * Returns the current mouse X coordinate. This coordinate is relative to
789 * the left edge of the frame the mouse is in. It takes into account the
790 * user being in twin frames mode.
791 *
792 * @return The X coordinate of the mouse.
793 */
794 public static int getMouseX()
795 {
796 return getMousePosition().getX();
797 }
798
799 /**
800 * Returns the current mouse position. This coordinate is relative to
801 * the upper-left edge of the frame the mouse is in. It takes into account the
802 * user being in twin frames mode.
803 *
804 * @return The position of the mouse.
805 */
806 public static Point getMousePosition()
807 {
808 Point mousePos = EcosystemManager.getInputManager().getCursorPosition();
809
810 if (isTwinFramesOn() && getRightFramePaintArea().contains(mousePos)) {
811 mousePos.setX(mousePos.getX() - getRightFramePaintArea().getMinX());
812 }
813
814 return mousePos;
815 }
816
817 /**
818 * Returns the current mouse Y coordinate. This coordinate is relative to
819 * the top edge of the frame the mouse is in.
820 *
821 * @return The Y coordinate of the mouse.
822 */
823 public static float getFloatMouseY()
824 {
825 return (float) getMouseY();
826 }
827
828 /**
829 * Returns the current mouse Y coordinate. This coordinate is relative to
830 * the top edge of the frame the mouse is in.
831 *
832 * @return The Y coordinate of the mouse.
833 */
834 public static int getMouseY()
835 {
836 return getMousePosition().getY();
837 }
838
839 /**
840 * TODO: Comment. cts16
841 * @return
842 */
843 public static boolean Back()
844 {
845 TwinFramesSide side = getCurrentSide();
846 Stack<String> visitedFrames = getVisitedFrames(side);
847
848 // there must be a frame to go back to
849 if (visitedFrames.size() < 1) {
850 MessageBay.displayMessageOnce("You are already on the home frame");
851 return false;
852 }
853
854 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
855 MessageBay.displayMessage("Error navigating back");
856 return false;
857 }
858
859 String oldFrame = getCurrentFrame().getName().toLowerCase();
860
861 // do not get a cached version (in case it is in the other window)
862 if (isTwinFramesOn()) FrameIO.SuspendCache();
863
864 Frame frame = FrameIO.LoadFrame(removeFromBack());
865 // If the top frame on the backup stack is the current frame go back
866 // again... or if it has been deleted
867 // Recursively backup the stack
868 if (frame == null || frame.equals(getCurrentFrame())) {
869 Back();
870 return false;
871 }
872
873 if (isTwinFramesOn()) {
874 FrameIO.ResumeCache();
875 }
876 getBackedUpFrames(side).push(oldFrame);
877 FrameUtils.DisplayFrame(frame, false, true);
878 StandardGestureActions.setHighlightHold(true);
879
880 for (Item i : frame.getItems()) {
881 if (i.getLink() != null && i.getAbsoluteLink().toLowerCase().equals(oldFrame)) {
882 if (i.getHighlightMode() != Item.HighlightMode.Normal) {
883 i.setHighlightModeAndColour(Item.HighlightMode.Normal,
884 BACK_HIGHLIGHT_COLOR);
885 }
886 // check if its an @f item and if so update the buffer
887 if (i instanceof Picture) {
888 Picture p = (Picture) i;
889 p.refresh();
890 }
891 }
892 }
893 requestRefresh(true);
894 return true;
895 }
896
897 /**
898 * TODO: Comment. cts16
899 * @return
900 */
901 public static boolean Forward()
902 {
903 TwinFramesSide side = getCurrentSide();
904
905 // there must be a frame to go back to
906 if (getBackedUpFrames(side).size() == 0) {
907 return false;
908 }
909
910 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
911 MessageBay.displayMessage("Error navigating forward");
912 return false;
913 }
914
915 String oldFrame = getCurrentFrame().getName().toLowerCase();
916
917 // do not get a cached version (in case it is in the other window)
918 if (isTwinFramesOn())
919 FrameIO.SuspendCache();
920 Frame frame = FrameIO.LoadFrame(getBackedUpFrames(side).pop());
921 // If the top frame on the backup stack is the current frame go back
922 // again... or if it has been deleted
923 // Recursively backup the stack
924 if (frame == null || frame.equals(getCurrentFrame())) {
925 Forward();
926 return false;
927 }
928
929 if (isTwinFramesOn()) {
930 FrameIO.ResumeCache();
931 }
932 getVisitedFrames(side).push(oldFrame);
933 FrameUtils.DisplayFrame(frame, false, true);
934 requestRefresh(true);
935 return true;
936 }
937
938 /** Toggles the display of frames between TwinFrames mode and Single frame mode. */
939 public static void toggleTwinFrames()
940 {
941 // determine which side is the active side
942 TwinFramesSide opposite = getOppositeSide();
943 _twinFramesMode = !_twinFramesMode;
944
945 // if TwinFrames is being turned on
946 if (_twinFramesMode) {
947 // if this is the first time TwinFrames has been toggled on,
948 // load the user's first frame
949 if (getVisitedFrames(opposite).size() == 0) {
950 FrameIO.SuspendCache();
951 setCurrentFrame(FrameIO.LoadFrame(UserSettings.HomeFrame.get()), true);
952 FrameIO.ResumeCache();
953 } else {
954 // otherwise, restore the frame from the side's back-stack
955 setCurrentFrame(FrameIO.LoadFrame(getVisitedFrames(opposite).pop()), true);
956 }
957
958 // else, TwinFrames is being turned off
959 } else {
960 // add the frame to the back-stack
961 Frame hiding = getOppositeFrame();
962 FrameUtils.LeavingFrame(hiding);
963 getVisitedFrames(opposite).add(hiding.getName());
964 setFrameOnSide(null, opposite);
965 getCurrentFrame().refreshSize();
966 }
967
968 // Update the sizes of the displayed frames
969 if (getCurrentFrame() != null) getCurrentFrame().refreshSize();
970 if (getOppositeFrame() != null) getOppositeFrame().refreshSize();
971
972 requestRefresh(false);
973 }
974
975 /** Whether the display is currently in twin-frames mode. */
976 public static boolean isTwinFramesOn()
977 {
978 return _twinFramesMode;
979 }
980
981 /** TODO: Comment. cts16 */
982 public static void Reload(TwinFramesSide side)
983 {
984 if (side == null) return;
985
986 FrameIO.SuspendCache();
987 Frame frame = FrameIO.LoadFrame(getFrameOnSide(side).getName());
988 setFrameOnSide(frame, side);
989 FrameIO.ResumeCache();
990 }
991
992 /**
993 * Moves the cursor the end of this item.
994 *
995 * @param i
996 */
997 public static void MoveCursorToEndOfItem(Item i)
998 {
999 setTextCursor((Text) i, Text.END, true, false, false, false);
1000 }
1001
1002 public static void clearBackedUpFrames()
1003 {
1004 getBackedUpFrames(getCurrentSide()).clear();
1005 }
1006
1007 /**
1008 * @param secondsDelay
1009 * @param s
1010 * @throws InterruptedException
1011 */
1012 public static void typeStringDirect(double secondsDelay, String s) throws InterruptedException
1013 {
1014 for (int i = 0; i < s.length(); i++) {
1015 StandardGestureActions.processChar(s.charAt(i), false);
1016 Thread.sleep((int) (secondsDelay * 1000));
1017 }
1018 }
1019
1020 public static List<String> getUnmodifiableVisitedList()
1021 {
1022 return Collections.unmodifiableList(getVisitedFrames(getCurrentSide()));
1023 }
1024
1025 /** If both sides have the same frame (returns false if both sides have no frame). */
1026 public static boolean bothSidesHaveSameFrame()
1027 {
1028 return (getCurrentFrame() != null && getCurrentFrame() == getOppositeFrame());
1029 }
1030
1031 /** Sets the given side to the given frame. */
1032 private static void setFrameOnSide(Frame frame, TwinFramesSide side)
1033 {
1034 if (side == null) return;
1035
1036 _currentFrames[side.ordinal()] = frame;
1037 }
1038
1039 /** Gets the stack of visited frames for the given side. */
1040 private static Stack<String> getVisitedFrames(TwinFramesSide side)
1041 {
1042 if (side == null) return null;
1043
1044 return _visitedFrames[side.ordinal()];
1045 }
1046
1047 /** Gets the stack of backed-up frames for the given side. */
1048 private static Stack<String> getBackedUpFrames(TwinFramesSide side)
1049 {
1050 if (side == null) return null;
1051
1052 return _backedUpFrames[side.ordinal()];
1053 }
1054
1055 /**
1056 * Rotates through normal -> audience -> audience + fullscreen modes and back again.
1057 */
1058 public static void rotateAudienceModes()
1059 {
1060 Frame current = DisplayController.getCurrentFrame();
1061 GraphicsManager g = EcosystemManager.getGraphicsManager();
1062 if (_audienceMode && g.isFullscreen()) {
1063 ToggleAudienceMode();
1064 g.exitFullscreen();
1065 } else if (_audienceMode) {
1066 if (g.canGoFullscreen()) {
1067 g.goFullscreen();
1068 } else {
1069 ToggleAudienceMode();
1070 }
1071 } else {
1072 ToggleAudienceMode();
1073 ItemUtils.UpdateConnectedToAnnotations(current.getItems());
1074 for (Overlay o : current.getOverlays()) {
1075 ItemUtils.UpdateConnectedToAnnotations(o.Frame.getItems());
1076 }
1077 for (Vector v : current.getVectorsDeep()) {
1078 ItemUtils.UpdateConnectedToAnnotations(v.Frame.getItems());
1079 }
1080 }
1081 }
1082
1083 /**
1084 * If Audience Mode is on this method will toggle it to be off, or
1085 * vice-versa. This results in the Frame being re-parsed and repainted.
1086 */
1087 public static void ToggleAudienceMode()
1088 {
1089 Frame current = DisplayController.getCurrentFrame();
1090
1091 // Turn off x-ray mode if it's on
1092 if (_xrayMode) ToggleXRayMode();
1093
1094 // Toggle audience mode
1095 _audienceMode = !_audienceMode;
1096
1097 refreshPaintAreas();
1098 FrameUtils.Parse(current);
1099 updateTitle();
1100 requestRefresh(false);
1101 }
1102
1103 /**
1104 * If X-Ray Mode is on this method will toggle it to be off, or vice-versa.
1105 * This results in the Frame being re-parsed and repainted.
1106 */
1107 public static void ToggleXRayMode()
1108 {
1109 // Turn off x-ray mode if it is on
1110 if (_audienceMode) ToggleAudienceMode();
1111
1112 _xrayMode = !_xrayMode;
1113
1114 getCurrentFrame().parse();
1115 getCurrentFrame().refreshSize();
1116 updateTitle();
1117 StandardGestureActions.refreshHighlights();
1118 StandardGestureActions.updateCursor();
1119 requestRefresh(false);
1120 }
1121
1122 /** Whether audience mode is currently on. */
1123 public static boolean isAudienceMode()
1124 {
1125 return _audienceMode;
1126 }
1127
1128 /** Whether x-ray mode is currently on. */
1129 public static boolean isXRayMode()
1130 {
1131 return _xrayMode;
1132 }
1133
1134 /** Tells the display controller to get the current window size. */
1135 public static void refreshWindowSize()
1136 {
1137 _windowSize = EcosystemManager.getGraphicsManager().getWindowSize();
1138 _refreshBuffer = Image.createImage(_windowSize, true);
1139 refreshPaintAreas();
1140 }
1141
1142 /** Recalculates the paint areas for the frames and message bay. */
1143 public static void refreshPaintAreas()
1144 {
1145 // Calculate the width of each frame in twin-frames mode
1146 int availableWindowWidth = _windowSize.width - (int) SEPARATOR_STROKE.thickness;
1147 int leftFrameWidth = (int) (availableWindowWidth * _twinFramesLeftWidthProportion);
1148 int rightFrameWidth = availableWindowWidth - leftFrameWidth;
1149
1150 // The height of each frame is the window height, minus the message bay if visible
1151 int frameHeight = _windowSize.height;
1152 if (!isAudienceMode()) {
1153 frameHeight -= _messageBayHeight + (int) SEPARATOR_STROKE.thickness;
1154 }
1155
1156 int rightFrameX = _windowSize.width - rightFrameWidth;
1157 int messageBayY = _windowSize.height - _messageBayHeight;
1158
1159 _leftFramePaintArea = new AxisAlignedBoxBounds(0, 0, leftFrameWidth, frameHeight);
1160 _rightFramePaintArea = new AxisAlignedBoxBounds(rightFrameX, 0, rightFrameWidth, frameHeight);
1161 _framePaintArea = new AxisAlignedBoxBounds(0, 0, _windowSize.width, frameHeight);
1162 _messageBayPaintArea = new AxisAlignedBoxBounds(0, messageBayY, _windowSize.width, _messageBayHeight);
1163 }
1164
1165 public static AxisAlignedBoxBounds getLeftFramePaintArea()
1166 {
1167 return _leftFramePaintArea;
1168 }
1169
1170 public static AxisAlignedBoxBounds getRightFramePaintArea()
1171 {
1172 return _rightFramePaintArea;
1173 }
1174
1175 public static AxisAlignedBoxBounds getFramePaintArea()
1176 {
1177 return _framePaintArea;
1178 }
1179
1180 public static AxisAlignedBoxBounds getMessageBayPaintArea()
1181 {
1182 return _messageBayPaintArea;
1183 }
1184
1185 /**
1186 * Checks that the item is visible (on current frame && overlays) - if
1187 * visible then damaged area will be re-rendered on the next refresh.
1188 *
1189 * @param damagedItem
1190 * @param toRepaint
1191 */
1192 public static void invalidateItem(Item damagedItem, Bounds toRepaint)
1193 {
1194 if (toRepaint == null) return;
1195
1196 // Only add area to repaint if item is visible...
1197 if (ItemUtils.isVisible(damagedItem)) {
1198 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint));
1199 } else if (MessageBay.isMessageItem(damagedItem)) {
1200 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint).translate(0, getMessageBayPaintArea().getMinY()));
1201 }
1202 }
1203
1204 /**
1205 * The given area will be re-rendered in the next refresh. This is the
1206 * quicker version and is more useful for re-rendering animated areas.
1207 */
1208 public static void invalidateArea(AxisAlignedBoxBounds toRepaint)
1209 {
1210 _damagedAreas.addArea(toRepaint);
1211 }
1212
1213 public static void clearInvalidAreas()
1214 {
1215 _damagedAreas.clear();
1216 }
1217
1218 /*
1219 *
1220 * Thread-safe rendering.
1221 *
1222 */
1223
1224 /**
1225 * Redraws the entire window. Shouldn't be called directly, instead use
1226 * requestRefresh(boolean) to ensure no infinite refresh call loops are created.
1227 */
1228 private static boolean refresh(boolean useInvalidation)
1229 {
1230 Frame currentFrame = getCurrentFrame();
1231 if(currentFrame == null) {
1232 return false;
1233 }
1234 //For Monday; at this point, the DisplayController is ready to do something
1235 //RequestRefesh could have a callback method
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.