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

Last change on this file since 167 was 167, checked in by bjn8, 16 years ago

Improvements for widgets and popups

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