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

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

Recoding of the Labels class to improve surrogate mode functionality. Surrogate mode is now maintained when you navigate from one frame to another, even if that frame has a different set of labels. Completely different sets of labels cause Expeditee to exit surrogate mode with a message to the user.

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