package org.expeditee.gio.swing; import java.awt.AWTException; import java.awt.Color; import java.awt.Dimension; import java.awt.KeyEventDispatcher; import java.awt.KeyboardFocusManager; import java.awt.MouseInfo; import java.awt.Robot; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.awt.event.WindowStateListener; import java.util.List; import javax.swing.SwingUtilities; import javax.swing.Timer; import org.expeditee.core.Line; import org.expeditee.core.Point; import org.expeditee.gio.EcosystemManager; import org.expeditee.gio.InputManager; import org.expeditee.gio.input.InputEvent; import org.expeditee.gio.input.InputEvent.InputType; import org.expeditee.gio.input.KBMInputEvent; import org.expeditee.gui.Popup; import org.expeditee.gui.PopupManager; import org.expeditee.items.widgets.Widget; public class SwingInputManager extends InputManager implements ComponentListener, WindowListener, WindowStateListener, KeyListener, MouseListener, MouseMotionListener, MouseWheelListener, KeyEventDispatcher { /** Singleton instance. */ private static SwingInputManager _instance; /** Singleton constructor. */ public static SwingInputManager getInstance() { if (_instance == null) { try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { _instance = new SwingInputManager(); } }); } catch (Exception e) { System.err.println("Error while initialising InputManager. Aborting..."); System.exit(1); } } return _instance; } /** TODO: What functionality does this object provide? */ private MouseEventRouter _mouseEventRouter; /** For robotically controlling the mouse position. */ // private Robot _robot; private MouseCorrectRobot _robot; private SwingInputManager() { SwingInputManager swingInputManager = this; // Sign up to receive input as events SwingGraphicsManager graphicsManager = SwingMiscManager.getIfUsingSwingGraphicsManager(); if (graphicsManager != null) { graphicsManager.addWindowListener(swingInputManager); graphicsManager.addWindowStateListener(swingInputManager); graphicsManager.getContentPane().addKeyListener(swingInputManager); // TODO: Why do we need... graphicsManager.addKeyListener(swingInputManager); // TODO: ...both of these? cts16 graphicsManager.addComponentListener(swingInputManager); // Sign up to receive mouse events, from the SwingGraphicsManager // TODO: Decide if we will keep using MouseEventRouter. cts16 _mouseEventRouter = new MouseEventRouter(graphicsManager.getJMenuBar(), graphicsManager.getContentPane()); _mouseEventRouter.addExpediteeMouseListener(swingInputManager); _mouseEventRouter.addExpediteeMouseMotionListener(swingInputManager); _mouseEventRouter.addExpediteeMouseWheelListener(swingInputManager); // enable the glasspane-for capturing all mouse events graphicsManager.setGlassPane(_mouseEventRouter); graphicsManager.getGlassPane().setVisible(true); graphicsManager.getContentPane().setBackground(Color.WHITE); } graphicsManager.getContentPane().setFocusTraversalKeysEnabled(false); // Set up the robot (for controlling the mouse) // TODO: What to do if robot throws exception??? cts16 try { _robot = new MouseCorrectRobot(); } catch (AWTException e) { e.printStackTrace(); } // Create the timer for firing timeout events Timer.setLogTimers(false); _timer = new Timer(1, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { swingInputManager.triggerTimeoutEvents(); } }); _timer.setRepeats(false); // AWTEventListener[] listeners = Toolkit.getDefaultToolkit().getAWTEventListeners(); // for (AWTEventListener listener: listeners) { // System.err.println(listener); // F10FriendlyKeyboardFocusManager keyboardManager = new F10FriendlyKeyboardFocusManager(listener); // KeyboardFocusManager.setCurrentKeyboardFocusManager(keyboardManager); // } // // F10FriendlyKeyboardFocusManager keyboardManager = new F10FriendlyKeyboardFocusManager(this); // KeyboardFocusManager.setCurrentKeyboardFocusManager(keyboardManager); KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this); } @Override protected Point getRealCursorPosition() { Point mouseScreenPos = SwingConversions.fromSwingPoint(MouseInfo.getPointerInfo().getLocation()); return screenToWindowPosition(mouseScreenPos); } @Override public void setCursorPosition(Point position) { // System.err.println("Move to position; Moving to: " + position); Point screenPosition = windowToScreenPosition(position); // System.err.println("Move to position: window adjusted; Moving to: " + // screenPosition); // _robot.mouseMove(screenPosition.x, screenPosition.y); final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); _robot.MoveMouseControlled(screenPosition.getX() / screenSize.getWidth(), screenPosition.getY() / screenSize.getHeight()); updateCursorPosition(position.getX(), position.getY()); } @Override public void windowStateChanged(WindowEvent e) { // Does nothing. cts16 } @Override public void windowOpened(WindowEvent e) { // Does nothing. cts16 } @Override public void windowClosing(WindowEvent e) { // Does nothing. cts16 } @Override public void windowClosed(WindowEvent e) { SwingGraphicsManager g = SwingMiscManager.getIfUsingSwingGraphicsManager(); if (g == null || !g.isFullscreenTransitionPending()) { distributeWindowEvent(WindowEventType.WINDOW_CLOSED); } } @Override public void windowIconified(WindowEvent e) { // Does nothing. cts16 } @Override public void windowDeiconified(WindowEvent e) { // Does nothing. cts16 } @Override public void windowActivated(WindowEvent e) { // Does nothing. cts16 } @Override public void windowDeactivated(WindowEvent e) { // Does nothing. cts16 } @Override public void componentResized(ComponentEvent e) { SwingGraphicsManager g = SwingMiscManager.getIfUsingSwingGraphicsManager(); if (g != null) { g.refreshRootSurface(); } distributeWindowEvent(WindowEventType.WINDOW_RESIZED); } @Override public void componentMoved(ComponentEvent e) { // Does nothing. cts16 } @Override public void componentShown(ComponentEvent e) { SwingGraphicsManager g = SwingMiscManager.getIfUsingSwingGraphicsManager(); if (g != null) { g.finishFullscreenTransition(); } } @Override public void componentHidden(ComponentEvent e) { // Does nothing. cts16 } /** Converts a location given in screen-space to window-space. */ private Point screenToWindowPosition(Point screenPosition) { Point windowTopLeft = EcosystemManager.getGraphicsManager().getWindowLocation(); return Point.difference(screenPosition, windowTopLeft); } /** Converts a location given in window-space to screen-space. */ private Point windowToScreenPosition(Point windowPosition) { Point windowTopLeft = EcosystemManager.getGraphicsManager().getWindowLocation(); return windowPosition.clone().add(windowTopLeft); } @Override public void mouseWheelMoved(MouseWheelEvent e) { //ensureShiftAltStateCorrect(e); // Give widgets first whack at input distributeNativeInput(e); if (e.isConsumed()) { return; } try { // Create an input event Integer rotation = new Integer(e.getWheelRotation()); InputEvent event = new KBMInputEvent(KBMInputEvent.EventType.MOUSE_WHEEL_SCROLL, rotation); // Translate and perform the input distributeInputEvent(event); } catch (Exception exception) { System.err.println(exception.getMessage()); } } @Override public void mouseDragged(MouseEvent e) { mouseMoved(e); } @Override public void mouseMoved(MouseEvent e) { //ensureShiftAltStateCorrect(e); // Update the cursor position updateCursorPosition(e.getX(), e.getY()); // Give widgets first whack at input distributeNativeInput(e); if (e.isConsumed()) { return; } try { // Create an input event Point movedFrom = getCursorPosition(); Point movedTo = new Point(e.getX(), e.getY()); InputEvent event = new KBMInputEvent(KBMInputEvent.EventType.MOUSE_MOVE, new Line(movedFrom, movedTo)); // Translate and perform the input distributeInputEvent(event); } catch (Exception exception) { System.err.println(exception.getMessage()); } } @Override public void mouseClicked(MouseEvent e) { //ensureShiftAltStateCorrect(e); // Give widgets first whack at input distributeNativeInput(e); if (e.isConsumed()) { return; } // Do nothing else (handled by mousePressed and mouseReleased) } @Override public void mousePressed(MouseEvent e) { //ensureShiftAltStateCorrect(e); mouseAction(e, true); } @Override public void mouseReleased(MouseEvent e) { //ensureShiftAltStateCorrect(e); mouseAction(e, false); } /** Handles mouse pressed/released events. */ private void mouseAction(MouseEvent e, boolean down) { // Give widgets first whack at input distributeNativeInput(e); if (e.isConsumed()) { return; } // Work out which button was pressed KBMInputEvent.MouseButton button; switch (e.getButton()) { case MouseEvent.BUTTON1: button = KBMInputEvent.MouseButton.LEFT; break; case MouseEvent.BUTTON2: button = KBMInputEvent.MouseButton.MIDDLE; break; case MouseEvent.BUTTON3: button = KBMInputEvent.MouseButton.RIGHT; break; default: return; } try { // Create an input event KBMInputEvent.EventType type = down ? KBMInputEvent.EventType.MOUSE_BUTTON_DOWN : KBMInputEvent.EventType.MOUSE_BUTTON_UP; InputEvent event = new KBMInputEvent(type, button); // Translate and perform the input distributeInputEvent(event); } catch (Exception exception) { System.err.println("SwingInputManager::mouseAction: " + exception.getMessage()); exception.printStackTrace(); } } /** * Prevents forwarding mouse-entered-window events when coming back from a * widget. */ private boolean _inWindow = false; @Override public void mouseEntered(MouseEvent e) { if (!_inWindow) { _inWindow = true; distributeWindowEvent(WindowEventType.MOUSE_ENTERED_WINDOW); } } @Override public void mouseExited(MouseEvent e) { _inWindow = false; distributeWindowEvent(WindowEventType.MOUSE_EXITED_WINDOW); } @Override public void keyTyped(KeyEvent e) { //System.err.println("SwingInputManager::keyTyped::" + KeyEvent.getKeyText(e.getKeyCode())); //ensureShiftAltStateCorrect(e); // Give widgets first whack at input distributeNativeInput(e); if (e.isConsumed()) { return; } try { // Ignore escape character and control characters if (e.getKeyChar() == KeyEvent.VK_ESCAPE || e.isControlDown() || e.isAltDown()) { return; } // Create an input event Character character = new Character(e.getKeyChar()); InputEvent event = new KBMInputEvent(KBMInputEvent.EventType.CHAR_TYPED, character); // Translate and perform the input distributeInputEvent(event); } catch (Exception exception) { exception.printStackTrace(); } } @Override public void keyPressed(KeyEvent e) { //System.err.println("SwingInputManager::keyPressed::" + KeyEvent.getKeyText(e.getKeyCode())); //ensureShiftAltStateCorrect(e); keyAction(e, true); } @Override public void keyReleased(KeyEvent e) { //System.err.println("SwingInputManager::keyReleased::" + KeyEvent.getKeyText(e.getKeyCode())); keyAction(e, false); } // private void ensureShiftAltStateCorrect(java.awt.event.InputEvent inputEvent) { //// Component source = (Component) e.getSource(); //// int id = KeyEvent.KEY_RELEASED; //// long when = System.currentTimeMillis(); //// int keyCode = KeyEvent.VK_UNDEFINED; //// char keyChar = KeyEvent.CHAR_UNDEFINED; // //// int modifiers = (e.getModifiers() & ~KeyEvent.SHIFT_DOWN_MASK); //// KeyEvent event = new KeyEvent(source, id, when, modifiers, keyCode, keyChar); //// Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); // // boolean shiftIsDownInRecordedState = StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT); // boolean shiftIsDownEventState = inputEvent.isShiftDown(); // if (shiftIsDownInRecordedState && !shiftIsDownEventState) { // try { // // Create an input event // InputEvent event = new KBMInputEvent(KBMInputEvent.EventType.KEY_UP, KBMInputEvent.Key.SHIFT); // System.err.println("SwingInputManager::ensureShiftAltStateCorrect::Sending followup Shift Up"); // // // Translate and perform the input // distributeInputEvent(event); // } catch (Exception exception) { // System.err.println(exception.getMessage()); // } // } // System.err.println("SwingInputManager::ensureShiftAltStateCorrect::datastructure shift down: " + shiftIsDownInRecordedState); // if (shiftIsDownInRecordedState != e.isShiftDown() && !e.isShiftDown()) { // System.err.println("SwingInputManager::ensureShiftAltStateCorrect::Sending shift up event."); // int modifiers = (e.getModifiers() & ~KeyEvent.SHIFT_DOWN_MASK); // KeyEvent event = new KeyEvent(source, id, when, modifiers, keyCode, keyChar); // Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); // } // boolean altIsDown = StandardInputEventListeners.kbmStateListener.isKeyDown(Key.ALT); // if (altIsDown != e.isAltDown() && !e.isAltDown()) { // System.err.println("SwingInputManager::ensureShiftAltStateCorrect::Sending alt up event."); // int modifiers = (e.getModifiers() & ~KeyEvent.ALT_DOWN_MASK); // KeyEvent event = new KeyEvent(source, id, when, modifiers, keyCode, keyChar); // Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); // } // } /** Handles key pressed/released events. */ private void keyAction(KeyEvent e, boolean down) { // Give widgets first whack at input distributeNativeInput(e); if (e.isConsumed()) { return; } KBMInputEvent.Key key = SwingConversions.fromSwingVirtualKey(e.getKeyCode()); if (key == null) { return; } try { // Create an input event KBMInputEvent.EventType type = down ? KBMInputEvent.EventType.KEY_DOWN : KBMInputEvent.EventType.KEY_UP; InputEvent event = new KBMInputEvent(type, key); // Translate and perform the input distributeInputEvent(event); } catch (Exception exception) { System.err.println(exception.getMessage()); } } @Override protected final boolean isInputTypeSupported(InputType type) { // Only keyboard/mouse input is currently supported if (type == InputEvent.InputType.KBM) { return true; } return false; } /** Forwards the given input event to the listener if it is listening for it. */ private void forwardNativeInputToListener(Object listener, java.awt.event.InputEvent event) { if (listener == null || event == null) { return; } if (listener instanceof KeyListener && event instanceof KeyEvent) { KeyListener keyListener = (KeyListener) listener; KeyEvent ke = (KeyEvent) event; switch (event.getID()) { case KeyEvent.KEY_PRESSED: keyListener.keyPressed(ke); return; case KeyEvent.KEY_RELEASED: keyListener.keyReleased(ke); return; case KeyEvent.KEY_TYPED: keyListener.keyTyped(ke); return; } } else if (event instanceof MouseEvent) { MouseEvent me = (MouseEvent) event; if (listener instanceof MouseListener) { MouseListener mouseListener = (MouseListener) listener; switch (event.getID()) { case MouseEvent.MOUSE_CLICKED: mouseListener.mouseClicked(me); return; case MouseEvent.MOUSE_PRESSED: mouseListener.mousePressed(me); return; case MouseEvent.MOUSE_RELEASED: mouseListener.mouseReleased(me); return; case MouseEvent.MOUSE_ENTERED: mouseListener.mouseEntered(me); return; case MouseEvent.MOUSE_EXITED: mouseListener.mouseExited(me); return; } } if (listener instanceof MouseMotionListener) { MouseMotionListener motionListener = (MouseMotionListener) listener; switch (event.getID()) { case MouseEvent.MOUSE_MOVED: motionListener.mouseMoved(me); return; case MouseEvent.MOUSE_DRAGGED: motionListener.mouseDragged(me); return; } } if (listener instanceof MouseWheelListener && me instanceof MouseWheelEvent) { MouseWheelListener wheelListener = (MouseWheelListener) listener; MouseWheelEvent mwe = (MouseWheelEvent) me; switch (event.getID()) { case MouseEvent.MOUSE_WHEEL: wheelListener.mouseWheelMoved(mwe); return; } } } } /** Distributes the native input to all pop-ups and widgets. */ private void distributeNativeInput(java.awt.event.InputEvent event) { // Distribute to pop-ups first distributeNativeInputToPopups(event); if (event.isConsumed()) { return; } // Then distribute to widgets distributeNativeInputToWidgets(event); } /** Distributes the input event to registered widgets. */ private void distributeNativeInputToPopups(java.awt.event.InputEvent event) { if (event == null) { return; } final List popups = PopupManager.getInstance().getPopups(); for (Popup popup : popups) { forwardNativeInputToListener(popup, event); if (event.isConsumed()) { return; } } } /** Distributes the input event to registered widgets. */ private void distributeNativeInputToWidgets(java.awt.event.InputEvent event) { if (event == null) { return; } for (Widget iw : _widgets) { java.awt.event.InputEvent e = respecInputEventForWidget(event, iw); forwardNativeInputToListener(iw, e); if (e != event && e.isConsumed()) { event.consume(); } if (event.isConsumed()) { return; } } } /** Modifies the event so that it is relative to the given widget. */ private java.awt.event.InputEvent respecInputEventForWidget(java.awt.event.InputEvent event, Widget iw) { // TODO: Complete. cts16 return event; } /** The timer which fires timeout events for the Swing input manager. */ private Timer _timer; @Override protected void updateTimer(long nextTimeout) { if (nextTimeout < 0) { nextTimeout = 0; } _timer.stop(); _timer.setInitialDelay((int) nextTimeout); _timer.start(); } /** * Obtained from stackoverflow. * https://stackoverflow.com/questions/48799393/robot-mousemove-not-moving-to-specified-location-properly * Fixes a (hopefully temporary) issue with the java.awt.Robot class on some * specific systems (such as David's laptop) wherein the mouseMove function * would move the mouse to odd locations. * * @author bln4 * */ private class MouseCorrectRobot extends Robot { final Dimension ScreenSize;// Primary Screen Size public MouseCorrectRobot() throws AWTException { super(); ScreenSize = Toolkit.getDefaultToolkit().getScreenSize(); } private double getTav(java.awt.Point a, java.awt.Point b) { return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); } public void MoveMouseControlled(double xbe, double ybe)// Position of the cursor in [0,1] ranges. (0,0) is the // upper // left corner { int xbepix = (int) (ScreenSize.width * xbe); int ybepix = (int) (ScreenSize.height * ybe); int x = xbepix; int y = ybepix; java.awt.Point mert = MouseInfo.getPointerInfo().getLocation(); java.awt.Point ElozoInitPont = new java.awt.Point(0, 0); int UgyanAztMeri = 0; final int UgyanAZtMeriLimit = 30; int i = 0; final int LepesLimit = 20000; while ((mert.x != xbepix || mert.y != ybepix) && i < LepesLimit && UgyanAztMeri < UgyanAZtMeriLimit) { ++i; if (mert.x < xbepix) { ++x; } else { --x; } if (mert.y < ybepix) { ++y; } else { --y; } mouseMove(x, y); mert = MouseInfo.getPointerInfo().getLocation(); if (getTav(ElozoInitPont, mert) < 5) { ++UgyanAztMeri; } else { UgyanAztMeri = 0; ElozoInitPont.x = mert.x; ElozoInitPont.y = mert.y; } } } } @Override public boolean dispatchKeyEvent(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_F10) { if (KeyEvent.KEY_PRESSED == e.getID()) { keyPressed(e); } else if (KeyEvent.KEY_RELEASED == e.getID()) { keyReleased(e); } else if (KeyEvent.KEY_TYPED == e.getID()) { keyTyped(e); } else { System.err.println("SwingInputManager::dispatchKeyEvent::Found Key that is not pressed, " + "released or typed. What is it? " + e.getID()); } return true; } return false; } }