1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33package com.jme3.input.awt;
34
35import com.jme3.input.MouseInput;
36import com.jme3.input.RawInputListener;
37import com.jme3.input.event.MouseButtonEvent;
38import com.jme3.input.event.MouseMotionEvent;
39import java.awt.*;
40import java.awt.event.*;
41import java.awt.image.BufferedImage;
42import java.util.ArrayList;
43import java.util.logging.Level;
44import java.util.logging.Logger;
45import javax.swing.SwingUtilities;
46
47/**
48 * <code>AwtMouseInput</code>
49 *
50 * @author Joshua Slack
51 * @author MHenze (cylab)
52 *
53 * @version $Revision$
54 */
55public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListener, MouseMotionListener {
56
57    public static int WHEEL_AMP = 40;   // arbitrary...  Java's mouse wheel seems to report something a lot lower than lwjgl's
58
59    private static final Logger logger = Logger.getLogger(AwtMouseInput.class.getName());
60
61    private boolean visible = true;
62
63    private RawInputListener listener;
64
65    private Component component;
66
67    private final ArrayList<MouseButtonEvent> eventQueue = new ArrayList<MouseButtonEvent>();
68    private final ArrayList<MouseButtonEvent> eventQueueCopy = new ArrayList<MouseButtonEvent>();
69
70    private int lastEventX;
71    private int lastEventY;
72    private int lastEventWheel;
73
74    private Cursor transparentCursor;
75
76    private Robot robot;
77    private int wheelPos;
78    private Point location;
79    private Point centerLocation;
80    private Point centerLocationOnScreen;
81    private Point lastKnownLocation;
82    private boolean isRecentering;
83    private boolean cursorMoved;
84    private int eventsSinceRecenter;
85
86    public AwtMouseInput() {
87        location = new Point();
88        centerLocation = new Point();
89        centerLocationOnScreen = new Point();
90        lastKnownLocation = new Point();
91
92        try{
93            robot = new Robot();
94        }catch (java.awt.AWTException e){
95            logger.log(Level.SEVERE, "Could not create a robot, so the mouse cannot be grabbed! ", e);
96        }
97    }
98
99    public void setInputSource(Component comp){
100        if (component != null){
101            component.removeMouseListener(this);
102            component.removeMouseMotionListener(this);
103            component.removeMouseWheelListener(this);
104
105            eventQueue.clear();
106
107            wheelPos = 0;
108            isRecentering = false;
109            eventsSinceRecenter = 0;
110            lastEventX = 0;
111            lastEventY = 0;
112            lastEventWheel = 0;
113            location = new Point();
114            centerLocation = new Point();
115            centerLocationOnScreen = new Point();
116            lastKnownLocation = new Point();
117        }
118
119        component = comp;
120        component.addMouseListener(this);
121        component.addMouseMotionListener(this);
122        component.addMouseWheelListener(this);
123    }
124
125    public void initialize() {
126    }
127
128    public void destroy() {
129    }
130
131    public boolean isInitialized() {
132        return true;
133    }
134
135    public void setInputListener(RawInputListener listener){
136        this.listener = listener;
137    }
138
139    public long getInputTimeNanos() {
140        return System.nanoTime();
141    }
142
143    public void setCursorVisible(boolean visible){
144        if (this.visible != visible){
145
146            lastKnownLocation.x = lastKnownLocation.y = 0;
147
148            this.visible = visible;
149            final boolean newVisible = visible;
150            SwingUtilities.invokeLater(new Runnable() {
151                public void run() {
152                    component.setCursor(newVisible ? null : getTransparentCursor());
153                    if (!newVisible)
154                        recenterMouse(component);
155                }
156            });
157        }
158    }
159
160    public void update() {
161        if (cursorMoved){
162            int newX = location.x;
163            int newY = location.y;
164            int newWheel = wheelPos;
165
166            // invert DY
167            int actualX = lastKnownLocation.x;
168            int actualY = component.getHeight() - lastKnownLocation.y;
169            MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY,
170                                                        newX - lastEventX,
171                                                        lastEventY - newY,
172                                                        wheelPos, lastEventWheel - wheelPos);
173            listener.onMouseMotionEvent(evt);
174
175            lastEventX = newX;
176            lastEventY = newY;
177            lastEventWheel = newWheel;
178
179            cursorMoved = false;
180        }
181
182        synchronized (eventQueue){
183            eventQueueCopy.clear();
184            eventQueueCopy.addAll(eventQueue);
185            eventQueue.clear();
186        }
187
188        int size = eventQueueCopy.size();
189        for (int i = 0; i < size; i++){
190            listener.onMouseButtonEvent(eventQueueCopy.get(i));
191        }
192    }
193
194    private Cursor getTransparentCursor() {
195        if (transparentCursor == null){
196            BufferedImage cursorImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
197            cursorImage.setRGB(0, 0, 0);
198            transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImage, new Point(0, 0), "empty cursor");
199        }
200        return transparentCursor;
201    }
202
203//	public void setHardwareCursor(URL file, int xHotspot, int yHotspot) {
204//	    //Create the image from the provided url
205//	    java.awt.Image cursorImage = new ImageIcon( file ).getImage( );
206//	    //Create a custom cursor with this image
207//	    opaqueCursor = Toolkit.getDefaultToolkit().createCustomCursor( cursorImage , new Point( xHotspot , yHotspot ) , "custom cursor" );
208//	    //Use this cursor
209//	    setCursorVisible( isCursorVisible );
210//	}
211
212
213    public int getButtonCount() {
214        return 3;
215    }
216
217    public void mouseClicked(MouseEvent arg0) {
218//        MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false);
219//        listener.onMouseButtonEvent(evt);
220    }
221
222    public void mousePressed(MouseEvent arg0) {
223        MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), true, arg0.getX(), arg0.getY());
224        evt.setTime(arg0.getWhen());
225        synchronized (eventQueue){
226            eventQueue.add(evt);
227        }
228    }
229
230    public void mouseReleased(MouseEvent arg0) {
231        MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false, arg0.getX(), arg0.getY());
232        evt.setTime(arg0.getWhen());
233        synchronized (eventQueue){
234            eventQueue.add(evt);
235        }
236    }
237
238    public void mouseEntered(MouseEvent arg0) {
239        if (!visible)
240            recenterMouse(arg0.getComponent());
241    }
242
243    public void mouseExited(MouseEvent arg0) {
244        if (!visible)
245            recenterMouse(arg0.getComponent());
246    }
247
248    public void mouseWheelMoved(MouseWheelEvent arg0) {
249        int dwheel = arg0.getUnitsToScroll();
250        wheelPos += dwheel * WHEEL_AMP;
251        cursorMoved = true;
252    }
253
254    public void mouseDragged(MouseEvent arg0) {
255        mouseMoved(arg0);
256    }
257
258    public void mouseMoved(MouseEvent arg0) {
259        if (isRecentering) {
260            // MHenze (cylab) Fix Issue 35:
261            // As long as the MouseInput is in recentering mode, nothing is done until the mouse is entered in the component
262            // by the events generated by the robot. If this happens, the last known location is resetted.
263            if ((centerLocation.x == arg0.getX() && centerLocation.y == arg0.getY()) || eventsSinceRecenter++ == 5) {
264                lastKnownLocation.x = arg0.getX();
265                lastKnownLocation.y = arg0.getY();
266                isRecentering = false;
267            }
268        } else {
269            // MHenze (cylab) Fix Issue 35:
270            // Compute the delta and absolute coordinates and recenter the mouse if necessary
271            int dx = arg0.getX() - lastKnownLocation.x;
272            int dy = arg0.getY() - lastKnownLocation.y;
273            location.x += dx;
274            location.y += dy;
275            if (!visible) {
276                recenterMouse(arg0.getComponent());
277            }
278            lastKnownLocation.x = arg0.getX();
279            lastKnownLocation.y = arg0.getY();
280
281            cursorMoved = true;
282        }
283    }
284
285    // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse
286    private void recenterMouse(final Component component) {
287        if (robot != null) {
288            eventsSinceRecenter = 0;
289            isRecentering = true;
290            centerLocation.setLocation(component.getWidth()/2, component.getHeight()/2);
291            centerLocationOnScreen.setLocation(centerLocation);
292            SwingUtilities.convertPointToScreen(centerLocationOnScreen, component);
293            robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y);
294        }
295    }
296
297    private int getJMEButtonIndex( MouseEvent arg0 ) {
298        int index;
299        switch (arg0.getButton()) {
300            default:
301            case MouseEvent.BUTTON1: //left
302                index = MouseInput.BUTTON_LEFT;
303                break;
304            case MouseEvent.BUTTON2: //middle
305                index = MouseInput.BUTTON_MIDDLE;
306                break;
307            case MouseEvent.BUTTON3: //right
308                index = MouseInput.BUTTON_RIGHT;
309                break;
310        }
311        return index;
312    }
313}
314