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

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

Changed surrogates to work the way discussed with David. EncryptedExpReader/Writer updated to work with this.

File size: 48.5 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().getItems()) {
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().getItems()) {
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.getItems()) {
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.getItems()) {
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.getItems());
1134 for (Overlay o : current.getOverlays()) {
1135 ItemUtils.UpdateConnectedToAnnotations(o.Frame.getItems());
1136 }
1137 for (Vector v : current.getVectorsDeep()) {
1138 ItemUtils.UpdateConnectedToAnnotations(v.Frame.getItems());
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 {
1211 // Turn off x-ray mode if it is on
1212 if (_audienceMode) {
1213 ToggleAudienceMode();
1214 }
1215
1216 _xrayMode = !_xrayMode;
1217
1218 if(_xrayMode) {
1219 //Entering XRay Mode is a Save point for saveable entities
1220 EntitySaveManager.getInstance().saveAll();
1221 }
1222
1223 getCurrentFrame().parse();
1224 getCurrentFrame().refreshSize();
1225 updateTitle();
1226 StandardGestureActions.refreshHighlights();
1227 StandardGestureActions.updateCursor();
1228 requestRefresh(false);
1229 }
1230
1231 public static void ToggleSurrogateMode() {
1232 // Turn off x-ray mode if it is on
1233 if (_audienceMode) {
1234 ToggleAudienceMode();
1235 }
1236
1237 Label.progressSurrogateMode();
1238
1239 getCurrentFrame().parse();
1240 getCurrentFrame().refreshSize();
1241 updateTitle();
1242 StandardGestureActions.refreshHighlights();
1243 StandardGestureActions.updateCursor();
1244 requestRefresh(false);
1245 }
1246
1247 public static void ResetSurrogateMode() {
1248 // Turn off x-ray mode if it is on
1249 if (_audienceMode) {
1250 ToggleAudienceMode();
1251 }
1252
1253 Label.resetSurrogateMode();
1254
1255 getCurrentFrame().parse();
1256 getCurrentFrame().refreshSize();
1257 updateTitle();
1258 StandardGestureActions.refreshHighlights();
1259 StandardGestureActions.updateCursor();
1260 requestRefresh(false);
1261 }
1262
1263 /** Whether audience mode is currently on. */
1264 public static boolean isAudienceMode()
1265 {
1266 return _audienceMode;
1267 }
1268
1269 /** Whether mail mode is currently on. */
1270 public static boolean isMailMode() {
1271 return _mailMode;
1272 }
1273
1274 /** Whether x-ray mode is currently on. */
1275 public static boolean isXRayMode()
1276 {
1277 return _xrayMode;
1278 }
1279
1280 public static Dimension getSizeEnforceMinimumXXXX() { // **** DB
1281 Dimension actual_dim = getFramePaintAreaSize();
1282
1283 int enforced_width = Math.max(actual_dim.width, MINIMUM_FRAME_WIDTH);
1284 int enforced_height = Math.max(actual_dim.height,MINIMUM_FRAME_HEIGHT);
1285
1286 Dimension enforced_dim = new Dimension(enforced_width,enforced_height);
1287
1288 return enforced_dim;
1289 }
1290
1291 /** Tells the display controller to get the current window size. */
1292 public static void refreshWindowSize()
1293 {
1294 _windowSize = EcosystemManager.getGraphicsManager().getWindowSize();
1295 _refreshBuffer = Image.createImage(_windowSize, true);
1296 if (_windowSize.getWidth() > MINIMUM_FRAME_WIDTH) {
1297 DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH = true;
1298 }
1299 refreshPaintAreas();
1300 }
1301
1302 /** Recalculates the paint areas for the frames and message bay. */
1303 public static void refreshPaintAreas()
1304 {
1305 // Calculate the width of each frame in twin-frames mode
1306 int availableWindowWidth = _windowSize.width - (int) SEPARATOR_STROKE.thickness;
1307 int leftFrameWidth = (int) (availableWindowWidth * _twinFramesLeftWidthProportion);
1308 int rightFrameWidth = availableWindowWidth - leftFrameWidth;
1309
1310 // The height of each frame is the window height, minus the message bay if visible
1311 int frameHeight = _windowSize.height;
1312 if (!isAudienceMode()) {
1313 frameHeight -= _messageBayHeight + (int) SEPARATOR_STROKE.thickness;
1314 }
1315
1316 int rightFrameX = _windowSize.width - rightFrameWidth;
1317 int messageBayY = _windowSize.height - _messageBayHeight;
1318
1319 _leftFramePaintArea = new AxisAlignedBoxBounds(0, 0, leftFrameWidth, frameHeight);
1320 _rightFramePaintArea = new AxisAlignedBoxBounds(rightFrameX, 0, rightFrameWidth, frameHeight);
1321 _framePaintArea = new AxisAlignedBoxBounds(0, 0, _windowSize.width, frameHeight);
1322 _messageBayPaintArea = new AxisAlignedBoxBounds(0, messageBayY, _windowSize.width, _messageBayHeight);
1323 }
1324
1325 public static AxisAlignedBoxBounds getLeftFramePaintArea()
1326 {
1327 return _leftFramePaintArea;
1328 }
1329
1330 public static AxisAlignedBoxBounds getRightFramePaintArea()
1331 {
1332 return _rightFramePaintArea;
1333 }
1334
1335 public static AxisAlignedBoxBounds getFramePaintArea()
1336 {
1337 return _framePaintArea;
1338 }
1339
1340 public static int getFramePaintAreaWidth()
1341 {
1342 if (!DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH) {
1343 return MINIMUM_FRAME_WIDTH;
1344 }
1345
1346 return _framePaintArea.getWidth();
1347 }
1348
1349 public static int getFramePaintAreaHeight()
1350 {
1351 if (!DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH) {
1352 return MINIMUM_FRAME_HEIGHT;
1353 }
1354
1355 return _framePaintArea.getHeight();
1356 }
1357
1358 public static Dimension getFramePaintAreaSize()
1359 {
1360 if (!DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH) {
1361 Dimension min_dim = new Dimension(MINIMUM_FRAME_WIDTH,MINIMUM_FRAME_HEIGHT);
1362 return min_dim;
1363 }
1364
1365 return _framePaintArea.getSize();
1366 }
1367
1368 public static AxisAlignedBoxBounds getMessageBayPaintArea()
1369 {
1370 return _messageBayPaintArea;
1371 }
1372
1373 /**
1374 * Checks that the item is visible (on current frame && overlays) - if
1375 * visible then damaged area will be re-rendered on the next refresh.
1376 *
1377 * @param damagedItem
1378 * @param toRepaint
1379 */
1380 public static void invalidateItem(Item damagedItem, Bounds toRepaint)
1381 {
1382 if (toRepaint == null) {
1383 return;
1384 }
1385
1386 // Only add area to repaint if item is visible...
1387 if (ItemUtils.isVisible(damagedItem)) {
1388 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint));
1389 } else if (!_mailMode && MessageBay.isMessageItem(damagedItem)) {
1390 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint).translate(0, getMessageBayPaintArea().getMinY()));
1391 } else if (_mailMode && MailBay.isPreviewMailItem(damagedItem)) {
1392 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint).translate(0, getMessageBayPaintArea().getMinY()));
1393 }
1394 }
1395
1396 /**
1397 * The given area will be re-rendered in the next refresh. This is the
1398 * quicker version and is more useful for re-rendering animated areas.
1399 */
1400 public static void invalidateArea(AxisAlignedBoxBounds toRepaint)
1401 {
1402 _damagedAreas.addArea(toRepaint);
1403 }
1404
1405 public static void clearInvalidAreas()
1406 {
1407 _damagedAreas.clear();
1408 }
1409
1410 public static void refreshBayArea() {
1411 // Always get the clip as it clears at the same time
1412 Clip clip = _damagedAreas.getClip();
1413 GraphicsManager g = EcosystemManager.getGraphicsManager();
1414 pushBayAreaUpdate(true, clip, g);
1415 }
1416
1417 /**
1418 * Redraws the entire window. Shouldn't be called directly, instead use
1419 * requestRefresh(boolean) to ensure no infinite refresh call loops are created.
1420 */
1421 private static boolean refresh(boolean useInvalidation)
1422 {
1423 Frame currentFrame = getCurrentFrame();
1424 if(currentFrame == null) {
1425 return false;
1426 }
1427
1428 // Always get the clip as it clears at the same time
1429 Clip clip = _damagedAreas.getClip();
1430
1431 // No damaged areas
1432 if (useInvalidation && clip.isFullyClipped()) {
1433 return true;
1434 }
1435
1436 GraphicsManager g = EcosystemManager.getGraphicsManager();
1437 g.pushDrawingSurface(_refreshBuffer);
1438
1439 if (isTwinFramesOn()) {
1440 Clip leftClip = null;
1441 Clip rightClip = null;
1442
1443 if (useInvalidation) {
1444 leftClip = clip.clone().intersectWith(getLeftFramePaintArea());
1445 rightClip = clip.clone().intersectWith(getRightFramePaintArea());
1446 if (!rightClip.isFullyClipped()) {
1447 rightClip = new Clip(rightClip.getBounds().translate(-getRightFramePaintArea().getMinX(), 0));
1448 }
1449 }
1450
1451 Image left = null;
1452 if (!useInvalidation || !leftClip.isFullyClipped()) {
1453 left = FrameGraphics.getFrameImage(getFrameOnSide(LEFT), leftClip, getLeftFramePaintArea().getSize());
1454 }
1455
1456 Image right = null;
1457 if (!useInvalidation || !rightClip.isFullyClipped()) {
1458 right = FrameGraphics.getFrameImage(getFrameOnSide(RIGHT), rightClip, getRightFramePaintArea().getSize());
1459 }
1460
1461 paintTwinFrames(left, right);
1462 } else {
1463 Clip frameClip = null;
1464
1465 if (useInvalidation) {
1466 frameClip = clip.clone().intersectWith(getFramePaintArea());
1467 }
1468
1469 Image image = null;
1470 if (currentFrame != null && (!useInvalidation || !frameClip.isFullyClipped())) {
1471 image = FrameGraphics.getFrameImage(currentFrame, frameClip, getFramePaintAreaSize());
1472 }
1473
1474 paintSingleFrame(image);
1475 }
1476
1477 pushBayAreaUpdate(useInvalidation, clip, g);
1478
1479 // Draw any separator lines
1480 g.drawLine(getMessageBaySeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1481 g.drawLine(getTwinFramesSeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1482
1483 // Paint any popups
1484 if (PopupManager.getInstance() != null) {
1485 PopupManager.getInstance().paint();
1486 }
1487
1488 g.popDrawingSurface();
1489 g.drawImage(_refreshBuffer, Point.ORIGIN);
1490 return true;
1491 }
1492
1493 private static void pushBayAreaUpdate(boolean useInvalidation, Clip clip, GraphicsManager g) {
1494 if (!isAudienceMode()) {
1495 if (!isMailMode()) {
1496 // Paint message bay
1497 paintMessageBay(useInvalidation, clip, g);
1498 } else {
1499 // Paint mail bay
1500 paintMailBay(useInvalidation, clip, g);
1501 }
1502 }
1503 }
1504
1505 private static void paintMessageBay(boolean useInvalidation, Clip clip, GraphicsManager g) {
1506 Clip messageBayClip = null;
1507
1508 AxisAlignedBoxBounds messageBayPaintArea = getMessageBayPaintArea();
1509 if (useInvalidation) {
1510 messageBayClip = clip.clone().intersectWith(messageBayPaintArea);
1511 if (!messageBayClip.isFullyClipped()) {
1512 messageBayClip = new Clip(messageBayClip.getBounds().translate(0, -messageBayPaintArea.getMinY()));
1513 }
1514 }
1515
1516 Image image = null;
1517 if (!useInvalidation || !messageBayClip.isFullyClipped()) {
1518 image = MessageBay.getImage(messageBayClip, messageBayPaintArea.getSize());
1519 }
1520
1521 if (image != null) {
1522 g.drawImage(image, messageBayPaintArea.getTopLeft(), messageBayPaintArea.getSize());
1523 }
1524 }
1525
1526 private static void paintMailBay(boolean useInvalidation, Clip clip, GraphicsManager g) {
1527 Clip mailBayClip = null;
1528
1529 AxisAlignedBoxBounds mailBayPaintArea = getMessageBayPaintArea();
1530 if (useInvalidation) {
1531 mailBayClip = clip.clone().intersectWith(mailBayPaintArea);
1532 if (!mailBayClip.isFullyClipped()) {
1533 mailBayClip = new Clip(mailBayClip.getBounds().translate(0, -mailBayPaintArea.getMinY()));
1534 }
1535 }
1536
1537 Image image = null;
1538 if (!useInvalidation || !mailBayClip.isFullyClipped()) {
1539 image = MailBay.getImage(mailBayClip, mailBayPaintArea.getSize());
1540 }
1541
1542 if (image != null) {
1543 g.drawImage(image, mailBayPaintArea.getTopLeft(), mailBayPaintArea.getSize());
1544 }
1545 }
1546
1547 /** Draws the two frame images to the display and puts the separator line between them. */
1548 private static void paintTwinFrames(Image leftFrameImage, Image rightFrameImage)
1549 {
1550 if (leftFrameImage == null && rightFrameImage == null) {
1551 return;
1552 }
1553
1554 InOutReference<FrameTransition> leftTransition = new InOutReference<FrameTransition>(getTransition(LEFT));
1555 InOutReference<FrameTransition> rightTransition = new InOutReference<FrameTransition>(getTransition(RIGHT));
1556
1557 paintFrameIntoArea(leftFrameImage, getLeftFramePaintArea(), leftTransition);
1558 paintFrameIntoArea(rightFrameImage, getRightFramePaintArea(), rightTransition);
1559
1560 setTransition(LEFT, leftTransition.get());
1561 setTransition(RIGHT, rightTransition.get());
1562 }
1563
1564 /** Draws the single frame image to the screen. */
1565 private static void paintSingleFrame(Image frameImage)
1566 {
1567 if (frameImage == null) {
1568 return;
1569 }
1570
1571 InOutReference<FrameTransition> transition = new InOutReference<FrameTransition>(getTransition(getCurrentSide()));
1572
1573 paintFrameIntoArea(frameImage, getFramePaintArea(), transition);
1574
1575 setTransition(getCurrentSide(), transition.get());
1576 }
1577
1578 /** Draws the given frame image into the given area, using a transition if one is provided. */
1579 private static void paintFrameIntoArea(Image frameImage, AxisAlignedBoxBounds area, InOutReference<FrameTransition> transition)
1580 {
1581 if (frameImage == null || area == null) {
1582 return;
1583 }
1584
1585 GraphicsManager g = EcosystemManager.getGraphicsManager();
1586 EnforcedClipKey key = g.pushClip(new Clip(area));
1587
1588 FrameTransition t = null;
1589 if (transition != null) {
1590 t = transition.get();
1591 }
1592
1593 // Attempt to draw the transition if there is one
1594 if (t != null) {
1595 if (!t.drawTransition(frameImage, area)) {
1596 // If drawing failed, throw the transition away
1597 t = null;
1598 } else {
1599 // Schedule the next frame to be drawn
1600 invalidateArea(area);
1601 requestRefresh(true);
1602 }
1603 }
1604
1605 // If t == null at this stage, no transition has been drawn, so just draw the image
1606 if (t == null) {
1607 g.drawImage(frameImage, area.getTopLeft(), area.getSize());
1608 }
1609
1610 // Discard the transition if drawing failed or it is finished
1611 if (t == null || t.isCompleted()) {
1612 transition.set(null);
1613 }
1614
1615 g.popClip(key);
1616 }
1617
1618 /** Runnable which ensures overlapping refresh requests are joined. */
1619 private static RenderRequestMarsheller _requestMarsheller = new RenderRequestMarsheller();
1620
1621 /**
1622 * If wanting to refresh from another thread - other than the main thread
1623 * that handles the expeditee datamodel (modifying / accessing / rendering).
1624 * Use this method for thread safety.
1625 */
1626 public static synchronized void requestRefresh(boolean useInvalidation) {
1627 requestRefresh(useInvalidation, null);
1628 }
1629
1630 public static synchronized void requestRefresh(final boolean useInvalidation, final BooleanSupplier callback) {
1631 try {
1632 _requestMarsheller.enqueue(useInvalidation, callback);
1633 } catch (Throwable e) {
1634 e.printStackTrace();
1635 }
1636 }
1637
1638 /**
1639 * Used for marshelling render requests from foreign threads to the event
1640 * dispatcher thread... (AWT)
1641 *
1642 * @author Brook Novak
1643 */
1644 private static class RenderRequestMarsheller implements Runnable {
1645
1646 private boolean _useInvalidation = true;
1647 private boolean _enqueued = false;
1648 private Boolean _requeueWithInvalidation = null;
1649 private Object _lock = new Object();
1650 private List<BooleanSupplier> callbacks = new LinkedList<BooleanSupplier>();
1651
1652 /** Enqueues a redraw on the GIO thread, or remembers to do so once the current redraw finishes. */
1653 public void enqueue(final boolean useInvalidation, final BooleanSupplier callback)
1654 {
1655 synchronized(callbacks) {
1656 if(callback != null) {
1657 callbacks.add(callback);
1658 }
1659 }
1660 synchronized (_lock) {
1661 if (!_enqueued) {
1662 _enqueued = true;
1663 _useInvalidation = useInvalidation;
1664 EcosystemManager.getMiscManager().runOnGIOThread(this);
1665 } else if (_requeueWithInvalidation == null || _requeueWithInvalidation == true) {
1666 _requeueWithInvalidation = useInvalidation;
1667 }
1668 }
1669 }
1670
1671 @Override
1672 public void run()
1673 {
1674 try {
1675 if(refresh(_useInvalidation)) {
1676 synchronized (callbacks) {
1677 callbacks.forEach(cb -> cb.getAsBoolean());
1678 callbacks.clear();
1679 }
1680 }
1681 } catch (Throwable e) {
1682 e.printStackTrace();
1683 }
1684
1685 // Do another redraw if we received a new request while doing this one.
1686 synchronized (_lock) {
1687 if (_requeueWithInvalidation != null) {
1688 _useInvalidation = _requeueWithInvalidation.booleanValue();
1689 _requeueWithInvalidation = null;
1690 EcosystemManager.getMiscManager().runOnGIOThread(this);
1691 } else {
1692 _enqueued = false;
1693 }
1694 }
1695 }
1696 }
1697}
Note: See TracBrowser for help on using the repository browser.