source: trunk/src/org/expeditee/gui/DisplayIO.java@ 67

Last change on this file since 67 was 67, checked in by ra33, 16 years ago

Fixed a bunch of problems with rectangles and resizing the window, as well as adding some more unit tests etc.

File size: 17.7 KB
Line 
1package org.expeditee.gui;
2
3import java.awt.AWTException;
4import java.awt.Color;
5import java.awt.Cursor;
6import java.awt.Image;
7import java.awt.Point;
8import java.awt.Robot;
9import java.awt.Toolkit;
10import java.awt.image.MemoryImageSource;
11import java.util.LinkedList;
12import java.util.List;
13import java.util.Stack;
14
15import javax.swing.JOptionPane;
16
17import org.expeditee.actions.NavigationActions;
18import org.expeditee.items.Item;
19import org.expeditee.items.ItemParentStateChangedEvent;
20import org.expeditee.items.Picture;
21import org.expeditee.stats.SessionStats;
22
23/**
24 * This Interface is used by the Frame to control all display input and output.
25 *
26 * @author jdm18
27 *
28 */
29public class DisplayIO {
30
31 private static final int SMALL_CURSOR_SIZE = 16;
32
33 private static final int MEDIUM_CURSOR_SIZE = 32;
34
35 private static final int LARGE_CURSOR_SIZE = 64;
36
37 public static final Color DEFAULT_BACKGROUND = Color.WHITE;
38
39 /**
40 * The color to be used to highlight the linked parent item, when the user
41 * navigates backwards.
42 */
43 public static final Color BACK_HIGHLIGHT_COLOR = Color.MAGENTA;
44
45 private static Browser _Browser;
46
47 // The current Frame being displayed on the screen.
48 private static Frame _CurrentFrames[] = new Frame[2];
49
50 // Maintains the list of frames visited thus-far for back-tracking
51 @SuppressWarnings("unchecked")
52 private static Stack<String>[] _VisitedFrames = new Stack[2];
53
54 // used to change the mouse cursor position on the screen
55 private static Robot _Robot;
56
57 private static boolean _TwinFrames = false;
58
59 /**
60 * The title to display in the Title bar.
61 */
62 public static final String TITLE = "Exp17May2008A";
63
64 private DisplayIO() {
65 }
66
67 public static void Init(Browser browser) {
68 _Browser = browser;
69 try {
70 _Robot = new Robot();
71 } catch (AWTException e) {
72 e.printStackTrace();
73 }
74
75 Point mouse = _Browser.getMousePosition();
76 if (mouse != null) {
77 FrameMouseActions.MouseX = mouse.x;
78 FrameMouseActions.MouseY = mouse.y;
79 }
80
81 _VisitedFrames[0] = new Stack<String>();
82 _VisitedFrames[1] = new Stack<String>();
83 }
84
85 public static void setTextCursor(int size) {
86 if (cursorType == Item.TEXT_CURSOR)
87 return;
88
89 cursorType = Item.TEXT_CURSOR;
90
91 // Do some stuff to adjust the cursor size based on the font size
92 final int MEDIUM_CURSOR_CUTOFF = 31;
93 final int LARGE_CURSOR_CUTOFF = 62;
94
95 int cursorSize = LARGE_CURSOR_SIZE;
96 int hotspotPos = 0;
97 int start = 0;
98
99 if (size < MEDIUM_CURSOR_CUTOFF) {
100 cursorSize = MEDIUM_CURSOR_SIZE;
101 start = cursorSize - size - 2;
102 hotspotPos = cursorSize - (size + 2) / 4;
103 } else if (size < LARGE_CURSOR_CUTOFF) {
104 hotspotPos = cursorSize - (size - 5) / 4;
105 start = cursorSize - size - 2;
106 } else {
107 int FIXED_CURSOR_MIN = 77;
108 if (size >= FIXED_CURSOR_MIN) {
109 hotspotPos = cursorSize - 2;
110 } else {
111 hotspotPos = size - (FIXED_CURSOR_MIN - cursorSize);
112 }
113 }
114
115 int[] pixels = new int[cursorSize * cursorSize];
116
117 for (int i = start; i < cursorSize; i++)
118 pixels[i * cursorSize] = pixels[(i * cursorSize) + 1] = 0xFF000000;
119
120 Image image = Toolkit.getDefaultToolkit().createImage(
121 new MemoryImageSource(cursorSize, cursorSize, pixels, 0,
122 cursorSize));
123 Cursor textCursor = Toolkit.getDefaultToolkit().createCustomCursor(
124 image, new Point(0, hotspotPos), "textcursor");
125 _Browser.setCursor(textCursor);
126 }
127
128 /**
129 * Sets the type of cursor the display should be using
130 *
131 * @param type
132 * The type of cursor to display, using constants defined in the
133 * Cursor class.
134 */
135 public static void setCursor(int type) {
136 // avoid flicker when not changing
137 if (type == cursorType || type == Item.UNCHANGED_CURSOR)
138 return;
139
140 cursorType = type;
141
142 if (type == Item.HIDDEN_CURSOR) {
143 int[] pixels = new int[SMALL_CURSOR_SIZE * SMALL_CURSOR_SIZE];
144 Image image = Toolkit.getDefaultToolkit().createImage(
145 new MemoryImageSource(SMALL_CURSOR_SIZE, SMALL_CURSOR_SIZE,
146 pixels, 0, SMALL_CURSOR_SIZE));
147 Cursor transparentCursor = Toolkit.getDefaultToolkit()
148 .createCustomCursor(image, new Point(0, 0),
149 "invisiblecursor");
150 _Browser.setCursor(transparentCursor);
151 } else
152 _Browser.setCursor(new Cursor(type));
153 }
154
155 private static int cursorType = Item.DEFAULT_CURSOR;
156
157 public static int getCursor() {
158 return cursorType;
159 }
160
161 /**
162 * Moves the mouse cursor to the given x,y coordinates on the screen
163 *
164 * @param x
165 * The x coordinate
166 * @param y
167 * The y coordinate
168 */
169 public static void setCursorPosition(int x, int y) {
170 setCursorPosition(x, y, true);
171 }
172
173 public static void setCursorPosition(int x, int y, boolean forceArrow) {
174 int deltax = x - FrameMouseActions.MouseX;
175 int deltay = y - FrameMouseActions.MouseY;
176
177 if (_TwinFrames) {
178 if (getCurrentSide() == 1)
179 x += getMiddle();
180 }
181
182 // ensures the cursor doesn't fall behind while waiting for robot
183 FrameMouseActions.MouseX = x;
184 FrameMouseActions.MouseY = y;
185
186 if (Frame.itemAttachedToCursor()) {
187 List<Item> toMove = Frame.FreeItems;
188 for (Item move : toMove) {
189 move.setPosition(move.getX() + deltax, move.getY() + deltay);
190 }
191 }
192
193 // cheat
194 FrameMouseActions.setForceArrow(forceArrow);
195 _Robot.mouseMove((int) _Browser.getContentPane().getLocationOnScreen()
196 .getX()
197 + x, (int) _Browser.getContentPane().getLocationOnScreen()
198 .getY()
199 + y);
200 }
201
202 public static void resetCursorOffset() {
203 FrameMouseActions.resetOffset();
204 }
205
206 public static void setCursorPosition(Point pos) {
207 setCursorPosition(pos.x, pos.y);
208 }
209
210 public static void setCursorPosition(Point pos, boolean forceArrow) {
211 setCursorPosition(pos.x, pos.y, forceArrow);
212 }
213
214 private static boolean _typed = false;
215
216 public static void setKeyTyped(boolean val) {
217 _typed = val;
218 }
219
220 public static boolean getKeyTyped() {
221 return _typed;
222 }
223
224 /**
225 * Returns the top item (last added) of the Back-Stack (which is popped off)
226 *
227 * @return The name of the last Frame added to the back-stack
228 */
229 public static String getLastFrame() {
230 int side = getCurrentSide();
231
232 if (_VisitedFrames[side].size() > 0)
233 return _VisitedFrames[side].pop();
234 else
235 return null;
236 }
237
238 /**
239 * Adds the given Frame to the back-stack
240 *
241 * @param frame
242 * The Frame to add
243 */
244 public static void addToBack(Frame toAdd) {
245 int side = getCurrentSide();
246
247 // do not allow duplicate frames
248 if (_VisitedFrames[side].size() > 0)
249 if (_VisitedFrames[side].peek().equals(toAdd.getFrameName())) {
250 return;
251 }
252
253 Item ip = FrameUtils.getCurrentItem();
254 if (ip == null)
255 _VisitedFrames[side].push(toAdd.getFrameName());
256 else
257 _VisitedFrames[side].push(toAdd.getFrameName());
258 }
259
260 /**
261 * Returns a 'peek' at the end element on the back-stack of the current
262 * side. If the back-stack is empty, null is returned.
263 *
264 * @return The name of the most recent Frame added to the back-stack, or
265 * null if the back-stack is empty.
266 */
267 public static String peekFromBack() {
268 int side = getCurrentSide();
269
270 // check that the stack is not empty
271 if (_VisitedFrames[side].size() > 0)
272 return _VisitedFrames[side].peek();
273
274 // if the stack is empty, return null
275 return null;
276 }
277
278 /**
279 * Sets the currently displayed Frame to the given Frame
280 *
281 * @param frame
282 * The Frame to display on the screen
283 */
284 public static void setCurrentFrame(Frame frame) {
285 if (frame == null)
286 return;
287
288 if (_TwinFrames) {
289 if (_CurrentFrames[0] == null) {
290 _CurrentFrames[0] = frame;
291 return;
292 }
293 if (_CurrentFrames[1] == null) {
294 _CurrentFrames[1] = frame;
295 return;
296 }
297 }
298
299 // if this is already the current frame
300 if (frame == getCurrentFrame()) {
301 FrameGraphics.Repaint();
302 FrameGraphics.DisplayMessage(frame.getFrameName()
303 + " is already the current frame.");
304 return;
305 } else {
306 SessionStats.AccessedFrame();
307 }
308
309 if (_TwinFrames) {
310 // if the same frame is being shown in both sides, load a fresh
311 // copy
312 // from disk
313 if (_CurrentFrames[getOppositeSide()] == frame
314 || _CurrentFrames[getOppositeSide()].getOverlays()
315 .contains(new Overlay(frame, 1))) {
316 FrameIO.SuspendCache();
317 frame = FrameIO.LoadFrame(frame.getFrameName());
318 FrameIO.ResumeCache();
319 }
320
321 // If the frames are the same then the items for the
322 // frame that is just about to hide will still be in view
323 // so only notify items that they are hidden if the
324 // frames differ.
325 if (_CurrentFrames[getCurrentSide()] != null
326 && _CurrentFrames[0] != _CurrentFrames[1]) {
327 for (Item i : _CurrentFrames[getCurrentSide()].getItems()) {
328 i.onParentStateChanged(new ItemParentStateChangedEvent(
329 _CurrentFrames[getCurrentSide()],
330 ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN));
331 }
332 }
333 _CurrentFrames[getCurrentSide()] = frame;
334
335 // BROOK : TODO...
336 for (Item i : _CurrentFrames[getCurrentSide()].getItems()) {
337 i.onParentStateChanged(new ItemParentStateChangedEvent(
338 _CurrentFrames[getCurrentSide()],
339 ItemParentStateChangedEvent.EVENT_TYPE_SHOWN));
340 }
341 } else {
342
343 // Notifying items on the frame being hidden that they
344 // are about to be hidden.
345 // ie. Widgets use this method to remove themselves from the JPanel
346 List<Frame> currentOnlyOverlays = new LinkedList<Frame>();
347 List<Frame> nextOnlyOverlays = new LinkedList<Frame>();
348 List<Frame> sharedOverlays = new LinkedList<Frame>();
349
350 // Get all overlayed frames seen by the next frame
351 for (Overlay o : frame.getOverlaysDeep()) {
352 if (!nextOnlyOverlays.contains(o))
353 nextOnlyOverlays.add(o.Frame);
354 }
355
356 // Get all overlayed frames seen by the current frame
357 if (_CurrentFrames[getCurrentSide()] != null) {
358 for (Overlay o : _CurrentFrames[getCurrentSide()]
359 .getOverlaysDeep()) {
360 if (!currentOnlyOverlays.contains(o))
361 currentOnlyOverlays.add(o.Frame);
362 }
363 }
364
365 // Extract shared overlays between the current and next frame
366 for (Frame of : currentOnlyOverlays) {
367 if (nextOnlyOverlays.contains(of)) {
368 sharedOverlays.add(of);
369 }
370 }
371
372 // The first set, currentOnlyOverlays, must be notified that they
373 // are hidden
374 List<Item> items = new LinkedList<Item>();
375
376 // Notify items that will not be in view any more
377 if (_CurrentFrames[getCurrentSide()] != null) {
378 List<Frame> seen = new LinkedList<Frame>();
379 seen.addAll(sharedOverlays); // Signify that seen all shared
380 // overlays
381 seen.remove(_CurrentFrames[getCurrentSide()]); // must ensure
382 // excluded
383
384 // Get all items seen from the current frame - including all
385 // possible non-shared overlays
386 FrameGraphics.AddAllOverlayItems(items,
387 _CurrentFrames[getCurrentSide()], seen);
388
389 // Notify items that they are hidden
390 for (Item i : items) {
391 i.onParentStateChanged(new ItemParentStateChangedEvent(
392 _CurrentFrames[getCurrentSide()],
393 ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN));
394 }
395 }
396
397 // Set the new frame
398 _CurrentFrames[getCurrentSide()] = frame;
399 frame.setMaxSize(FrameGraphics.getMaxFrameSize());
400
401 // Notify items on the frame being displayed that they are in view
402 // ie. widgets use this method to add themselves to the content pane
403 items.clear();
404
405 // Get all items seen from the new frame - including all possible
406 // non-shared overlays
407 FrameGraphics.AddAllOverlayItems(items,
408 _CurrentFrames[getCurrentSide()], new LinkedList<Frame>());
409
410 // Seperate current frames items from its overlayed items
411 List<Item> nonOverlayItems = _CurrentFrames[getCurrentSide()]
412 .getItems();
413 items.removeAll(nonOverlayItems);
414
415 // Notify overlay items that they are shown
416 for (Item i : items) {
417 Overlay owner = _CurrentFrames[getCurrentSide()]
418 .getOverlayOwner(i);
419 // if (owner == null) i.onParentFameShown(false, 0);
420 // else ...
421 assert (owner != null);
422 i.onParentStateChanged(new ItemParentStateChangedEvent(
423 _CurrentFrames[getCurrentSide()],
424 ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY,
425 owner.Level));
426 }
427
428 // Notify non-overlay items that they are shown
429 for (Item i : nonOverlayItems) {
430 i.onParentStateChanged(new ItemParentStateChangedEvent(
431 _CurrentFrames[getCurrentSide()],
432 ItemParentStateChangedEvent.EVENT_TYPE_SHOWN));
433 }
434 }
435
436 FrameGraphics.Repaint();
437 }
438
439 public static void UpdateTitle() {
440 StringBuffer title = new StringBuffer(TITLE + " [");
441
442 if (FrameGraphics.isAudienceMode())
443 title.append(" - Audience Mode");
444 else
445 title.append(SessionStats.getShortStats()).append(']');
446
447 _Browser.setTitle(title.toString());
448 }
449
450 public static int getCurrentSide() {
451 if (_Browser == null)
452 return 0;
453
454 if (_TwinFrames
455 && FrameMouseActions.MouseX >= (_Browser.getWidth() / 2)
456 && _CurrentFrames[1] != null)
457 return 1;
458
459 if (_CurrentFrames[0] == null && _CurrentFrames[1] != null)
460 return 1;
461
462 return 0;
463 }
464
465 private static int getOppositeSide() {
466 if (getCurrentSide() == 0)
467 return 1;
468
469 return 0;
470 }
471
472 public static int FrameOnSide(Frame toFind) {
473 if (_CurrentFrames[0] == toFind)
474 return 0;
475
476 if (_CurrentFrames[1] == toFind)
477 return 1;
478
479 return -1;
480 }
481
482 /**
483 * Returns the Frame currently being displayed on the screen.
484 *
485 * @return The Frame currently displayed.
486 */
487 public static Frame getCurrentFrame() {
488 return _CurrentFrames[getCurrentSide()];
489 }
490
491 public static Frame getOppositeFrame() {
492 return _CurrentFrames[getOppositeSide()];
493 }
494
495 public static Frame[] getFrames() {
496 return _CurrentFrames;
497 }
498
499 public static int getMiddle() {
500 return _Browser.getWidth() / 2;
501 }
502
503 public static int getHeight() {
504 return _Browser.getHeight();
505 }
506
507 /**
508 * Returns the current mouse X coordinate. This coordinate is relative to
509 * the left edge of the frame the mouse is in. It takes into account the
510 * user being in twin frames mode.
511 *
512 * @return The X coordinate of the mouse.
513 */
514 public static int getMouseX() {
515 if (_TwinFrames && getMouseY() < FrameGraphics.getMaxSize().height)
516 return FrameMouseActions.MouseX % (_Browser.getWidth() / 2);
517
518 return FrameMouseActions.MouseX;
519 }
520
521 /**
522 * Returns the current mouse Y coordinate. This is relative to the top of
523 * the frame.
524 *
525 * @return The Y coordinate of the mouse.
526 */
527 public static int getMouseY() {
528 return FrameMouseActions.MouseY;
529 }
530
531 public static int getRealMouseX() {
532 return FrameMouseActions.MouseX;
533 }
534
535 public static int getRealMouseY() {
536 return FrameMouseActions.MouseY;
537 }
538
539 public static void Back() {
540 NavigationActions.ResetLastAddToBack();
541
542 int side = getCurrentSide();
543
544 // there must be a frame to go back to
545 if (_VisitedFrames[side].size() < 1) {
546 FrameGraphics.DisplayMessage("You are already on the home frame");
547 return;
548 }
549
550 if (!FrameUtils.LeavingFrame(getCurrentFrame())) {
551 FrameGraphics.DisplayMessage("Back operation cancelled");
552 return;
553 }
554
555 String oldFrame = getCurrentFrame().getFrameName().toLowerCase();
556
557 // do not get a cached version (in case it is in the other window)
558 if (isTwinFramesOn())
559 FrameIO.SuspendCache();
560 Frame frame = FrameIO.LoadFrame(_VisitedFrames[side].pop());
561 if (isTwinFramesOn())
562 FrameIO.ResumeCache();
563
564 FrameUtils.DisplayFrame(frame, false);
565 FrameMouseActions.setHighlightHold(true);
566
567 for (Item i : frame.getItems())
568 if (i.getLink() != null
569 && i.getAbsoluteLink().toLowerCase().equals(oldFrame)) {
570 i.setSelectedMode(Item.SelectedMode.Normal,
571 BACK_HIGHLIGHT_COLOR);
572 // check if its an @f item and if so update the buffer
573 if (i instanceof Picture) {
574 Picture p = (Picture) i;
575 p.refresh();
576 }
577 }
578
579 FrameGraphics.Repaint();
580 }
581
582 /**
583 * Toggles the display of frames between TwinFrames mode and Single frame
584 * mode.
585 */
586 public static void ToggleTwinFrames() {
587 // determine which side is the active side
588 int opposite = getOppositeSide();
589 int current = getCurrentSide();
590 _TwinFrames = !_TwinFrames;
591
592 // if TwinFrames is being turned on
593 if (_TwinFrames) {
594 // if this is the first time TwinFrames has been toggled on,
595 // load
596 // the user's first frame
597 if (_VisitedFrames[opposite].size() == 0) {
598 FrameIO.SuspendCache();
599 setCurrentFrame(FrameIO.LoadFrame(UserSettings.FirstFrame));
600 FrameIO.ResumeCache();
601 } else {
602 // otherwise, restore the frame from the side's back-stack
603 setCurrentFrame(FrameIO.LoadFrame(_VisitedFrames[opposite]
604 .pop()));
605 }
606
607 // else, TwinFrames is being turned off
608 } else {
609 // add the frame to the back-stack
610 Frame hiding = _CurrentFrames[opposite];
611 FrameUtils.LeavingFrame(hiding);
612 _VisitedFrames[opposite].add(hiding.getFrameName());
613 _CurrentFrames[opposite] = null;
614 _CurrentFrames[current].setMaxSize(FrameGraphics.getMaxFrameSize());
615 }
616 if (_CurrentFrames[current] != null)
617 _CurrentFrames[current].setMaxSize(FrameGraphics.getMaxFrameSize());
618 if (_CurrentFrames[opposite] != null)
619 _CurrentFrames[opposite]
620 .setMaxSize(FrameGraphics.getMaxFrameSize());
621
622 FrameGraphics.Clear();
623 FrameGraphics.Repaint();
624 }
625
626 public static boolean isTwinFramesOn() {
627 return _TwinFrames;
628 }
629
630 public static void Reload(int side) {
631 if (side < 0)
632 return;
633
634 FrameIO.SuspendCache();
635 _CurrentFrames[side] = FrameIO.LoadFrame(_CurrentFrames[side]
636 .getFrameName());
637 FrameIO.ResumeCache();
638 }
639
640 public static boolean DisplayConfirmDialog(String message, String title,
641 int type, int options, int res) {
642 return JOptionPane.showConfirmDialog(_Browser, message, title, options,
643 type) == res;
644 }
645
646 public static final int RESULT_OK = JOptionPane.OK_OPTION;
647
648 public static final int OPTIONS_OK_CANCEL = JOptionPane.OK_CANCEL_OPTION;
649
650 public static final int TYPE_WARNING = JOptionPane.WARNING_MESSAGE;
651
652 public static void pressMouse(int buttons) {
653 _Robot.mousePress(buttons);
654 }
655
656 public static void releaseMouse(int buttons) {
657 _Robot.mouseRelease(buttons);
658 }
659
660 public static void clickMouse(int buttons) {
661 _Robot.mousePress(buttons);
662 _Robot.mouseRelease(buttons);
663 }
664}
Note: See TracBrowser for help on using the repository browser.