1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17/**
18 * @author Dmitry A. Durnev, Michael Danilov, Pavel Dolgov
19 * @version $Revision$
20 */
21package java.awt;
22
23import java.awt.event.MouseEvent;
24import java.awt.event.MouseListener;
25import java.awt.event.MouseMotionListener;
26import java.awt.event.MouseWheelEvent;
27import java.awt.event.MouseWheelListener;
28import java.awt.Dispatcher.MouseGrabManager;
29import java.util.EventListener;
30
31import org.apache.harmony.awt.wtk.NativeEvent;
32import org.apache.harmony.awt.wtk.NativeWindow;
33
34
35class MouseDispatcher {
36
37    // Fields for synthetic mouse click events generation
38    private static final int clickDelta = 5;
39    private final long[] lastPressTime = new long[] {0l, 0l, 0l};
40    private final Point[] lastPressPos = new Point[] {null, null, null};
41    private final boolean[] buttonPressed = new boolean[] {false, false, false};
42    private final int[] clickCount = new int[] {0, 0, 0};
43
44    // Fields for mouse entered/exited support
45    private Component lastUnderPointer = null;
46    private final Point lastScreenPos = new Point(-1, -1);
47
48    // Fields for redundant mouse moved/dragged filtering
49    private Component lastUnderMotion = null;
50    private Point lastLocalPos = new Point(-1, -1);
51
52    private final MouseGrabManager mouseGrabManager;
53    private final Toolkit toolkit;
54
55    static Point convertPoint(Component src, int x, int y, Component dest) {
56        Point srcPoint = getAbsLocation(src);
57        Point destPoint = getAbsLocation(dest);
58
59        return new Point(x + (srcPoint.x - destPoint.x),
60                         y + (srcPoint.y - destPoint.y));
61    }
62
63    static Point convertPoint(Component src, Point p, Component dst) {
64        return convertPoint(src, p.x, p.y, dst);
65    }
66
67    private static Point getAbsLocation(Component comp) {
68        Point location = new Point(0, 0);
69// BEGIN android-changed: AWT components not supported
70//        for (Component parent = comp; parent != null; parent = parent.parent) {
71//            Point parentPos = (parent instanceof EmbeddedWindow ?
72//                               parent.getNativeWindow().getScreenPos() :
73//                               parent.getLocation());
74//
75//            location.translate(parentPos.x, parentPos.y);
76//
77//            if (parent instanceof Window) {
78//                break;
79//            }
80//        }
81// END android-changed
82
83        return location;
84    }
85
86    MouseDispatcher(MouseGrabManager mouseGrabManager,
87                    Toolkit toolkit) {
88        this.mouseGrabManager = mouseGrabManager;
89        this.toolkit = toolkit;
90    }
91
92    Point getPointerPos() {
93        return lastScreenPos;
94    }
95
96    boolean dispatch(Component src, NativeEvent event) {
97        int id = event.getEventId();
98
99        lastScreenPos.setLocation(event.getScreenPos());
100        checkMouseEnterExit(event.getInputModifiers(), event.getTime());
101
102        if (id == MouseEvent.MOUSE_WHEEL) {
103// BEGIN android-changed: AWT components not supported
104//            dispatchWheelEvent(src, event);
105// END android-changed
106        } else if ((id != MouseEvent.MOUSE_ENTERED) &&
107                   (id != MouseEvent.MOUSE_EXITED)) {
108            PointerInfo info = new PointerInfo(src, event.getLocalPos());
109
110            mouseGrabManager.preprocessEvent(event);
111            findEventSource(info);
112            if ((id == MouseEvent.MOUSE_PRESSED) ||
113                (id == MouseEvent.MOUSE_RELEASED)) {
114
115                dispatchButtonEvent(info, event);
116            } else if ((id == MouseEvent.MOUSE_MOVED) ||
117                       (id == MouseEvent.MOUSE_DRAGGED)) {
118
119                dispatchMotionEvent(info, event);
120            }
121        }
122
123        return false;
124    }
125
126    private void checkMouseEnterExit(int modifiers, long when) {
127// BEGIN android-changed: AWT components not supported
128//        PointerInfo info = findComponentUnderPointer();
129//        Component curUnderPointer =
130//                propagateEvent(info, AWTEvent.MOUSE_EVENT_MASK,
131//                               MouseListener.class, false).src;
132//
133//        if (curUnderPointer != lastUnderPointer) {
134//            Point pos = info.position;
135//            if ((lastUnderPointer != null) &&
136//                 lastUnderPointer.isMouseExitedExpected()) {
137//
138//                Point exitPos = convertPoint(null, lastScreenPos.x,
139//                                             lastScreenPos.y, lastUnderPointer);
140//
141//                postMouseEnterExit(MouseEvent.MOUSE_EXITED, modifiers, when,
142//                                   exitPos.x, exitPos.y, lastUnderPointer);
143//            }
144//            setCursor(curUnderPointer);
145//            if (curUnderPointer != null) {
146//                postMouseEnterExit(MouseEvent.MOUSE_ENTERED, modifiers, when,
147//                                   pos.x, pos.y, curUnderPointer);
148//            }
149//            lastUnderPointer = curUnderPointer;
150//        }
151// END android-changed
152    }
153
154    private void setCursor(Component comp) {
155        if (comp == null) {
156            return;
157        }
158        Component grabOwner = mouseGrabManager.getSyntheticGrabOwner();
159        Component cursorComp = ((grabOwner != null) &&
160                                 grabOwner.isShowing() ? grabOwner : comp);
161        cursorComp.setCursor();
162    }
163
164    private void postMouseEnterExit(int id, int mod, long when,
165                                    int x, int y, Component comp) {
166        if (comp.isIndirectlyEnabled()) {
167            toolkit.getSystemEventQueueImpl().postEvent(
168                    new MouseEvent(comp, id, when, mod, x, y, 0, false));
169            comp.setMouseExitedExpected(id == MouseEvent.MOUSE_ENTERED);
170        } else {
171            comp.setMouseExitedExpected(false);
172        }
173    }
174
175 // BEGIN android-changed: AWT components not supported
176//    private PointerInfo findComponentUnderPointer() {
177//        NativeWindow nativeWindow = toolkit.getWindowFactory().
178//        getWindowFromPoint(lastScreenPos);
179//
180//        if (nativeWindow != null) {
181//            Component comp = toolkit.getComponentById(nativeWindow.getId());
182//
183//            if (comp != null) {
184//                Window window = comp.getWindowAncestor();
185//                Point pointerPos = convertPoint(null, lastScreenPos.x,
186//                                                lastScreenPos.y, window);
187//
188//                if (window.getClient().contains(pointerPos)) {
189//                    PointerInfo info = new PointerInfo(window, pointerPos);
190//
191//                    fall2Child(info);
192//
193//                    return info;
194//                }
195//            }
196//        }
197//
198//        return new PointerInfo(null, null);
199//    }
200// END android-changed
201
202    private void findEventSource(PointerInfo info) {
203        Component grabOwner = mouseGrabManager.getSyntheticGrabOwner();
204
205        if (grabOwner != null && grabOwner.isShowing()) {
206            info.position = convertPoint(info.src, info.position, grabOwner);
207            info.src = grabOwner;
208        } else {
209            //???AWT: rise2TopLevel(info);
210            //???AWT: fall2Child(info);
211        }
212    }
213
214 // BEGIN android-changed: AWT components not supported
215//    private void rise2TopLevel(PointerInfo info) {
216//        while (!(info.src instanceof Window)) {
217//            info.position.translate(info.src.x, info.src.y);
218//            info.src = info.src.parent;
219//        }
220//    }
221//
222//    private void fall2Child(PointerInfo info) {
223//        Insets insets = info.src.getInsets();
224//
225//        final Point pos = info.position;
226//        final int x = pos.x;
227//        final int y = pos.y;
228//        if ((x >= insets.left) && (y >= insets.top) &&
229//                (x < (info.src.w - insets.right)) &&
230//                (y < (info.src.h - insets.bottom)))
231//        {
232//            Component[] children = ((Container) info.src).getComponents();
233//
234//            for (Component child : children) {
235//                if (child.isShowing()) {
236//                    if (child.contains(x - child.getX(),
237//                            y - child.getY()))
238//                    {
239//                        info.src = child;
240//                        pos.translate(-child.x, -child.y);
241//
242//                        if (child instanceof Container) {
243//                            fall2Child(info);
244//                        }
245//
246//                        return;
247//                    }
248//                }
249//            }
250//        }
251//    }
252// END android-changed
253
254    private void dispatchButtonEvent(PointerInfo info, NativeEvent event) {
255        int button = event.getMouseButton();
256        long time = event.getTime();
257        int id = event.getEventId();
258        int index = button - 1;
259        boolean clickRequired = false;
260
261        propagateEvent(info, AWTEvent.MOUSE_EVENT_MASK,
262                       MouseListener.class, false);
263        if (id == MouseEvent.MOUSE_PRESSED) {
264            int clickInterval = toolkit.dispatcher.clickInterval;
265            mouseGrabManager.onMousePressed(info.src);
266            buttonPressed[index] = true;
267            clickCount[index] = (!deltaExceeded(index, info) &&
268                    ((time - lastPressTime[index]) <= clickInterval)) ?
269                    clickCount[index] + 1 : 1;
270            lastPressTime[index] = time;
271            lastPressPos[index] = info.position;
272        } else {
273            mouseGrabManager.onMouseReleased(info.src);
274            // set cursor back on synthetic mouse grab end:
275// BEGIN android-changed: AWT components not supported
276//            setCursor(findComponentUnderPointer().src);
277// END android-changed
278            if (buttonPressed[index]) {
279                buttonPressed[index] = false;
280                clickRequired = !deltaExceeded(index, info);
281            } else {
282                clickCount[index] = 0;
283            }
284        }
285        if (info.src.isIndirectlyEnabled()) {
286            final Point pos = info.position;
287            final int mod = event.getInputModifiers();
288            toolkit.getSystemEventQueueImpl().postEvent(
289                            new MouseEvent(info.src, id, time, mod, pos.x,
290                            pos.y, clickCount[index],
291                            event.getTrigger(), button));
292            if (clickRequired) {
293                toolkit.getSystemEventQueueImpl().postEvent(
294                            new MouseEvent(info.src,
295                            MouseEvent.MOUSE_CLICKED,
296                            time, mod, pos.x, pos.y,
297                            clickCount[index], false,
298                            button));
299            }
300        }
301    }
302
303    private boolean deltaExceeded(int index, PointerInfo info) {
304        final Point lastPos = lastPressPos[index];
305        if (lastPos == null) {
306            return true;
307        }
308        return ((Math.abs(lastPos.x - info.position.x) > clickDelta) ||
309                (Math.abs(lastPos.y - info.position.y) > clickDelta));
310    }
311
312    private void dispatchMotionEvent(PointerInfo info, NativeEvent event) {
313        propagateEvent(info, AWTEvent.MOUSE_MOTION_EVENT_MASK,
314                       MouseMotionListener.class, false);
315        final Point pos = info.position;
316        if ((lastUnderMotion != info.src) ||
317            !lastLocalPos.equals(pos)) {
318
319            lastUnderMotion = info.src;
320            lastLocalPos = pos;
321
322            if (info.src.isIndirectlyEnabled()) {
323                toolkit.getSystemEventQueueImpl().postEvent(
324                            new MouseEvent(info.src, event.getEventId(),
325                            event.getTime(),
326                            event.getInputModifiers(),
327                            pos.x, pos.y, 0, false));
328            }
329        }
330    }
331
332    MouseWheelEvent createWheelEvent(Component src, NativeEvent event,
333                                     Point where) {
334
335        Integer scrollAmountProperty =
336            (Integer)toolkit.getDesktopProperty("awt.wheelScrollingSize"); //$NON-NLS-1$
337        int amount = 1;
338        int type = MouseWheelEvent.WHEEL_UNIT_SCROLL;
339
340        if (scrollAmountProperty != null) {
341            amount = scrollAmountProperty.intValue();
342            if (amount == -1) {
343                type = MouseWheelEvent.WHEEL_BLOCK_SCROLL;
344                amount = 1;
345            }
346        }
347        return new MouseWheelEvent(src, event.getEventId(),
348                event.getTime(), event.getInputModifiers(),
349                where.x, where.y, 0, false, type, amount,
350                event.getWheelRotation());
351    }
352
353// BEGIN android-changed: AWT components not supported
354//    private void dispatchWheelEvent(Component src, NativeEvent event) {
355//        PointerInfo info = findComponentUnderPointer();
356//
357//        if (info.src == null) {
358//            info.src = src;
359//            info.position = event.getLocalPos();
360//        }
361//
362//        propagateEvent(info, AWTEvent.MOUSE_WHEEL_EVENT_MASK,
363//                       MouseWheelListener.class, true);
364//        if ((info.src != null) && info.src.isIndirectlyEnabled()) {
365//            toolkit.getSystemEventQueueImpl().postEvent(
366//                    createWheelEvent(info.src, event, info.position));
367//        }
368//    }
369// END android-changed
370
371    private PointerInfo propagateEvent(PointerInfo info, long mask,
372                                       Class<? extends EventListener> type, boolean pierceHW) {
373        Component src = info.src;
374        while ((src != null) &&
375               (src.isLightweight() || pierceHW) &&
376              !(src.isMouseEventEnabled(mask) ||
377               (src.getListeners(type).length > 0))) {
378
379            info.position.translate(src.x, src.y);
380// BEGIN android-changed: AWT components not supported
381//            src = src.parent;
382// END android-changed
383            info.src = src;
384        }
385
386        return info;
387    }
388
389// BEGIN android-changed: AWT components not supported
390//    Window findWindowAt(Point p) {
391//        NativeWindow nativeWindow =
392//            toolkit.getWindowFactory().getWindowFromPoint(p);
393//
394//        Window window = null;
395//        if (nativeWindow != null) {
396//            Component comp = toolkit.getComponentById(nativeWindow.getId());
397//
398//            if (comp != null) {
399//                window = comp.getWindowAncestor();
400//            }
401//        }
402//        return window;
403//    }
404// END android-changed
405
406    private class PointerInfo {
407
408        Component src;
409        Point position;
410
411        PointerInfo(Component src, Point position) {
412            this.src = src;
413            this.position = position;
414        }
415
416    }
417
418}
419