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

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

org.expeditee.gio.EcosystemManager ->
org.expeditee.gui.DisplayController ->

Added some security code to ensure that the Ecosystem had finished initialising before using it.

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