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

Last change on this file since 1480 was 1480, checked in by bnemhaus, 4 years ago

Window title now notifies you when you are in surrogate mode.
Window title also now notifies you when you are running demo mode.

File size: 49.8 KB
Line 
1/**
2 * DisplayIO.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.gui;
20
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.HashSet;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Stack;
28import java.util.function.BooleanSupplier;
29
30import org.expeditee.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 if (Boolean.getBoolean("expeditee.demo-mode")) {
703 title.append( " [DEMO MODE ACTIVE] ");
704 }
705
706 if (Label.isInSurrogateMode()) {
707 title.append(" [" + Label.surrogateModeString() + "]");
708 }
709
710 EcosystemManager.getGraphicsManager().setWindowTitle(title.toString());
711 }
712
713 /** Whether the given side has a frame. */
714 public static boolean sideHasFrame(TwinFramesSide side)
715 {
716 return getFrameOnSide(side) != null;
717 }
718
719 /** Gets the side of the twin-frames display that the mouse is over. */
720 public static TwinFramesSide getCurrentSide()
721 {
722
723 // If the mouse is over the right side of the window and there's a valid frame,
724 // we are on the right side.
725 if(isTwinFramesOn()) {
726 int mouseX = EcosystemManager.getInputManager().getCursorPosition().getX();
727 if(mouseX >= getTwinFramesSeparatorX() && sideHasFrame(RIGHT)) {
728 return RIGHT;
729 }
730 }
731
732 // If there's only a right frame, that's the current side
733 if (!sideHasFrame(LEFT) && sideHasFrame(RIGHT)) {
734 return RIGHT;
735 }
736
737 // In any other case the left side is the current side
738 return LEFT;
739 }
740
741 /** Gets the opposite side to the current side of the twin-frames. */
742 private static TwinFramesSide getOppositeSide()
743 {
744 if (getCurrentSide() == LEFT) {
745 return RIGHT;
746 }
747
748 return LEFT;
749 }
750
751 /** Returns the side that the given frame is on, or null if it's not on either side. */
752 public static TwinFramesSide getSideFrameIsOn(Frame frame)
753 {
754 // Loop through both sides
755 for (TwinFramesSide side : TwinFramesSide.values()) {
756 if (getFrameOnSide(side) == frame) {
757 return side;
758 }
759 }
760
761 return null;
762 }
763
764 /** Gets the frame that is on the given side of the display. */
765 public static Frame getFrameOnSide(TwinFramesSide side)
766 {
767 if (side == null) {
768 return null;
769 }
770
771 return _currentFrames[side.ordinal()];
772 }
773
774 /** Returns the frame on the current side of the display. */
775 public static Frame getCurrentFrame()
776 {
777 return getFrameOnSide(getCurrentSide());
778 }
779
780 /** Returns the frame on the opposite side of the display. */
781 public static Frame getOppositeFrame()
782 {
783 return getFrameOnSide(getOppositeSide());
784 }
785
786 /** Gets the two frames being displayed. */
787 public static Frame[] getFrames()
788 {
789 return _currentFrames;
790 }
791
792 /** Gets the x-coordinate of the division between the left and right sides. */
793 public static int getTwinFramesSeparatorX()
794 {
795 return getLeftFramePaintArea().getWidth() + (int) (SEPARATOR_STROKE.thickness / 2);
796 }
797
798 /** Gets the line that separates the two sides of the twin-frames display. */
799 public static Line getTwinFramesSeparatorLine()
800 {
801 if (!isTwinFramesOn()) {
802 return null;
803 }
804
805 int x = getTwinFramesSeparatorX();
806 int bottom = getFramePaintArea().getMaxY();
807
808 return new Line(x, 0, x, bottom);
809
810 }
811
812 /** Gets the line that separates the message bay from the frame area. */
813 public static Line getMessageBaySeparatorLine()
814 {
815 // No message bay in audience mode
816 if (isAudienceMode()) {
817 return null;
818 }
819
820 int separatorThickness = (int) SEPARATOR_STROKE.thickness;
821 int y = getFramePaintArea().getMaxY() + separatorThickness / 2 + separatorThickness % 2;
822 int right = getMessageBayPaintArea().getMaxX();
823
824 return new Line(0, y, right, y);
825 }
826
827 /**
828 * Returns the current mouse X coordinate. This coordinate is relative to
829 * the left edge of the frame the mouse is in. It takes into account the
830 * user being in twin frames mode.
831 *
832 * @return The X coordinate of the mouse.
833 */
834 public static float getFloatMouseX()
835 {
836 return getMouseX();
837 }
838
839
840 /**
841 * Returns the current mouse X coordinate. This coordinate is relative to
842 * the left edge of the frame the mouse is in. It takes into account the
843 * user being in twin frames mode.
844 *
845 * @return The X coordinate of the mouse.
846 */
847 public static int getMouseX()
848 {
849 return getMousePosition().getX();
850 }
851
852 /**
853 * Returns the current mouse position. This coordinate is relative to
854 * the upper-left edge of the frame the mouse is in. It takes into account the
855 * user being in twin frames mode.
856 *
857 * @return The position of the mouse.
858 */
859 public static Point getMousePosition()
860 {
861 Point mousePos = EcosystemManager.getInputManager().getCursorPosition();
862
863 if (isTwinFramesOn() && getRightFramePaintArea().contains(mousePos)) {
864 mousePos.setX(mousePos.getX() - getRightFramePaintArea().getMinX());
865 }
866
867 return mousePos;
868 }
869
870 /**
871 * Returns the current mouse Y coordinate. This coordinate is relative to
872 * the top edge of the frame the mouse is in.
873 *
874 * @return The Y coordinate of the mouse.
875 */
876 public static float getFloatMouseY()
877 {
878 return getMouseY();
879 }
880
881 /**
882 * Returns the current mouse Y coordinate. This coordinate is relative to
883 * the top edge of the frame the mouse is in.
884 *
885 * @return The Y coordinate of the mouse.
886 */
887 public static int getMouseY()
888 {
889 return getMousePosition().getY();
890 }
891
892 /**
893 * TODO: Comment. cts16
894 * @return
895 */
896 public static boolean Back()
897 {
898 TwinFramesSide side = getCurrentSide();
899 Stack<String> visitedFrames = getVisitedFrames(side);
900
901 // there must be a frame to go back to
902 if (visitedFrames.size() < 1) {
903 MessageBay.displayMessageOnce("You are already on the home frame");
904 return false;
905 }
906
907 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
908 MessageBay.displayMessage("Error navigating back");
909 return false;
910 }
911
912 String oldFrame = getCurrentFrame().getName().toLowerCase();
913
914 // do not get a cached version (in case it is in the other window)
915 if (isTwinFramesOn()) {
916 FrameIO.SuspendCache();
917 }
918
919 Frame frame = FrameIO.LoadFrame(removeFromBack());
920 // If the top frame on the backup stack is the current frame go back
921 // again... or if it has been deleted
922 // Recursively backup the stack
923 if (frame == null || frame.equals(getCurrentFrame())) {
924 Back();
925 return false;
926 }
927
928 if (isTwinFramesOn()) {
929 FrameIO.ResumeCache();
930 }
931 getBackedUpFrames(side).push(oldFrame);
932 FrameUtils.DisplayFrame(frame, false, true);
933 StandardGestureActions.setHighlightHold(true);
934
935 for (Item i : frame.getSortedItems()) {
936 if (i.getLink() != null && i.getAbsoluteLink().toLowerCase().equals(oldFrame)) {
937 if (i.getHighlightMode() != Item.HighlightMode.Normal) {
938 i.setHighlightModeAndColour(Item.HighlightMode.Normal,
939 BACK_HIGHLIGHT_COLOR);
940 }
941 // check if its an @f item and if so update the buffer
942 if (i instanceof Picture) {
943 Picture p = (Picture) i;
944 p.refresh();
945 }
946 }
947 }
948 requestRefresh(true);
949 return true;
950 }
951
952 /**
953 * TODO: Comment. cts16
954 * @return
955 */
956 public static boolean Forward()
957 {
958 TwinFramesSide side = getCurrentSide();
959
960 // there must be a frame to go back to
961 if (getBackedUpFrames(side).size() == 0) {
962 return false;
963 }
964
965 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
966 MessageBay.displayMessage("Error navigating forward");
967 return false;
968 }
969
970 String oldFrame = getCurrentFrame().getName().toLowerCase();
971
972 // do not get a cached version (in case it is in the other window)
973 if (isTwinFramesOn()) {
974 FrameIO.SuspendCache();
975 }
976 Frame frame = FrameIO.LoadFrame(getBackedUpFrames(side).pop());
977 // If the top frame on the backup stack is the current frame go back
978 // again... or if it has been deleted
979 // Recursively backup the stack
980 if (frame == null || frame.equals(getCurrentFrame())) {
981 Forward();
982 return false;
983 }
984
985 if (isTwinFramesOn()) {
986 FrameIO.ResumeCache();
987 }
988 getVisitedFrames(side).push(oldFrame);
989 FrameUtils.DisplayFrame(frame, false, true);
990 requestRefresh(true);
991 return true;
992 }
993
994 /** Toggles the display of frames between TwinFrames mode and Single frame mode. */
995 public static void toggleTwinFrames()
996 {
997 // determine which side is the active side
998 TwinFramesSide opposite = getOppositeSide();
999 _twinFramesMode = !_twinFramesMode;
1000
1001 // if TwinFrames is being turned on
1002 if (_twinFramesMode) {
1003 // if this is the first time TwinFrames has been toggled on,
1004 // load the user's first frame
1005 if (getVisitedFrames(opposite).size() == 0) {
1006 FrameIO.SuspendCache();
1007 setCurrentFrame(FrameIO.LoadFrame(UserSettings.HomeFrame.get()), true);
1008 FrameIO.ResumeCache();
1009 } else {
1010 // otherwise, restore the frame from the side's back-stack
1011 setCurrentFrame(FrameIO.LoadFrame(getVisitedFrames(opposite).pop()), true);
1012 }
1013
1014 // else, TwinFrames is being turned off
1015 } else {
1016 // add the frame to the back-stack
1017 Frame hiding = getOppositeFrame();
1018 FrameUtils.LeavingFrame(hiding);
1019 getVisitedFrames(opposite).add(hiding.getName());
1020 setFrameOnSide(null, opposite);
1021 getCurrentFrame().refreshSize();
1022 }
1023
1024 // Update the sizes of the displayed frames
1025 if (getCurrentFrame() != null) {
1026 getCurrentFrame().refreshSize();
1027 }
1028 if (getOppositeFrame() != null) {
1029 getOppositeFrame().refreshSize();
1030 }
1031
1032 requestRefresh(false);
1033 }
1034
1035 /** Whether the display is currently in twin-frames mode. */
1036 public static boolean isTwinFramesOn()
1037 {
1038 return _twinFramesMode;
1039 }
1040
1041 /** TODO: Comment. cts16 */
1042 public static void Reload(TwinFramesSide side)
1043 {
1044 if (side == null) {
1045 return;
1046 }
1047
1048 FrameIO.SuspendCache();
1049 Frame frame = FrameIO.LoadFrame(getFrameOnSide(side).getName());
1050 setFrameOnSide(frame, side);
1051 FrameIO.ResumeCache();
1052 }
1053
1054 /**
1055 * Moves the cursor the end of this item.
1056 *
1057 * @param i
1058 */
1059 public static void MoveCursorToEndOfItem(Item i)
1060 {
1061 setTextCursor((Text) i, Text.END, true, false, false, false);
1062 }
1063
1064 public static void clearBackedUpFrames()
1065 {
1066 getBackedUpFrames(getCurrentSide()).clear();
1067 }
1068
1069 /**
1070 * @param secondsDelay
1071 * @param s
1072 * @throws InterruptedException
1073 */
1074 public static void typeStringDirect(double secondsDelay, String s) throws InterruptedException
1075 {
1076 for (int i = 0; i < s.length(); i++) {
1077 StandardGestureActions.processChar(s.charAt(i), false);
1078 Thread.sleep((int) (secondsDelay * 1000));
1079 }
1080 }
1081
1082 public static List<String> getUnmodifiableVisitedList()
1083 {
1084 return Collections.unmodifiableList(getVisitedFrames(getCurrentSide()));
1085 }
1086
1087 /** If both sides have the same frame (returns false if both sides have no frame). */
1088 public static boolean bothSidesHaveSameFrame()
1089 {
1090 return (getCurrentFrame() != null && getCurrentFrame() == getOppositeFrame());
1091 }
1092
1093 /** Sets the given side to the given frame. */
1094 private static void setFrameOnSide(Frame frame, TwinFramesSide side)
1095 {
1096 if (side == null) {
1097 return;
1098 }
1099
1100 _currentFrames[side.ordinal()] = frame;
1101 }
1102
1103 /** Gets the stack of visited frames for the given side. */
1104 private static Stack<String> getVisitedFrames(TwinFramesSide side)
1105 {
1106 if (side == null) {
1107 return null;
1108 }
1109
1110 return _visitedFrames[side.ordinal()];
1111 }
1112
1113 /** Gets the stack of backed-up frames for the given side. */
1114 private static Stack<String> getBackedUpFrames(TwinFramesSide side)
1115 {
1116 if (side == null) {
1117 return null;
1118 }
1119
1120 return _backedUpFrames[side.ordinal()];
1121 }
1122
1123 /**
1124 * Rotates through normal -> audience -> audience + fullscreen modes and back again.
1125 */
1126 public static void rotateAudienceModes()
1127 {
1128 Frame current = DisplayController.getCurrentFrame();
1129 GraphicsManager g = EcosystemManager.getGraphicsManager();
1130 if (_audienceMode && g.isFullscreen()) {
1131 ToggleAudienceMode();
1132 g.exitFullscreen();
1133 } else if (_audienceMode) {
1134 if (g.canGoFullscreen()) {
1135 g.goFullscreen();
1136 } else {
1137 ToggleAudienceMode();
1138 }
1139 } else {
1140 ToggleAudienceMode();
1141 ItemUtils.UpdateConnectedToAnnotations(current.getSortedItems());
1142 for (Overlay o : current.getOverlays()) {
1143 ItemUtils.UpdateConnectedToAnnotations(o.Frame.getSortedItems());
1144 }
1145 for (Vector v : current.getVectorsDeep()) {
1146 ItemUtils.UpdateConnectedToAnnotations(v.Frame.getSortedItems());
1147 }
1148 }
1149 }
1150
1151 /**
1152 * If Audience Mode is on this method will toggle it to be off, or
1153 * vice-versa. This results in the Frame being re-parsed and repainted.
1154 */
1155 public static void ToggleAudienceMode()
1156 {
1157 Frame current = DisplayController.getCurrentFrame();
1158
1159 // Turn off x-ray mode if it's on
1160 if (_xrayMode) {
1161 ToggleXRayMode();
1162 }
1163
1164 // Toggle audience mode
1165 _audienceMode = !_audienceMode;
1166
1167 refreshPaintAreas();
1168 FrameUtils.Parse(current);
1169 updateTitle();
1170 requestRefresh(false);
1171 }
1172
1173 /**
1174 * If Mail Mode is on, this method will toggle it to be off, or vice-versa.
1175 * This results in the Frame being re-parsed and repainted.
1176 */
1177 public static void ToggleMailMode() {
1178 Frame current = DisplayController.getCurrentFrame();
1179
1180 // Turn off x-ray mode if it's on
1181 if (_xrayMode) {
1182 ToggleXRayMode();
1183 }
1184
1185 _mailMode = !_mailMode;
1186
1187 refreshPaintAreas();
1188 FrameUtils.Parse(current);
1189 updateTitle();
1190 requestRefresh(false);
1191 }
1192
1193 /**
1194 * Turns Mail Mode off.
1195 * This results in the Frame being re-parsed and repainted.
1196 */
1197 public static void DisableMailMode() {
1198 Frame current = DisplayController.getCurrentFrame();
1199
1200 // Turn off x-ray mode if it's on
1201 if (_xrayMode) {
1202 ToggleXRayMode();
1203 }
1204
1205 _mailMode = false;
1206
1207 refreshPaintAreas();
1208 FrameUtils.Parse(current);
1209 updateTitle();
1210 requestRefresh(false);
1211 }
1212
1213 /**
1214 * If X-Ray Mode is on this method will toggle it to be off, or vice-versa.
1215 * This results in the Frame being re-parsed and repainted.
1216 */
1217 public static void ToggleXRayMode() {
1218 // Turn off x-ray mode if it is on
1219 if (_audienceMode) {
1220 ToggleAudienceMode();
1221 }
1222
1223 _xrayMode = !_xrayMode;
1224
1225 if(_xrayMode) {
1226 //Entering XRay Mode is a Save point for saveable entities
1227 EntitySaveManager.getInstance().saveAll();
1228 }
1229
1230 getCurrentFrame().parse();
1231 getCurrentFrame().refreshSize();
1232 updateTitle();
1233 StandardGestureActions.refreshHighlights();
1234 StandardGestureActions.updateCursor();
1235 requestRefresh(false);
1236 }
1237
1238 public static void ToggleSurrogateMode() {
1239 // Turn off x-ray mode if it is on
1240 if (_audienceMode) {
1241 ToggleAudienceMode();
1242 }
1243
1244 Frame currentFrame = DisplayController.getCurrentFrame();
1245 ItemsList primaryBody = currentFrame.getPrimaryBody();
1246 ItemsList primaryBodyCopy = new ItemsList(primaryBody);
1247 List<String> accessibleLabelsNames = Label.progressSurrogateMode(primaryBodyCopy);
1248 if (Label.isInSurrogateMode()) {
1249 if (accessibleLabelsNames == null) {
1250 MessageBay.displayMessage("Unable to enter surrogate mode as there are no encryption labels on the current Frame");
1251 } else if (accessibleLabelsNames.isEmpty()) {
1252 MessageBay.displayMessage("Surrogate Mode currently accepts no labels. (Unprivileged view)");
1253 } else {
1254 StringBuilder sb = new StringBuilder("Surrogate Mode currently accepts labels: ");
1255 for (String acceessibleLabel: accessibleLabelsNames) {
1256 sb.append(acceessibleLabel + ", ");
1257 }
1258 String message = sb.substring(0, sb.length() - 2);
1259 MessageBay.displayMessage(message);
1260 }
1261 } else {
1262 if (accessibleLabelsNames == null) {
1263 MessageBay.displayMessage("Unable to enter surrogate mode as there are no encryption labels on the current Frame");
1264 } else {
1265 MessageBay.displayMessage("No longer in surrogate mode.");
1266 }
1267 }
1268
1269 getCurrentFrame().parse();
1270 getCurrentFrame().refreshSize();
1271 updateTitle();
1272 StandardGestureActions.refreshHighlights();
1273 StandardGestureActions.updateCursor();
1274 requestRefresh(false);
1275 }
1276
1277 public static void ResetSurrogateMode() {
1278 // Turn off x-ray mode if it is on
1279 if (_audienceMode) {
1280 ToggleAudienceMode();
1281 }
1282
1283 Label.resetSurrogateMode();
1284
1285 getCurrentFrame().parse();
1286 getCurrentFrame().refreshSize();
1287 updateTitle();
1288 StandardGestureActions.refreshHighlights();
1289 StandardGestureActions.updateCursor();
1290 requestRefresh(false);
1291 }
1292
1293 /** Whether audience mode is currently on. */
1294 public static boolean isAudienceMode()
1295 {
1296 return _audienceMode;
1297 }
1298
1299 /** Whether mail mode is currently on. */
1300 public static boolean isMailMode() {
1301 return _mailMode;
1302 }
1303
1304 /** Whether x-ray mode is currently on. */
1305 public static boolean isXRayMode()
1306 {
1307 return _xrayMode;
1308 }
1309
1310 public static Dimension getSizeEnforceMinimumXXXX() { // **** DB
1311 Dimension actual_dim = getFramePaintAreaSize();
1312
1313 int enforced_width = Math.max(actual_dim.width, MINIMUM_FRAME_WIDTH);
1314 int enforced_height = Math.max(actual_dim.height,MINIMUM_FRAME_HEIGHT);
1315
1316 Dimension enforced_dim = new Dimension(enforced_width,enforced_height);
1317
1318 return enforced_dim;
1319 }
1320
1321 /** Tells the display controller to get the current window size. */
1322 public static void refreshWindowSize()
1323 {
1324 _windowSize = EcosystemManager.getGraphicsManager().getWindowSize();
1325 _refreshBuffer = Image.createImage(_windowSize, true);
1326 if (_windowSize.getWidth() > MINIMUM_FRAME_WIDTH) {
1327 DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH = true;
1328 }
1329 refreshPaintAreas();
1330 }
1331
1332 /** Recalculates the paint areas for the frames and message bay. */
1333 public static void refreshPaintAreas()
1334 {
1335 // Calculate the width of each frame in twin-frames mode
1336 int availableWindowWidth = _windowSize.width - (int) SEPARATOR_STROKE.thickness;
1337 int leftFrameWidth = (int) (availableWindowWidth * _twinFramesLeftWidthProportion);
1338 int rightFrameWidth = availableWindowWidth - leftFrameWidth;
1339
1340 // The height of each frame is the window height, minus the message bay if visible
1341 int frameHeight = _windowSize.height;
1342 if (!isAudienceMode()) {
1343 frameHeight -= _messageBayHeight + (int) SEPARATOR_STROKE.thickness;
1344 }
1345
1346 int rightFrameX = _windowSize.width - rightFrameWidth;
1347 int messageBayY = _windowSize.height - _messageBayHeight;
1348
1349 _leftFramePaintArea = new AxisAlignedBoxBounds(0, 0, leftFrameWidth, frameHeight);
1350 _rightFramePaintArea = new AxisAlignedBoxBounds(rightFrameX, 0, rightFrameWidth, frameHeight);
1351 _framePaintArea = new AxisAlignedBoxBounds(0, 0, _windowSize.width, frameHeight);
1352 _messageBayPaintArea = new AxisAlignedBoxBounds(0, messageBayY, _windowSize.width, _messageBayHeight);
1353 }
1354
1355 public static AxisAlignedBoxBounds getLeftFramePaintArea()
1356 {
1357 return _leftFramePaintArea;
1358 }
1359
1360 public static AxisAlignedBoxBounds getRightFramePaintArea()
1361 {
1362 return _rightFramePaintArea;
1363 }
1364
1365 public static AxisAlignedBoxBounds getFramePaintArea()
1366 {
1367 return _framePaintArea;
1368 }
1369
1370 public static int getFramePaintAreaWidth()
1371 {
1372 if (!DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH) {
1373 return MINIMUM_FRAME_WIDTH;
1374 }
1375
1376 return _framePaintArea.getWidth();
1377 }
1378
1379 public static int getFramePaintAreaHeight()
1380 {
1381 if (!DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH) {
1382 return MINIMUM_FRAME_HEIGHT;
1383 }
1384
1385 return _framePaintArea.getHeight();
1386 }
1387
1388 public static Dimension getFramePaintAreaSize()
1389 {
1390 if (!DISPLAYED_ABOVE_MINIMUM_FRAME_WIDTH) {
1391 Dimension min_dim = new Dimension(MINIMUM_FRAME_WIDTH,MINIMUM_FRAME_HEIGHT);
1392 return min_dim;
1393 }
1394
1395 return _framePaintArea.getSize();
1396 }
1397
1398 public static AxisAlignedBoxBounds getMessageBayPaintArea()
1399 {
1400 return _messageBayPaintArea;
1401 }
1402
1403 /**
1404 * Checks that the item is visible (on current frame && overlays) - if
1405 * visible then damaged area will be re-rendered on the next refresh.
1406 *
1407 * @param damagedItem
1408 * @param toRepaint
1409 */
1410 public static void invalidateItem(Item damagedItem, Bounds toRepaint)
1411 {
1412 if (toRepaint == null) {
1413 return;
1414 }
1415
1416 // Only add area to repaint if item is visible...
1417 if (ItemUtils.isVisible(damagedItem)) {
1418 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint));
1419 } else if (!_mailMode && MessageBay.isMessageItem(damagedItem)) {
1420 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint).translate(0, getMessageBayPaintArea().getMinY()));
1421 } else if (_mailMode && MailBay.isPreviewMailItem(damagedItem)) {
1422 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint).translate(0, getMessageBayPaintArea().getMinY()));
1423 }
1424 }
1425
1426 /**
1427 * The given area will be re-rendered in the next refresh. This is the
1428 * quicker version and is more useful for re-rendering animated areas.
1429 */
1430 public static void invalidateArea(AxisAlignedBoxBounds toRepaint)
1431 {
1432 _damagedAreas.addArea(toRepaint);
1433 }
1434
1435 public static void clearInvalidAreas()
1436 {
1437 _damagedAreas.clear();
1438 }
1439
1440 public static void refreshBayArea() {
1441 // Always get the clip as it clears at the same time
1442 Clip clip = _damagedAreas.getClip();
1443 GraphicsManager g = EcosystemManager.getGraphicsManager();
1444 pushBayAreaUpdate(true, clip, g);
1445 }
1446
1447 /**
1448 * Redraws the entire window. Shouldn't be called directly, instead use
1449 * requestRefresh(boolean) to ensure no infinite refresh call loops are created.
1450 */
1451 private static boolean refresh(boolean useInvalidation)
1452 {
1453 Frame currentFrame = getCurrentFrame();
1454 if(currentFrame == null) {
1455 return false;
1456 }
1457
1458 // Always get the clip as it clears at the same time
1459 Clip clip = _damagedAreas.getClip();
1460
1461 // No damaged areas
1462 if (useInvalidation && clip.isFullyClipped()) {
1463 return true;
1464 }
1465
1466 GraphicsManager g = EcosystemManager.getGraphicsManager();
1467 g.pushDrawingSurface(_refreshBuffer);
1468
1469 if (isTwinFramesOn()) {
1470 Clip leftClip = null;
1471 Clip rightClip = null;
1472
1473 if (useInvalidation) {
1474 leftClip = clip.clone().intersectWith(getLeftFramePaintArea());
1475 rightClip = clip.clone().intersectWith(getRightFramePaintArea());
1476 if (!rightClip.isFullyClipped()) {
1477 rightClip = new Clip(rightClip.getBounds().translate(-getRightFramePaintArea().getMinX(), 0));
1478 }
1479 }
1480
1481 Image left = null;
1482 if (!useInvalidation || !leftClip.isFullyClipped()) {
1483 left = FrameGraphics.getFrameImage(getFrameOnSide(LEFT), leftClip, getLeftFramePaintArea().getSize());
1484 }
1485
1486 Image right = null;
1487 if (!useInvalidation || !rightClip.isFullyClipped()) {
1488 right = FrameGraphics.getFrameImage(getFrameOnSide(RIGHT), rightClip, getRightFramePaintArea().getSize());
1489 }
1490
1491 paintTwinFrames(left, right);
1492 } else {
1493 Clip frameClip = null;
1494
1495 if (useInvalidation) {
1496 frameClip = clip.clone().intersectWith(getFramePaintArea());
1497 }
1498
1499 Image image = null;
1500 if (currentFrame != null && (!useInvalidation || !frameClip.isFullyClipped())) {
1501 image = FrameGraphics.getFrameImage(currentFrame, frameClip, getFramePaintAreaSize());
1502 }
1503
1504 paintSingleFrame(image);
1505 }
1506
1507 pushBayAreaUpdate(useInvalidation, clip, g);
1508
1509 // Draw any separator lines
1510 g.drawLine(getMessageBaySeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1511 g.drawLine(getTwinFramesSeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1512
1513 // Paint any popups
1514 if (PopupManager.getInstance() != null) {
1515 PopupManager.getInstance().paint();
1516 }
1517
1518 g.popDrawingSurface();
1519 g.drawImage(_refreshBuffer, Point.ORIGIN);
1520 return true;
1521 }
1522
1523 private static void pushBayAreaUpdate(boolean useInvalidation, Clip clip, GraphicsManager g) {
1524 if (!isAudienceMode()) {
1525 if (!isMailMode()) {
1526 // Paint message bay
1527 paintMessageBay(useInvalidation, clip, g);
1528 } else {
1529 // Paint mail bay
1530 paintMailBay(useInvalidation, clip, g);
1531 }
1532 }
1533 }
1534
1535 private static void paintMessageBay(boolean useInvalidation, Clip clip, GraphicsManager g) {
1536 Clip messageBayClip = null;
1537
1538 AxisAlignedBoxBounds messageBayPaintArea = getMessageBayPaintArea();
1539 if (useInvalidation) {
1540 messageBayClip = clip.clone().intersectWith(messageBayPaintArea);
1541 if (!messageBayClip.isFullyClipped()) {
1542 messageBayClip = new Clip(messageBayClip.getBounds().translate(0, -messageBayPaintArea.getMinY()));
1543 }
1544 }
1545
1546 Image image = null;
1547 if (!useInvalidation || !messageBayClip.isFullyClipped()) {
1548 image = MessageBay.getImage(messageBayClip, messageBayPaintArea.getSize());
1549 }
1550
1551 if (image != null) {
1552 g.drawImage(image, messageBayPaintArea.getTopLeft(), messageBayPaintArea.getSize());
1553 }
1554 }
1555
1556 private static void paintMailBay(boolean useInvalidation, Clip clip, GraphicsManager g) {
1557 Clip mailBayClip = null;
1558
1559 AxisAlignedBoxBounds mailBayPaintArea = getMessageBayPaintArea();
1560 if (useInvalidation) {
1561 mailBayClip = clip.clone().intersectWith(mailBayPaintArea);
1562 if (!mailBayClip.isFullyClipped()) {
1563 mailBayClip = new Clip(mailBayClip.getBounds().translate(0, -mailBayPaintArea.getMinY()));
1564 }
1565 }
1566
1567 Image image = null;
1568 if (!useInvalidation || !mailBayClip.isFullyClipped()) {
1569 image = MailBay.getImage(mailBayClip, mailBayPaintArea.getSize());
1570 }
1571
1572 if (image != null) {
1573 g.drawImage(image, mailBayPaintArea.getTopLeft(), mailBayPaintArea.getSize());
1574 }
1575 }
1576
1577 /** Draws the two frame images to the display and puts the separator line between them. */
1578 private static void paintTwinFrames(Image leftFrameImage, Image rightFrameImage)
1579 {
1580 if (leftFrameImage == null && rightFrameImage == null) {
1581 return;
1582 }
1583
1584 InOutReference<FrameTransition> leftTransition = new InOutReference<FrameTransition>(getTransition(LEFT));
1585 InOutReference<FrameTransition> rightTransition = new InOutReference<FrameTransition>(getTransition(RIGHT));
1586
1587 paintFrameIntoArea(leftFrameImage, getLeftFramePaintArea(), leftTransition);
1588 paintFrameIntoArea(rightFrameImage, getRightFramePaintArea(), rightTransition);
1589
1590 setTransition(LEFT, leftTransition.get());
1591 setTransition(RIGHT, rightTransition.get());
1592 }
1593
1594 /** Draws the single frame image to the screen. */
1595 private static void paintSingleFrame(Image frameImage)
1596 {
1597 if (frameImage == null) {
1598 return;
1599 }
1600
1601 InOutReference<FrameTransition> transition = new InOutReference<FrameTransition>(getTransition(getCurrentSide()));
1602
1603 paintFrameIntoArea(frameImage, getFramePaintArea(), transition);
1604
1605 setTransition(getCurrentSide(), transition.get());
1606 }
1607
1608 /** Draws the given frame image into the given area, using a transition if one is provided. */
1609 private static void paintFrameIntoArea(Image frameImage, AxisAlignedBoxBounds area, InOutReference<FrameTransition> transition)
1610 {
1611 if (frameImage == null || area == null) {
1612 return;
1613 }
1614
1615 GraphicsManager g = EcosystemManager.getGraphicsManager();
1616 EnforcedClipKey key = g.pushClip(new Clip(area));
1617
1618 FrameTransition t = null;
1619 if (transition != null) {
1620 t = transition.get();
1621 }
1622
1623 // Attempt to draw the transition if there is one
1624 if (t != null) {
1625 if (!t.drawTransition(frameImage, area)) {
1626 // If drawing failed, throw the transition away
1627 t = null;
1628 } else {
1629 // Schedule the next frame to be drawn
1630 invalidateArea(area);
1631 requestRefresh(true);
1632 }
1633 }
1634
1635 // If t == null at this stage, no transition has been drawn, so just draw the image
1636 if (t == null) {
1637 g.drawImage(frameImage, area.getTopLeft(), area.getSize());
1638 }
1639
1640 // Discard the transition if drawing failed or it is finished
1641 if (t == null || t.isCompleted()) {
1642 transition.set(null);
1643 }
1644
1645 g.popClip(key);
1646 }
1647
1648 /** Runnable which ensures overlapping refresh requests are joined. */
1649 private static RenderRequestMarsheller _requestMarsheller = new RenderRequestMarsheller();
1650
1651 /**
1652 * If wanting to refresh from another thread - other than the main thread
1653 * that handles the expeditee datamodel (modifying / accessing / rendering).
1654 * Use this method for thread safety.
1655 */
1656 public static synchronized void requestRefresh(boolean useInvalidation) {
1657 requestRefresh(useInvalidation, null);
1658 }
1659
1660 public static synchronized void requestRefresh(final boolean useInvalidation, final BooleanSupplier callback) {
1661 try {
1662 _requestMarsheller.enqueue(useInvalidation, callback);
1663 } catch (Throwable e) {
1664 e.printStackTrace();
1665 }
1666 }
1667
1668 /**
1669 * Used for marshelling render requests from foreign threads to the event
1670 * dispatcher thread... (AWT)
1671 *
1672 * @author Brook Novak
1673 */
1674 private static class RenderRequestMarsheller implements Runnable {
1675
1676 private boolean _useInvalidation = true;
1677 private boolean _enqueued = false;
1678 private Boolean _requeueWithInvalidation = null;
1679 private Object _lock = new Object();
1680 private List<BooleanSupplier> callbacks = new LinkedList<BooleanSupplier>();
1681
1682 /** Enqueues a redraw on the GIO thread, or remembers to do so once the current redraw finishes. */
1683 public void enqueue(final boolean useInvalidation, final BooleanSupplier callback)
1684 {
1685 synchronized(callbacks) {
1686 if(callback != null) {
1687 callbacks.add(callback);
1688 }
1689 }
1690 synchronized (_lock) {
1691 if (!_enqueued) {
1692 _enqueued = true;
1693 _useInvalidation = useInvalidation;
1694 EcosystemManager.getMiscManager().runOnGIOThread(this);
1695 } else if (_requeueWithInvalidation == null || _requeueWithInvalidation == true) {
1696 _requeueWithInvalidation = useInvalidation;
1697 }
1698 }
1699 }
1700
1701 @Override
1702 public void run()
1703 {
1704 try {
1705 if(refresh(_useInvalidation)) {
1706 synchronized (callbacks) {
1707 callbacks.forEach(cb -> cb.getAsBoolean());
1708 callbacks.clear();
1709 }
1710 }
1711 } catch (Throwable e) {
1712 e.printStackTrace();
1713 }
1714
1715 // Do another redraw if we received a new request while doing this one.
1716 synchronized (_lock) {
1717 if (_requeueWithInvalidation != null) {
1718 _useInvalidation = _requeueWithInvalidation.booleanValue();
1719 _requeueWithInvalidation = null;
1720 EcosystemManager.getMiscManager().runOnGIOThread(this);
1721 } else {
1722 _enqueued = false;
1723 }
1724 }
1725 }
1726 }
1727}
Note: See TracBrowser for help on using the repository browser.