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

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

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

A less heavy handed solution to previously fixed exceptions on startup.

File size: 42.8 KB
Line 
1/**
2 * DisplayIO.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.gui;
20
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
24import java.util.HashSet;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Stack;
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
679 // If the mouse is over the right side of the window and there's a valid frame,
680 // we are on the right side.
681 if(isTwinFramesOn()) {
682 int mouseX = EcosystemManager.getInputManager().getCursorPosition().x;
683 if(mouseX >= getTwinFramesSeparatorX() && sideHasFrame(RIGHT)) {
684 return RIGHT;
685 }
686 }
687
688 // If there's only a right frame, that's the current side
689 if (!sideHasFrame(LEFT) && sideHasFrame(RIGHT)) return RIGHT;
690
691 // In any other case the left side is the current side
692 return LEFT;
693 }
694
695 /** Gets the opposite side to the current side of the twin-frames. */
696 private static TwinFramesSide getOppositeSide()
697 {
698 if (getCurrentSide() == LEFT) return RIGHT;
699
700 return LEFT;
701 }
702
703 /** Returns the side that the given frame is on, or null if it's not on either side. */
704 public static TwinFramesSide getSideFrameIsOn(Frame frame)
705 {
706 // Loop through both sides
707 for (TwinFramesSide side : TwinFramesSide.values()) {
708 if (getFrameOnSide(side) == frame) return side;
709 }
710
711 return null;
712 }
713
714 /** Gets the frame that is on the given side of the display. */
715 public static Frame getFrameOnSide(TwinFramesSide side)
716 {
717 if (side == null) return null;
718
719 return _currentFrames[side.ordinal()];
720 }
721
722 /** Returns the frame on the current side of the display. */
723 public static Frame getCurrentFrame()
724 {
725 return getFrameOnSide(getCurrentSide());
726 }
727
728 /** Returns the frame on the opposite side of the display. */
729 public static Frame getOppositeFrame()
730 {
731 return getFrameOnSide(getOppositeSide());
732 }
733
734 /** Gets the two frames being displayed. */
735 public static Frame[] getFrames()
736 {
737 return _currentFrames;
738 }
739
740 /** Gets the x-coordinate of the division between the left and right sides. */
741 public static int getTwinFramesSeparatorX()
742 {
743 return getLeftFramePaintArea().getWidth() + (int) (SEPARATOR_STROKE.thickness / 2);
744 }
745
746 /** Gets the line that separates the two sides of the twin-frames display. */
747 public static Line getTwinFramesSeparatorLine()
748 {
749 if (!isTwinFramesOn()) return null;
750
751 int x = getTwinFramesSeparatorX();
752 int bottom = getFramePaintArea().getMaxY();
753
754 return new Line(x, 0, x, bottom);
755
756 }
757
758 /** Gets the line that separates the message bay from the frame area. */
759 public static Line getMessageBaySeparatorLine()
760 {
761 // No message bay in audience mode
762 if (isAudienceMode()) return null;
763
764 int separatorThickness = (int) SEPARATOR_STROKE.thickness;
765 int y = getFramePaintArea().getMaxY() + separatorThickness / 2 + separatorThickness % 2;
766 int right = getMessageBayPaintArea().getMaxX();
767
768 return new Line(0, y, right, y);
769 }
770
771 /**
772 * Returns the current mouse X coordinate. This coordinate is relative to
773 * the left edge of the frame the mouse is in. It takes into account the
774 * user being in twin frames mode.
775 *
776 * @return The X coordinate of the mouse.
777 */
778 public static float getFloatMouseX()
779 {
780 return (float) getMouseX();
781 }
782
783
784 /**
785 * Returns the current mouse X coordinate. This coordinate is relative to
786 * the left edge of the frame the mouse is in. It takes into account the
787 * user being in twin frames mode.
788 *
789 * @return The X coordinate of the mouse.
790 */
791 public static int getMouseX()
792 {
793 return getMousePosition().x;
794 }
795
796 /**
797 * Returns the current mouse position. This coordinate is relative to
798 * the upper-left edge of the frame the mouse is in. It takes into account the
799 * user being in twin frames mode.
800 *
801 * @return The position of the mouse.
802 */
803 public static Point getMousePosition()
804 {
805 Point mousePos = EcosystemManager.getInputManager().getCursorPosition();
806
807 if (isTwinFramesOn() && getRightFramePaintArea().contains(mousePos)) {
808 mousePos.x -= getRightFramePaintArea().getMinX();
809 }
810
811 return mousePos;
812 }
813
814 /**
815 * Returns the current mouse Y coordinate. This coordinate is relative to
816 * the top edge of the frame the mouse is in.
817 *
818 * @return The Y coordinate of the mouse.
819 */
820 public static float getFloatMouseY()
821 {
822 return (float) getMouseY();
823 }
824
825 /**
826 * Returns the current mouse Y coordinate. This coordinate is relative to
827 * the top edge of the frame the mouse is in.
828 *
829 * @return The Y coordinate of the mouse.
830 */
831 public static int getMouseY()
832 {
833 return getMousePosition().y;
834 }
835
836 /**
837 * TODO: Comment. cts16
838 * @return
839 */
840 public static boolean Back()
841 {
842 TwinFramesSide side = getCurrentSide();
843 Stack<String> visitedFrames = getVisitedFrames(side);
844
845 // there must be a frame to go back to
846 if (visitedFrames.size() < 1) {
847 MessageBay.displayMessageOnce("You are already on the home frame");
848 return false;
849 }
850
851 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
852 MessageBay.displayMessage("Error navigating back");
853 return false;
854 }
855
856 String oldFrame = getCurrentFrame().getName().toLowerCase();
857
858 // do not get a cached version (in case it is in the other window)
859 if (isTwinFramesOn()) FrameIO.SuspendCache();
860
861 Frame frame = FrameIO.LoadFrame(removeFromBack());
862 // If the top frame on the backup stack is the current frame go back
863 // again... or if it has been deleted
864 // Recursively backup the stack
865 if (frame == null || frame.equals(getCurrentFrame())) {
866 Back();
867 return false;
868 }
869
870 if (isTwinFramesOn()) {
871 FrameIO.ResumeCache();
872 }
873 getBackedUpFrames(side).push(oldFrame);
874 FrameUtils.DisplayFrame(frame, false, true);
875 StandardGestureActions.setHighlightHold(true);
876
877 for (Item i : frame.getItems()) {
878 if (i.getLink() != null && i.getAbsoluteLink().toLowerCase().equals(oldFrame)) {
879 if (i.getHighlightMode() != Item.HighlightMode.Normal) {
880 i.setHighlightModeAndColour(Item.HighlightMode.Normal,
881 BACK_HIGHLIGHT_COLOR);
882 }
883 // check if its an @f item and if so update the buffer
884 if (i instanceof Picture) {
885 Picture p = (Picture) i;
886 p.refresh();
887 }
888 }
889 }
890 requestRefresh(true);
891 return true;
892 }
893
894 /**
895 * TODO: Comment. cts16
896 * @return
897 */
898 public static boolean Forward()
899 {
900 TwinFramesSide side = getCurrentSide();
901
902 // there must be a frame to go back to
903 if (getBackedUpFrames(side).size() == 0) {
904 return false;
905 }
906
907 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
908 MessageBay.displayMessage("Error navigating forward");
909 return false;
910 }
911
912 String oldFrame = getCurrentFrame().getName().toLowerCase();
913
914 // do not get a cached version (in case it is in the other window)
915 if (isTwinFramesOn())
916 FrameIO.SuspendCache();
917 Frame frame = FrameIO.LoadFrame(getBackedUpFrames(side).pop());
918 // If the top frame on the backup stack is the current frame go back
919 // again... or if it has been deleted
920 // Recursively backup the stack
921 if (frame == null || frame.equals(getCurrentFrame())) {
922 Forward();
923 return false;
924 }
925
926 if (isTwinFramesOn()) {
927 FrameIO.ResumeCache();
928 }
929 getVisitedFrames(side).push(oldFrame);
930 FrameUtils.DisplayFrame(frame, false, true);
931 requestRefresh(true);
932 return true;
933 }
934
935 /** Toggles the display of frames between TwinFrames mode and Single frame mode. */
936 public static void toggleTwinFrames()
937 {
938 // determine which side is the active side
939 TwinFramesSide opposite = getOppositeSide();
940 _twinFramesMode = !_twinFramesMode;
941
942 // if TwinFrames is being turned on
943 if (_twinFramesMode) {
944 // if this is the first time TwinFrames has been toggled on,
945 // load the user's first frame
946 if (getVisitedFrames(opposite).size() == 0) {
947 FrameIO.SuspendCache();
948 setCurrentFrame(FrameIO.LoadFrame(UserSettings.HomeFrame.get()), true);
949 FrameIO.ResumeCache();
950 } else {
951 // otherwise, restore the frame from the side's back-stack
952 setCurrentFrame(FrameIO.LoadFrame(getVisitedFrames(opposite).pop()), true);
953 }
954
955 // else, TwinFrames is being turned off
956 } else {
957 // add the frame to the back-stack
958 Frame hiding = getOppositeFrame();
959 FrameUtils.LeavingFrame(hiding);
960 getVisitedFrames(opposite).add(hiding.getName());
961 setFrameOnSide(null, opposite);
962 getCurrentFrame().refreshSize();
963 }
964
965 // Update the sizes of the displayed frames
966 if (getCurrentFrame() != null) getCurrentFrame().refreshSize();
967 if (getOppositeFrame() != null) getOppositeFrame().refreshSize();
968
969 requestRefresh(false);
970 }
971
972 /** Whether the display is currently in twin-frames mode. */
973 public static boolean isTwinFramesOn()
974 {
975 return _twinFramesMode;
976 }
977
978 /** TODO: Comment. cts16 */
979 public static void Reload(TwinFramesSide side)
980 {
981 if (side == null) return;
982
983 FrameIO.SuspendCache();
984 Frame frame = FrameIO.LoadFrame(getFrameOnSide(side).getName());
985 setFrameOnSide(frame, side);
986 FrameIO.ResumeCache();
987 }
988
989 /**
990 * Moves the cursor the end of this item.
991 *
992 * @param i
993 */
994 public static void MoveCursorToEndOfItem(Item i)
995 {
996 setTextCursor((Text) i, Text.END, true, false, false, false);
997 }
998
999 public static void clearBackedUpFrames()
1000 {
1001 getBackedUpFrames(getCurrentSide()).clear();
1002 }
1003
1004 /**
1005 * @param secondsDelay
1006 * @param s
1007 * @throws InterruptedException
1008 */
1009 public static void typeStringDirect(double secondsDelay, String s) throws InterruptedException
1010 {
1011 for (int i = 0; i < s.length(); i++) {
1012 StandardGestureActions.processChar(s.charAt(i), false);
1013 Thread.sleep((int) (secondsDelay * 1000));
1014 }
1015 }
1016
1017 public static List<String> getUnmodifiableVisitedList()
1018 {
1019 return Collections.unmodifiableList(getVisitedFrames(getCurrentSide()));
1020 }
1021
1022 /** If both sides have the same frame (returns false if both sides have no frame). */
1023 public static boolean bothSidesHaveSameFrame()
1024 {
1025 return (getCurrentFrame() != null && getCurrentFrame() == getOppositeFrame());
1026 }
1027
1028 /** Sets the given side to the given frame. */
1029 private static void setFrameOnSide(Frame frame, TwinFramesSide side)
1030 {
1031 if (side == null) return;
1032
1033 _currentFrames[side.ordinal()] = frame;
1034 }
1035
1036 /** Gets the stack of visited frames for the given side. */
1037 private static Stack<String> getVisitedFrames(TwinFramesSide side)
1038 {
1039 if (side == null) return null;
1040
1041 return _visitedFrames[side.ordinal()];
1042 }
1043
1044 /** Gets the stack of backed-up frames for the given side. */
1045 private static Stack<String> getBackedUpFrames(TwinFramesSide side)
1046 {
1047 if (side == null) return null;
1048
1049 return _backedUpFrames[side.ordinal()];
1050 }
1051
1052 /**
1053 * Rotates through normal -> audience -> audience + fullscreen modes and back again.
1054 */
1055 public static void rotateAudienceModes()
1056 {
1057 Frame current = DisplayController.getCurrentFrame();
1058 GraphicsManager g = EcosystemManager.getGraphicsManager();
1059 if (_audienceMode && g.isFullscreen()) {
1060 ToggleAudienceMode();
1061 g.exitFullscreen();
1062 } else if (_audienceMode) {
1063 if (g.canGoFullscreen()) {
1064 g.goFullscreen();
1065 } else {
1066 ToggleAudienceMode();
1067 }
1068 } else {
1069 ToggleAudienceMode();
1070 ItemUtils.UpdateConnectedToAnnotations(current.getItems());
1071 for (Overlay o : current.getOverlays()) {
1072 ItemUtils.UpdateConnectedToAnnotations(o.Frame.getItems());
1073 }
1074 for (Vector v : current.getVectorsDeep()) {
1075 ItemUtils.UpdateConnectedToAnnotations(v.Frame.getItems());
1076 }
1077 }
1078 }
1079
1080 /**
1081 * If Audience Mode is on this method will toggle it to be off, or
1082 * vice-versa. This results in the Frame being re-parsed and repainted.
1083 */
1084 public static void ToggleAudienceMode()
1085 {
1086 Frame current = DisplayController.getCurrentFrame();
1087
1088 // Turn off x-ray mode if it's on
1089 if (_xrayMode) ToggleXRayMode();
1090
1091 // Toggle audience mode
1092 _audienceMode = !_audienceMode;
1093
1094 refreshPaintAreas();
1095 FrameUtils.Parse(current);
1096 updateTitle();
1097 requestRefresh(false);
1098 }
1099
1100 /**
1101 * If X-Ray Mode is on this method will toggle it to be off, or vice-versa.
1102 * This results in the Frame being re-parsed and repainted.
1103 */
1104 public static void ToggleXRayMode()
1105 {
1106 // Turn off x-ray mode if it is on
1107 if (_audienceMode) ToggleAudienceMode();
1108
1109 _xrayMode = !_xrayMode;
1110
1111 getCurrentFrame().parse();
1112 getCurrentFrame().refreshSize();
1113 updateTitle();
1114 StandardGestureActions.refreshHighlights();
1115 StandardGestureActions.updateCursor();
1116 requestRefresh(false);
1117 }
1118
1119 /** Whether audience mode is currently on. */
1120 public static boolean isAudienceMode()
1121 {
1122 return _audienceMode;
1123 }
1124
1125 /** Whether x-ray mode is currently on. */
1126 public static boolean isXRayMode()
1127 {
1128 return _xrayMode;
1129 }
1130
1131 /** Tells the display controller to get the current window size. */
1132 public static void refreshWindowSize()
1133 {
1134 _windowSize = EcosystemManager.getGraphicsManager().getWindowSize();
1135 _refreshBuffer = Image.createImage(_windowSize, true);
1136 refreshPaintAreas();
1137 }
1138
1139 /** Recalculates the paint areas for the frames and message bay. */
1140 public static void refreshPaintAreas()
1141 {
1142 // Calculate the width of each frame in twin-frames mode
1143 int availableWindowWidth = _windowSize.width - (int) SEPARATOR_STROKE.thickness;
1144 int leftFrameWidth = (int) (availableWindowWidth * _twinFramesLeftWidthProportion);
1145 int rightFrameWidth = availableWindowWidth - leftFrameWidth;
1146
1147 // The height of each frame is the window height, minus the message bay if visible
1148 int frameHeight = _windowSize.height;
1149 if (!isAudienceMode()) {
1150 frameHeight -= _messageBayHeight + (int) SEPARATOR_STROKE.thickness;
1151 }
1152
1153 int rightFrameX = _windowSize.width - rightFrameWidth;
1154 int messageBayY = _windowSize.height - _messageBayHeight;
1155
1156 _leftFramePaintArea = new AxisAlignedBoxBounds(0, 0, leftFrameWidth, frameHeight);
1157 _rightFramePaintArea = new AxisAlignedBoxBounds(rightFrameX, 0, rightFrameWidth, frameHeight);
1158 _framePaintArea = new AxisAlignedBoxBounds(0, 0, _windowSize.width, frameHeight);
1159 _messageBayPaintArea = new AxisAlignedBoxBounds(0, messageBayY, _windowSize.width, _messageBayHeight);
1160 }
1161
1162 public static AxisAlignedBoxBounds getLeftFramePaintArea()
1163 {
1164 return _leftFramePaintArea;
1165 }
1166
1167 public static AxisAlignedBoxBounds getRightFramePaintArea()
1168 {
1169 return _rightFramePaintArea;
1170 }
1171
1172 public static AxisAlignedBoxBounds getFramePaintArea()
1173 {
1174 return _framePaintArea;
1175 }
1176
1177 public static AxisAlignedBoxBounds getMessageBayPaintArea()
1178 {
1179 return _messageBayPaintArea;
1180 }
1181
1182 /**
1183 * Checks that the item is visible (on current frame && overlays) - if
1184 * visible then damaged area will be re-rendered on the next refresh.
1185 *
1186 * @param damagedItem
1187 * @param toRepaint
1188 */
1189 public static void invalidateItem(Item damagedItem, Bounds toRepaint)
1190 {
1191 if (toRepaint == null) return;
1192
1193 // Only add area to repaint if item is visible...
1194 if (ItemUtils.isVisible(damagedItem)) {
1195 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint));
1196 } else if (MessageBay.isMessageItem(damagedItem)) {
1197 invalidateArea(AxisAlignedBoxBounds.getEnclosing(toRepaint).translate(0, getMessageBayPaintArea().getMinY()));
1198 }
1199 }
1200
1201 /**
1202 * The given area will be re-rendered in the next refresh. This is the
1203 * quicker version and is more useful for re-rendering animated areas.
1204 */
1205 public static void invalidateArea(AxisAlignedBoxBounds toRepaint)
1206 {
1207 _damagedAreas.addArea(toRepaint);
1208 }
1209
1210 public static void clearInvalidAreas()
1211 {
1212 _damagedAreas.clear();
1213 }
1214
1215 /*
1216 *
1217 * Thread-safe rendering.
1218 *
1219 */
1220
1221 /**
1222 * Redraws the entire window. Shouldn't be called directly, instead use
1223 * requestRefresh(boolean) to ensure no infinite refresh call loops are created.
1224 */
1225 private static void refresh(boolean useInvalidation)
1226 {
1227 Frame currentFrame = getCurrentFrame();
1228 if(currentFrame == null) {
1229 System.err.println("currentFrame == null");
1230 return;
1231 }
1232
1233 // Always get the clip as it clears at the same time
1234 Clip clip = _damagedAreas.getClip();
1235
1236 // No damaged areas
1237 if (useInvalidation && clip.isFullyClipped()) return;
1238
1239 GraphicsManager g = EcosystemManager.getGraphicsManager();
1240 g.pushDrawingSurface(_refreshBuffer);
1241
1242 if (isTwinFramesOn()) {
1243 Clip leftClip = null;
1244 Clip rightClip = null;
1245
1246 if (useInvalidation) {
1247 leftClip = clip.clone().intersectWith(getLeftFramePaintArea());
1248 rightClip = clip.clone().intersectWith(getRightFramePaintArea());
1249 if (!rightClip.isFullyClipped()) rightClip = new Clip(rightClip.getBounds().translate(-getRightFramePaintArea().getMinX(), 0));
1250 }
1251
1252 Image left = null;
1253 if (!useInvalidation || !leftClip.isFullyClipped()) {
1254 left = FrameGraphics.getFrameImage(getFrameOnSide(LEFT), leftClip, getLeftFramePaintArea().getSize());
1255 }
1256
1257 Image right = null;
1258 if (!useInvalidation || !rightClip.isFullyClipped()) {
1259 right = FrameGraphics.getFrameImage(getFrameOnSide(RIGHT), rightClip, getRightFramePaintArea().getSize());
1260 }
1261
1262 paintTwinFrames(left, right);
1263 } else {
1264 Clip frameClip = null;
1265
1266 if (useInvalidation) {
1267 frameClip = clip.clone().intersectWith(getFramePaintArea());
1268 }
1269
1270 Image image = null;
1271 if (currentFrame != null && (!useInvalidation || !frameClip.isFullyClipped())) {
1272 image = FrameGraphics.getFrameImage(currentFrame, frameClip, getFramePaintArea().getSize());
1273 }
1274
1275 paintSingleFrame(image);
1276 }
1277
1278 // Paint message bay
1279 if (!isAudienceMode()) {
1280 Clip messageBayClip = null;
1281
1282 AxisAlignedBoxBounds messageBayPaintArea = getMessageBayPaintArea();
1283 if (useInvalidation) {
1284 messageBayClip = clip.clone().intersectWith(messageBayPaintArea);
1285 if (!messageBayClip.isFullyClipped()) messageBayClip = new Clip(messageBayClip.getBounds().translate(0, -messageBayPaintArea.getMinY()));
1286 }
1287
1288 Image image = null;
1289 if (!useInvalidation || !messageBayClip.isFullyClipped()) {
1290 image = MessageBay.getImage(messageBayClip, messageBayPaintArea.getSize());
1291 }
1292
1293 if (image != null) {
1294 g.drawImage(image, messageBayPaintArea.getTopLeft(), messageBayPaintArea.getSize());
1295 }
1296 }
1297
1298 // Draw any separator lines
1299 g.drawLine(getMessageBaySeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1300 g.drawLine(getTwinFramesSeparatorLine(), SEPARATOR_COLOUR, SEPARATOR_STROKE);
1301
1302 // Paint any popups
1303 if (PopupManager.getInstance() != null) PopupManager.getInstance().paint();
1304
1305 g.popDrawingSurface();
1306 g.drawImage(_refreshBuffer, Point.ORIGIN);
1307 }
1308
1309 /** Draws the two frame images to the display and puts the separator line between them. */
1310 private static void paintTwinFrames(Image leftFrameImage, Image rightFrameImage)
1311 {
1312 if (leftFrameImage == null && rightFrameImage == null) return;
1313
1314 InOutReference<FrameTransition> leftTransition = new InOutReference<FrameTransition>(getTransition(LEFT));
1315 InOutReference<FrameTransition> rightTransition = new InOutReference<FrameTransition>(getTransition(RIGHT));
1316
1317 paintFrameIntoArea(leftFrameImage, getLeftFramePaintArea(), leftTransition);
1318 paintFrameIntoArea(rightFrameImage, getRightFramePaintArea(), rightTransition);
1319
1320 setTransition(LEFT, leftTransition.get());
1321 setTransition(RIGHT, rightTransition.get());
1322 }
1323
1324 /** Draws the single frame image to the screen. */
1325 private static void paintSingleFrame(Image frameImage)
1326 {
1327 if (frameImage == null) return;
1328
1329 InOutReference<FrameTransition> transition = new InOutReference<FrameTransition>(getTransition(getCurrentSide()));
1330
1331 paintFrameIntoArea(frameImage, getFramePaintArea(), transition);
1332
1333 setTransition(getCurrentSide(), transition.get());
1334 }
1335
1336 /** Draws the given frame image into the given area, using a transition if one is provided. */
1337 private static void paintFrameIntoArea(Image frameImage, AxisAlignedBoxBounds area, InOutReference<FrameTransition> transition)
1338 {
1339 if (frameImage == null || area == null) return;
1340
1341 GraphicsManager g = EcosystemManager.getGraphicsManager();
1342 EnforcedClipKey key = g.pushClip(new Clip(area));
1343
1344 FrameTransition t = null;
1345 if (transition != null) t = transition.get();
1346
1347 // Attempt to draw the transition if there is one
1348 if (t != null) {
1349 if (!t.drawTransition(frameImage, area)) {
1350 // If drawing failed, throw the transition away
1351 t = null;
1352 } else {
1353 // Schedule the next frame to be drawn
1354 invalidateArea(area);
1355 requestRefresh(true);
1356 }
1357 }
1358
1359 // If t == null at this stage, no transition has been drawn, so just draw the image
1360 if (t == null) {
1361 g.drawImage(frameImage, area.getTopLeft(), area.getSize());
1362 }
1363
1364 // Discard the transition if drawing failed or it is finished
1365 if (t == null || t.isCompleted()) transition.set(null);
1366
1367 g.popClip(key);
1368 }
1369
1370 /** Runnable which ensures overlapping refresh requests are joined. */
1371 private static RenderRequestMarsheller _requestMarsheller = new RenderRequestMarsheller();
1372
1373 /**
1374 * If wanting to refresh from another thread - other than the main thread
1375 * that handles the expeditee datamodel (modifying / accessing / rendering).
1376 * Use this method for thread safety.
1377 */
1378 public static synchronized void requestRefresh(boolean useInvalidation)
1379 {
1380 try {
1381 _requestMarsheller.enqueue(useInvalidation);
1382 } catch (Throwable e) {
1383 e.printStackTrace();
1384 }
1385 }
1386
1387 /**
1388 * Used for marshelling render requests from foreign threads to the event
1389 * dispatcher thread... (AWT)
1390 *
1391 * @author Brook Novak
1392 */
1393 private static class RenderRequestMarsheller implements Runnable {
1394
1395 private boolean _useInvalidation = true;
1396 private boolean _enqueued = false;
1397 private Boolean _requeueWithInvalidation = null;
1398 private Object _lock = new Object();
1399
1400 /** Enqueues a redraw on the GIO thread, or remembers to do so once the current redraw finishes. */
1401 public void enqueue(boolean useInvalidation)
1402 {
1403 synchronized (_lock) {
1404 if (!_enqueued) {
1405 _enqueued = true;
1406 _useInvalidation = useInvalidation;
1407 EcosystemManager.getMiscManager().runOnGIOThread(this);
1408 } else if (_requeueWithInvalidation == null || _requeueWithInvalidation == true) {
1409 _requeueWithInvalidation = useInvalidation;
1410 }
1411 }
1412 }
1413
1414 public void run()
1415 {
1416 try {
1417 refresh(_useInvalidation);
1418 } catch (Throwable e) {
1419 e.printStackTrace();
1420 }
1421
1422 // Do another redraw if we received a new request while doing this one.
1423 synchronized (_lock) {
1424 if (_requeueWithInvalidation != null) {
1425 _useInvalidation = _requeueWithInvalidation.booleanValue();
1426 _requeueWithInvalidation = null;
1427 EcosystemManager.getMiscManager().runOnGIOThread(this);
1428 } else {
1429 _enqueued = false;
1430 }
1431 }
1432 }
1433 }
1434}
Note: See TracBrowser for help on using the repository browser.