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

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

Renamed Frame.getItems() to Frame.getSortedItems() to better represent its functionality.

-> org.apollo.ApolloGestureActions
-> org.apollo.ApolloSystem
-> org.expeditee.actions.Actions
-> org.expeditee.actions.Debug
-> org.expeditee.actions.ExploratorySearchActions
-> org.expeditee.actions.JfxBrowserActions
-> org.expeditee.actions.Misc
-> org.expeditee.actions.Navigation
-> org.expeditee.actions.ScriptBase
-> org.expeditee.actions.Simple
-> org.expeditee.agents.ComputeTree
-> org.expeditee.agents.CopyTree
-> org.expeditee.agents.DisplayComet
-> org.expeditee.agents.DisplayTree
-> org.expeditee.agents.DisplayTreeLeaves
-> org.expeditee.agents.GraphFramesetLinks
-> org.expeditee.agents.TreeProcessor
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.DisplayController
-> org.expeditee.gui.FrameCreator
-> org.expeditee.gui.FrameIO
-> org.expeditee.io.DefaultTreeWriter
-> org.expeditee.io.JavaWriter
-> org.expeditee.io.PDF2Writer
-> org.expeditee.io.TXTWriter
-> org.expeditee.io.WebParser
-> org.expeditee.io.flowlayout.XGroupItem
-> org.expeditee.items.Dot
-> org.expeditee.items.Item
-> org.expeditee.items.ItemUtils
-> org.expeditee.network.FrameShare
-> org.expeditee.stats.TreeStats


Created ItemsList class to wrap ArrayList<Item>. Frames now use this new class to store its body list (used for display) as well as its primaryBody and surrogateBody.

-> org.expeditee.agents.Format
-> org.expeditee.agents.HFormat
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.Frame
-> org.expeditee.gui.FrameUtils


Refactorted Frame.setResort(bool) to Frame.invalidateSorted() to better function how it is intended to with a more accurate name.

-> org.expeditee.agents.Sort


When writing out .exp files and getting attributes to respond to LEFT + RIGHT click, boolean items are by default true. This has always been the case. An ammendment to this is that defaults can now be established.
Also added 'EnterClick' functionality. If cursored over a item with this property and you press enter, it acts as if you have clicked on it instead.

-> org.expeditee.assets.resources-public.framesets.authentication.1.exp to 6.exp
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gio.input.KBMInputEvent
-> org.expeditee.gio.javafx.JavaFXConversions
-> org.expeditee.gio.swing.SwingConversions
-> org.expeditee.gui.AttributeUtils
-> org.expeditee.io.Conversion
-> org.expeditee.io.DefaultFrameWriter
-> org.expeditee.items.Item


Fixed a bug caused by calling Math.abs on Integer.MIN_VALUE returning unexpected result. Due to zero being a thing, you cannot represent Math.abs(Integer.MIN_VALUE) in a Integer object. The solution is to use Integer.MIN_VALUE + 1 instead of Integer.MIN_VALUE.

-> org.expeditee.core.bounds.CombinationBounds
-> org.expeditee.io.flowlayout.DimensionExtent


Recoded the contains function in EllipticalBounds so that intersection tests containing circles work correctly.

-> org.expeditee.core.bounds.EllipticalBounds


Added toString() to PolygonBounds to allow for useful printing during debugging.

-> org.expeditee.core.bounds.PolygonBounds

Implemented Surrogate Mode!

-> org.expeditee.encryption.io.EncryptedExpReader
-> org.expeditee.encryption.io.EncryptedExpWriter
-> org.expeditee.encryption.items.surrogates.EncryptionDetail
-> org.expeditee.encryption.items.surrogates.Label
-> org.expeditee.gui.FrameUtils
-> org.expeditee.gui.ItemsList
-> org.expeditee.items.Item
-> org.expeditee.items.Text


???? Use Integer.MAX_VALUE cast to a float instead of Float.MAX_VALUE. This fixed some bug which I cannot remember.

-> org.expeditee.gio.TextLayoutManager
-> org.expeditee.gio.swing.SwingTextLayoutManager


Improved solution for dealing with the F10 key taking focus away from Expeditee due to it being a assessibility key.

-> org.expeditee.gio.swing.SwingInputManager


Renamed variable visibleItems in FrameGraphics.paintFrame to itemsToPaintCanditates to better represent functional intent.

-> org.expeditee.gui.FrameGraphics


Improved checking for if personal resources exist before recreating them

-> org.expeditee.gui.FrameIO


Repeated messages to message bay now have a visual feedback instead of just a beep. This visual feedback is in the form of a count of the amount of times it has repeated.

-> org.expeditee.gui.MessageBay


Updated comment on the Vector class to explain what vectors are.

-> org.expeditee.gui.Vector


Added constants to represent all of the property keys in DefaultFrameReader and DefaultFrameWriter.

-> org.expeditee.io.DefaultFrameReader
-> org.expeditee.io.DefaultFrameWriter


Updated the KeyList setting to be more heirarcial with how users will store their Secrets.

-> org.expeditee.settings.identity.secrets.KeyList

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