/* * $Id$ * * Copyright (C) 2003-2015 JNode.org * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; If not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package org.jnode.awt; import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.util.Collection; import java.util.HashSet; import java.security.AccessController; import java.security.PrivilegedAction; import org.apache.log4j.Logger; import org.jnode.driver.ApiNotFoundException; import org.jnode.driver.Device; import org.jnode.driver.DeviceUtils; import org.jnode.driver.DeviceListener; import org.jnode.driver.DeviceManager; import org.jnode.driver.input.PointerAPI; import org.jnode.driver.input.PointerEvent; import org.jnode.driver.input.PointerListener; import org.jnode.driver.video.HardwareCursor; import org.jnode.driver.video.HardwareCursorAPI; import javax.naming.NameNotFoundException; /** * @author Ewout Prangsma (epr@users.sourceforge.net) * @author Levente S\u00e1ntha */ public class MouseHandler implements PointerListener { //todo refactor this pattern to be generally available in JNode for AWT and text consoles as well private static class PointerAPIHandler implements PointerAPI, DeviceListener { private Collection<PointerAPI> pointerList = new HashSet<PointerAPI>(); private Collection<PointerListener> listenerList = new HashSet<PointerListener>(); PointerAPIHandler() { try { DeviceManager dm = DeviceUtils.getDeviceManager(); dm.addListener(this); for (Device device : dm.getDevicesByAPI(PointerAPI.class)) { try { pointerList.add(device.getAPI(PointerAPI.class)); } catch (ApiNotFoundException anfe) { //ignore } } } catch (NameNotFoundException nfe) { nfe.printStackTrace(); } } public void addPointerListener(PointerListener listener) { listenerList.add(listener); for (PointerAPI api : pointerList) { api.addPointerListener(listener); } } public void removePointerListener(PointerListener listener) { listenerList.remove(listener); for (PointerAPI api : pointerList) { api.removePointerListener(listener); } } public void setPreferredListener(PointerListener listener) { for (PointerAPI api : pointerList) { api.setPreferredListener(listener); } } public void deviceStarted(Device device) { if (device.implementsAPI(PointerAPI.class)) { try { PointerAPI api = device.getAPI(PointerAPI.class); pointerList.add(api); for (PointerListener listener : listenerList) { api.addPointerListener(listener); } } catch (ApiNotFoundException anfe) { //ignore } } } public void deviceStop(Device device) { if (device.implementsAPI(PointerAPI.class)) { try { PointerAPI api = device.getAPI(PointerAPI.class); pointerList.remove(api); for (PointerListener listener : listenerList) { api.removePointerListener(listener); } } catch (ApiNotFoundException anfe) { //ignore } } } boolean hasPointer() { return !pointerList.isEmpty(); } } private final PointerAPI pointerAPI; private static final int[] BUTTON_MASK = { PointerEvent.BUTTON_LEFT, PointerEvent.BUTTON_RIGHT, PointerEvent.BUTTON_MIDDLE }; private static final int[] BUTTON_NUMBER = {1, 2, 3}; private static int CLICK_REPEAT_DURATION = 400; /** * Queue where to post my events */ private final EventQueue eventQueue; private final HardwareCursorAPI hwCursor; private final KeyboardHandler keyboardHandler; private final Dimension screenSize; private int lastButtons = 0; private final boolean[] buttonPressed = new boolean[3]; private final int[] buttonClickCount = new int[3]; private final long[] buttonClickTime = new long[3]; private boolean postClicked = false; private static final Logger log = Logger.getLogger(MouseHandler.class); private Component lastSource; private Component dragSource; private int x; private int y; /** * Create a new instance. * * @param fbDevice a frame buffer device * @param screenSize screen size * @param eventQueue the event queue instance * @param keyboardHandler keyboard handler */ public MouseHandler(Device fbDevice, Dimension screenSize, EventQueue eventQueue, KeyboardHandler keyboardHandler) { this.eventQueue = eventQueue; HardwareCursorAPI hwCursor = null; try { hwCursor = fbDevice.getAPI(HardwareCursorAPI.class); } catch (ApiNotFoundException ex) { log.info("No hardware-cursor found on device " + fbDevice.getId()); } this.keyboardHandler = keyboardHandler; this.hwCursor = hwCursor; this.screenSize = screenSize; if (hwCursor != null) { hwCursor.setCursorImage(JNodeCursors.ARROW); hwCursor.setCursorVisible(true); hwCursor.setCursorPosition(0, 0); } this.pointerAPI = new PointerAPIHandler(); pointerAPI.addPointerListener(this); AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { MouseHandler.this.pointerAPI.setPreferredListener(MouseHandler.this); return null; } }); } void setCursorImage(HardwareCursor ci) { if (hwCursor != null) { synchronized (this) { hwCursor.setCursorImage(ci); hwCursor.setCursorVisible(true); } } } /** * Close this handler */ public void close() { if (pointerAPI != null) { pointerAPI.removePointerListener(this); } if (hwCursor != null) { // hide the cursor so that it won't stay displayed // if we stay in graphic mode hwCursor.setCursorVisible(false); } } /** * Move the mouse pointer to absolute coordinates (x, y). * * @param x the destination x coordinate * @param y the destination y coordinate * @see java.awt.peer.RobotPeer#mouseMove(int, int) */ void mouseMove(int x, int y) { // buttons and z unchanged (true means absolute values) pointerStateChanged(lastButtons, x, y, 0, true); } /** * Press one or more mouse buttons. * * @param buttons the buttons to press; a bitmask of one or more of * these {@link java.awt.event.InputEvent} fields: * <p/> * <ul> * <li>BUTTON1_MASK</li> * <li>BUTTON2_MASK</li> * <li>BUTTON3_MASK</li> * </ul> * @see java.awt.peer.RobotPeer#mousePress(int) */ void mousePress(int buttons) { // x, y and z unchanged (false means relative values) pointerStateChanged(buttons | lastButtons, 0, 0, 0, false); } /** * Release one or more mouse buttons. * * @param buttons the buttons to release; a bitmask of one or more * of these {@link java.awt.event.InputEvent} fields: * <p/> * <ul> * <li>BUTTON1_MASK</li> * <li>BUTTON2_MASK</li> * <li>BUTTON3_MASK</li> * </ul> * @see java.awt.peer.RobotPeer#mouseRelease(int) */ void mouseRelease(int buttons) { // x, y and z unchanged (false means relative values) pointerStateChanged(lastButtons & (~buttons), 0, 0, 0, false); } /** * Rotate the mouse scroll wheel. * * @param wheelAmt number of steps to rotate mouse wheel. negative * to rotate wheel up (away from the user), positive to rotate wheel * down (toward the user). So, this is a relative value. * @see java.awt.peer.RobotPeer#mouseWheel(int) */ void mouseWheel(int wheelAmt) { // buttons, x and y unchanged (false means relative values) pointerStateChanged(lastButtons, 0, 0, wheelAmt, false); } int getX() { return x; } int getY() { return y; } void setCursor(int x, int y) { this.x = x; this.y = y; if (hwCursor != null) hwCursor.setCursorPosition(x, y); } public void pointerStateChanged(PointerEvent event) { pointerStateChanged(event.getButtons(), event.getX(), event.getY(), event.getZ(), event.isAbsolute()); event.consume(); } /** * @param buttons a vlaue encoding the state of mouse buttons * @param newX a relative or absolute value for x coordinate * @param newY a relative or absolute value for y coordinate * @param newZ a relative or absolute value for wheel mouse * @param absolute are newX, newY and newZ relative or absolute values ? */ void pointerStateChanged(int buttons, int newX, int newY, int newZ, boolean absolute) { long time = System.currentTimeMillis(); final int newAbsX = absolute ? newX : x + newX; final int newAbsY = absolute ? newY : y + newY; x = Math.min(screenSize.width - 1, Math.max(0, newAbsX)); y = Math.min(screenSize.height - 1, Math.max(0, newAbsY)); if (hwCursor != null) hwCursor.setCursorPosition(x, y); lastButtons = buttons; Component source = findSource(); if (source == null || !source.isShowing()) return; if (newZ != 0) { postEvent(source, MouseEvent.MOUSE_WHEEL, time, 0, MouseEvent.NOBUTTON, newZ); } boolean eventFired = false; for (int i = 0; i < 3; i++) { if (time - buttonClickTime[i] > CLICK_REPEAT_DURATION) { buttonClickCount[i] = 0; } if ((buttons & BUTTON_MASK[i]) != 0) { if (!buttonPressed[i]) { buttonClickTime[i] = time; buttonClickCount[i] += 1; postEvent(source, MouseEvent.MOUSE_PRESSED, time, buttonClickCount[i], BUTTON_NUMBER[i], 0); dragSource = source; buttonPressed[i] = true; eventFired = true; postClicked = true; break; } } else if (buttonPressed[i]) { buttonClickTime[i] = time; postEvent(dragSource, MouseEvent.MOUSE_RELEASED, time, buttonClickCount[i], BUTTON_NUMBER[i], 0); if (postClicked || buttonClickCount[i] > 0) { postEvent(source, MouseEvent.MOUSE_CLICKED, time, buttonClickCount[i], BUTTON_NUMBER[i], 0); postClicked = false; } buttonPressed[i] = false; eventFired = true; break; } } if (source != lastSource) { if (lastSource != null) { // Notify mouse exited postEvent(lastSource, MouseEvent.MOUSE_EXITED, time, 0, MouseEvent.NOBUTTON, 0); if (lastSource.isCursorSet()) lastSource.setCursor(lastSource.getCursor()); } // Notify mouse entered postEvent(source, MouseEvent.MOUSE_ENTERED, time, 0, MouseEvent.NOBUTTON, 0); if (source.isCursorSet()) source.setCursor(source.getCursor()); eventFired = true; postClicked = false; } if (!eventFired) { if (buttonPressed[0]) { postEvent(dragSource, MouseEvent.MOUSE_DRAGGED, time, buttonClickCount[0], MouseEvent.BUTTON1, 0); } else if (buttonPressed[1]) { postEvent(dragSource, MouseEvent.MOUSE_DRAGGED, time, buttonClickCount[1], MouseEvent.BUTTON2, 0); } else if (buttonPressed[2]) { postEvent(dragSource, MouseEvent.MOUSE_DRAGGED, time, buttonClickCount[2], MouseEvent.BUTTON3, 0); } else { postEvent(source, MouseEvent.MOUSE_MOVED, time, 0, MouseEvent.NOBUTTON, 0); } postClicked = false; } lastSource = source; } /** * Post a mouse event with the given properties. * * @param source the source component of the event * @param id the event identifier * @param time the time of occurence of this event * @param clickCount the number of mouse clicks * @param button the mouse button in action * @param wheelAmt the amount that the mouse wheel was rotated */ private void postEvent(Component source, int id, long time, int clickCount, int button, int wheelAmt) { if (!source.isShowing()) return; // final Window w = SwingUtilities.getWindowAncestor(source); // Point pwo; // if (w != null && w.isShowing()) { // pwo = w.getLocationOnScreen(); // } else { // pwo = new Point(0, 0); // } final Point p = source.getLocationOnScreen(); final boolean popupTrigger = (button == MouseEvent.BUTTON2); final int ex = x - p.x; // - pwo.x; final int ey = y - p.y; // - pwo.y; final int modifiers = getModifiers(); MouseEvent event; if (id == MouseEvent.MOUSE_WHEEL) { event = new MouseWheelEvent(source, id, time, modifiers, ex, ey, 1, popupTrigger, MouseWheelEvent.WHEEL_UNIT_SCROLL, wheelAmt, //TODO check what to put here wheelAmt); //TODO check what to put here } else { event = new MouseEvent(source, id, time, modifiers, ex, ey, clickCount, popupTrigger, button); if (id == MouseEvent.MOUSE_PRESSED) { ((JNodeToolkit) Toolkit.getDefaultToolkit()).activateWindow(source); } } //debug /* if ( id == MouseEvent.MOUSE_PRESSED || id == MouseEvent.MOUSE_RELEASED || id == MouseEvent.MOUSE_CLICKED || id == MouseEvent.MOUSE_ENTERED || id == MouseEvent.MOUSE_EXITED ) { org.jnode.vm.Unsafe.debug(event.toString()+"\n"); org.jnode.vm.Unsafe.debug("x="+ x + " y=" + y +" ex="+ ex + " ey=" + ey + " p.x=" + p.x + " p.y=" + p.y +"\n"); } */ //todo enable this for isolate support in the gui //JNodeToolkit.postToTarget(event, source); eventQueue.postEvent(event); } private Component findSource() { final JNodeToolkit tk = (JNodeToolkit) Toolkit.getDefaultToolkit(); Component source = tk.getTopComponentAt(x, y); if ((source != null) && source.isShowing()) { return source; } else { return null; } } private int getModifiers() { int modifiers = 0; if (buttonPressed[0]) modifiers |= MouseEvent.BUTTON1_MASK; if (buttonPressed[1]) modifiers |= MouseEvent.BUTTON2_MASK; if (buttonPressed[2]) modifiers |= MouseEvent.BUTTON3_MASK; return modifiers | keyboardHandler.getModifiers(); } }