package org.expeditee.gui; import java.awt.AWTException; import java.awt.Color; import java.awt.Cursor; import java.awt.Image; import java.awt.Point; import java.awt.Robot; import java.awt.Toolkit; import java.awt.geom.Point2D; import java.awt.image.MemoryImageSource; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Stack; import javax.swing.JOptionPane; import org.expeditee.items.Item; import org.expeditee.items.ItemParentStateChangedEvent; import org.expeditee.items.Picture; import org.expeditee.items.Text; import org.expeditee.stats.SessionStats; /** * This Interface is used by the Frame to control all display input and output. * * @author jdm18 * */ public class DisplayIO { private static final int SMALL_CURSOR_SIZE = 16; private static final int MEDIUM_CURSOR_SIZE = 32; private static final int LARGE_CURSOR_SIZE = 64; /** * The color to be used to highlight the linked parent item, when the user * navigates backwards. */ public static final Color BACK_HIGHLIGHT_COLOR = Color.MAGENTA; private static Browser _Browser; // The current Frame being displayed on the screen. private static Frame _CurrentFrames[] = new Frame[2]; // Maintains the list of frames visited thus-far for back-tracking @SuppressWarnings("unchecked") private static Stack[] _VisitedFrames = new Stack[2]; // used to change the mouse cursor position on the screen private static Robot _Robot; private static boolean _TwinFrames = false; /** * The title to display in the Title bar. */ public static final String TITLE = "Exp02Jul2008A"; private DisplayIO() { } public static void Init(Browser browser) { _Browser = browser; try { _Robot = new Robot(); } catch (AWTException e) { e.printStackTrace(); } Point mouse = _Browser.getMousePosition(); if (mouse != null) { FrameMouseActions.MouseX = mouse.x; FrameMouseActions.MouseY = mouse.y; } _VisitedFrames[0] = new Stack(); _VisitedFrames[1] = new Stack(); } public static void setTextCursor(Text text, int cursorMovement) { setTextCursor(text, cursorMovement, false); } public static void setTextCursor(Text text, int cursorMovement, boolean newSize) { int size = Math.round(text.getSize()); Point2D.Float newMouse = text.moveCursor(cursorMovement, DisplayIO .getFloatMouseX(), FrameMouseActions.MouseY); if (!newSize && cursorType == Item.TEXT_CURSOR) { if (cursorMovement != 0) DisplayIO.setCursorPosition(newMouse, false); return; } cursorType = Item.TEXT_CURSOR; // Do some stuff to adjust the cursor size based on the font size final int MEDIUM_CURSOR_CUTOFF = 31; final int LARGE_CURSOR_CUTOFF = 62; int cursorSize = LARGE_CURSOR_SIZE; int hotspotPos = 0; int start = 0; if (size < MEDIUM_CURSOR_CUTOFF) { cursorSize = MEDIUM_CURSOR_SIZE; start = cursorSize - size - 2; hotspotPos = cursorSize - (size + 2) / 4; } else if (size < LARGE_CURSOR_CUTOFF) { hotspotPos = cursorSize - (size - 5) / 4; start = cursorSize - size - 2; } else { int FIXED_CURSOR_MIN = 77; if (size >= FIXED_CURSOR_MIN) { hotspotPos = cursorSize - 2; } else { hotspotPos = size - (FIXED_CURSOR_MIN - cursorSize); } } int[] pixels = new int[cursorSize * cursorSize]; for (int i = start; i < cursorSize; i++) pixels[i * cursorSize] = pixels[(i * cursorSize) + 1] = 0xFF000000; Image image = Toolkit.getDefaultToolkit().createImage( new MemoryImageSource(cursorSize, cursorSize, pixels, 0, cursorSize)); Cursor textCursor = Toolkit.getDefaultToolkit().createCustomCursor( image, new Point(0, hotspotPos), "textcursor"); _Browser.setCursor(textCursor); DisplayIO.setCursorPosition(newMouse, false); } /** * Sets the type of cursor the display should be using * * @param type * The type of cursor to display, using constants defined in the * Cursor class. */ public static void setCursor(int type) { // avoid flicker when not changing if (type == cursorType || type == Item.UNCHANGED_CURSOR) return; cursorType = type; if (type == Item.HIDDEN_CURSOR) { int[] pixels = new int[SMALL_CURSOR_SIZE * SMALL_CURSOR_SIZE]; Image image = Toolkit.getDefaultToolkit().createImage( new MemoryImageSource(SMALL_CURSOR_SIZE, SMALL_CURSOR_SIZE, pixels, 0, SMALL_CURSOR_SIZE)); Cursor transparentCursor = Toolkit.getDefaultToolkit() .createCustomCursor(image, new Point(0, 0), "invisiblecursor"); _Browser.setCursor(transparentCursor); } else _Browser.setCursor(new Cursor(type)); } private static int cursorType = Item.DEFAULT_CURSOR; public static int getCursor() { return cursorType; } /** * Moves the mouse cursor to the given x,y coordinates on the screen * * @param x * The x coordinate * @param y * The y coordinate */ public static void setCursorPosition(float x, float y) { setCursorPosition(x, y, true); } public static void setCursorPosition(float x, float y, boolean forceArrow) { // Adjust the position to move the mouse to to account for being in // TwinFramesMode if (_TwinFrames) { if (getCurrentSide() == 1) { int middle = getMiddle(); x += middle; } } float deltax = x - FrameMouseActions.MouseX; float deltay = y - FrameMouseActions.MouseY; // When the Robot moves the cursor... a short time later a mouseMoved // event is generated... // We want to ignore this event by remembering the location the robot // was shifted to. FrameMouseActions.setLastRobotMove(x, y); if (Frame.itemAttachedToCursor()) { List toMove = Frame.FreeItems; for (Item move : toMove) { move.setPosition(move.getX() + deltax, move.getY() + deltay); } } // cheat FrameMouseActions.setForceArrow(forceArrow); int mouseX = (int) _Browser.getContentPane().getLocationOnScreen() .getX() + Math.round(x); int mouseY = (int) _Browser.getContentPane().getLocationOnScreen() .getY() + Math.round(y); _Robot.mouseMove(mouseX, mouseY); // System.out.println("MouseMoved: " + x + "," + y); } public static void resetCursorOffset() { FrameMouseActions.resetOffset(); } /** * Sets the current cursor position in the current frame * * @param pos */ public static void setCursorPosition(Point2D.Float pos) { setCursorPosition(pos.x, pos.y); } public static void setCursorPosition(Point pos) { setCursorPosition(pos.x, pos.y); } public static void setCursorPosition(Point pos, boolean forceArrow) { setCursorPosition(pos.x, pos.y, forceArrow); } public static void setCursorPosition(Point2D.Float pos, boolean forceArrow) { setCursorPosition(pos.x, pos.y, forceArrow); } private static boolean _typed = false; public static void setKeyTyped(boolean val) { _typed = val; } public static boolean getKeyTyped() { return _typed; } /** * Returns the top item (last added) of the Back-Stack (which is popped off) * * @return The name of the last Frame added to the back-stack */ public static String getLastFrame() { int side = getCurrentSide(); if (_VisitedFrames[side].size() > 0) return _VisitedFrames[side].pop(); else return null; } /** * Adds the given Frame to the back-stack * * @param frame * The Frame to add */ public static void addToBack(Frame toAdd) { int side = getCurrentSide(); // // do not allow duplicate frames // if (_VisitedFrames[side].size() > 0) // if (_VisitedFrames[side].peek().equals(toAdd.getName())) { // return; // } Item ip = FrameUtils.getCurrentItem(); if (ip == null) _VisitedFrames[side].push(toAdd.getName()); else _VisitedFrames[side].push(toAdd.getName()); // System.out.println("Added: " + _VisitedFrames[side].size()); } public static void removeFromBack() { int side = getCurrentSide(); // there must be a frame to go back to if (_VisitedFrames[side].size() > 1) { _VisitedFrames[side].pop(); } // System.out.println("Removed: " + _VisitedFrames[side].size()); } /** * Returns a 'peek' at the end element on the back-stack of the current * side. If the back-stack is empty, null is returned. * * @return The name of the most recent Frame added to the back-stack, or * null if the back-stack is empty. */ public static String peekFromBackUpStack() { int side = getCurrentSide(); // check that the stack is not empty if (_VisitedFrames[side].size() > 0) return _VisitedFrames[side].peek(); // if the stack is empty, return null return null; } /** * Sets the currently displayed Frame to the given Frame * * @param frame * The Frame to display on the screen */ public static void setCurrentFrame(Frame frame) { if (frame == null) return; if (_TwinFrames) { if (_CurrentFrames[0] == null) { _CurrentFrames[0] = frame; return; } if (_CurrentFrames[1] == null) { _CurrentFrames[1] = frame; return; } } // if this is already the current frame if (frame == getCurrentFrame()) { FrameGraphics.Repaint(); FrameGraphics.DisplayMessage(frame.getName() + " is already the current frame."); return; } else { SessionStats.AccessedFrame(); } if (_TwinFrames) { // if the same frame is being shown in both sides, load a fresh // copy from disk if (_CurrentFrames[getOppositeSide()] == frame || _CurrentFrames[getOppositeSide()].hasOverlay(frame)) { FrameIO.SuspendCache(); frame = FrameIO.LoadFrame(frame.getName()); FrameIO.ResumeCache(); } // If the frames are the same then the items for the // frame that is just about to hide will still be in view // so only notify items that they are hidden if the // frames differ. if (_CurrentFrames[getCurrentSide()] != null && _CurrentFrames[0] != _CurrentFrames[1]) { for (Item i : _CurrentFrames[getCurrentSide()].getItems()) { i.onParentStateChanged(new ItemParentStateChangedEvent( _CurrentFrames[getCurrentSide()], ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN)); } } _CurrentFrames[getCurrentSide()] = frame; // BROOK : TODO... for (Item i : _CurrentFrames[getCurrentSide()].getItems()) { i.onParentStateChanged(new ItemParentStateChangedEvent( _CurrentFrames[getCurrentSide()], ItemParentStateChangedEvent.EVENT_TYPE_SHOWN)); } } else { // Notifying items on the frame being hidden that they // are about to be hidden. // ie. Widgets use this method to remove themselves from the JPanel List currentOnlyOverlays = new LinkedList(); List nextOnlyOverlays = new LinkedList(); List sharedOverlays = new LinkedList(); // Get all overlayed frames seen by the next frame for (Overlay o : frame.getOverlays()) { if (!nextOnlyOverlays.contains(o)) nextOnlyOverlays.add(o.Frame); } // Get all overlayed frames seen by the current frame if (_CurrentFrames[getCurrentSide()] != null) { for (Overlay o : _CurrentFrames[getCurrentSide()].getOverlays()) { if (!currentOnlyOverlays.contains(o)) currentOnlyOverlays.add(o.Frame); } } // Extract shared overlays between the current and next frame for (Frame of : currentOnlyOverlays) { if (nextOnlyOverlays.contains(of)) { sharedOverlays.add(of); } } // The first set, currentOnlyOverlays, must be notified that they // are hidden Collection items = new LinkedList(); // Notify items that will not be in view any more if (_CurrentFrames[getCurrentSide()] != null) { List seen = new LinkedList(); seen.addAll(sharedOverlays); // Signify that seen all shared // overlays seen.remove(_CurrentFrames[getCurrentSide()]); // must ensure // excluded // Get all items seen from the current frame - including all // possible non-shared overlays items = _CurrentFrames[getCurrentSide()].getAllItems(); for (Frame f : seen) items.removeAll(f.getAllItems()); // Notify items that they are hidden for (Item i : items) { i.onParentStateChanged(new ItemParentStateChangedEvent( _CurrentFrames[getCurrentSide()], ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN)); } } // Set the new frame _CurrentFrames[getCurrentSide()] = frame; frame.setMaxSize(FrameGraphics.getMaxFrameSize()); // Notify items on the frame being displayed that they are in view // ie. widgets use this method to add themselves to the content pane items.clear(); // Notify overlay items that they are shown for (Item i : frame.getOverlayItems()) { Overlay owner = frame.getOverlayOwner(i); // if (owner == null) i.onParentFameShown(false, 0); // else ... assert (owner != null); i .onParentStateChanged(new ItemParentStateChangedEvent( frame, ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY, owner.permission)); } // Notify non-overlay items that they are shown for (Item i : frame.getItems()) { i.onParentStateChanged(new ItemParentStateChangedEvent(frame, ItemParentStateChangedEvent.EVENT_TYPE_SHOWN)); } } FrameGraphics.Repaint(); } public static void UpdateTitle() { StringBuffer title = new StringBuffer(TITLE); if (FrameGraphics.isAudienceMode()) title.append(" - Audience Mode"); else if (FrameGraphics.isXRayMode()) title.append(" - X-Ray Mode"); else title.append(" [").append(SessionStats.getShortStats()).append(']'); _Browser.setTitle(title.toString()); } public static int getCurrentSide() { if (_Browser == null) return 0; if (_TwinFrames && FrameMouseActions.MouseX >= (_Browser.getWidth() / 2) && _CurrentFrames[1] != null) return 1; if (_CurrentFrames[0] == null && _CurrentFrames[1] != null) return 1; return 0; } private static int getOppositeSide() { if (getCurrentSide() == 0) return 1; return 0; } public static int FrameOnSide(Frame toFind) { if (_CurrentFrames[0] == toFind) return 0; if (_CurrentFrames[1] == toFind) return 1; return -1; } /** * Returns the Frame currently being displayed on the screen. * * @return The Frame currently displayed. */ public static Frame getCurrentFrame() { return _CurrentFrames[getCurrentSide()]; } public static Frame getOppositeFrame() { return _CurrentFrames[getOppositeSide()]; } public static Frame[] getFrames() { return _CurrentFrames; } public static int getMiddle() { return _Browser.getWidth() / 2; } public static int getHeight() { return _Browser.getHeight(); } /** * Returns the current mouse X coordinate. This coordinate is relative to * the left edge of the frame the mouse is in. It takes into account the * user being in twin frames mode. * * @return The X coordinate of the mouse. */ public static float getFloatMouseX() { if (_TwinFrames && FrameMouseActions.MouseY < FrameGraphics.getMaxSize().height) return FrameMouseActions.MouseX % (_Browser.getWidth() / 2); return FrameMouseActions.MouseX; } /** * Returns the current mouse X coordinate. This coordinate is relative to * the left edge of the frame the mouse is in. It takes into account the * user being in twin frames mode. * * @return The X coordinate of the mouse. */ public static int getMouseX() { return Math.round(getFloatMouseX()); } public static void Back() { int side = getCurrentSide(); // there must be a frame to go back to if (_VisitedFrames[side].size() < 1) { FrameGraphics .DisplayMessageOnce("You are already on the home frame"); return; } if (!FrameUtils.LeavingFrame(getCurrentFrame())) { FrameGraphics.DisplayMessage("Back operation cancelled"); return; } String oldFrame = getCurrentFrame().getName().toLowerCase(); // do not get a cached version (in case it is in the other window) if (isTwinFramesOn()) FrameIO.SuspendCache(); Frame frame = FrameIO.LoadFrame(_VisitedFrames[side].pop()); // If the top frame on the backup stack is the current frame go back // again... if (frame.equals(getCurrentFrame())) { Back(); return; } if (isTwinFramesOn()) { FrameIO.ResumeCache(); } FrameUtils.DisplayFrame(frame, false); FrameMouseActions.setHighlightHold(true); for (Item i : frame.getItems()) { if (i.getLink() != null && i.getAbsoluteLink().toLowerCase().equals(oldFrame)) { if (i.getHighlightMode() != Item.HighlightMode.Normal) { i.setHighlightMode(Item.HighlightMode.Normal, BACK_HIGHLIGHT_COLOR); } // check if its an @f item and if so update the buffer if (i instanceof Picture) { Picture p = (Picture) i; p.refresh(); } } } FrameGraphics.Repaint(); } /** * Toggles the display of frames between TwinFrames mode and Single frame * mode. */ public static void ToggleTwinFrames() { // determine which side is the active side int opposite = getOppositeSide(); int current = getCurrentSide(); _TwinFrames = !_TwinFrames; // if TwinFrames is being turned on if (_TwinFrames) { // if this is the first time TwinFrames has been toggled on, // load // the user's first frame if (_VisitedFrames[opposite].size() == 0) { FrameIO.SuspendCache(); setCurrentFrame(FrameIO.LoadFrame(UserSettings.FirstFrame)); FrameIO.ResumeCache(); } else { // otherwise, restore the frame from the side's back-stack setCurrentFrame(FrameIO.LoadFrame(_VisitedFrames[opposite] .pop())); } // else, TwinFrames is being turned off } else { // add the frame to the back-stack Frame hiding = _CurrentFrames[opposite]; FrameUtils.LeavingFrame(hiding); _VisitedFrames[opposite].add(hiding.getName()); _CurrentFrames[opposite] = null; _CurrentFrames[current].setMaxSize(FrameGraphics.getMaxFrameSize()); } if (_CurrentFrames[current] != null) _CurrentFrames[current].setMaxSize(FrameGraphics.getMaxFrameSize()); if (_CurrentFrames[opposite] != null) _CurrentFrames[opposite] .setMaxSize(FrameGraphics.getMaxFrameSize()); FrameGraphics.Clear(); FrameGraphics.Repaint(); } public static boolean isTwinFramesOn() { return _TwinFrames; } public static void Reload(int side) { if (side < 0) return; FrameIO.SuspendCache(); _CurrentFrames[side] = FrameIO .LoadFrame(_CurrentFrames[side].getName()); FrameIO.ResumeCache(); } public static boolean DisplayConfirmDialog(String message, String title, int type, int options, int res) { return JOptionPane.showConfirmDialog(_Browser, message, title, options, type) == res; } public static final int RESULT_OK = JOptionPane.OK_OPTION; public static final int OPTIONS_OK_CANCEL = JOptionPane.OK_CANCEL_OPTION; public static final int TYPE_WARNING = JOptionPane.WARNING_MESSAGE; public static void pressMouse(int buttons) { _Robot.mousePress(buttons); } public static void releaseMouse(int buttons) { _Robot.mouseRelease(buttons); } public static void clickMouse(int buttons) { _Robot.mousePress(buttons); _Robot.mouseRelease(buttons); } /** * Moves the cursor the end of this item. * * @param i */ public static void MoveCursorToEndOfItem(Item i) { setTextCursor((Text) i, Text.END, true); } }