# HG changeset patch
# User Alexander Nyßen <alexander@nyssen.org>
# Date 1471009938 -7200
#      Fri Aug 12 15:52:18 2016 +0200
# Node ID 94bbf1e9224d19c6594227ddcc2769c8a35cc253
# Parent  b52e1e2cd728466f845d44c0edbdc6a874cf7b81
[8161282] Ensure FXCanvas properly forwards horizontal mouse wheel events.

- Extended EmbeddedSceneInterface and EmbeddedScene to support propagation of scroll events.
- Extended FXCanvas to handle SWT.MouseHorizontalWheel events in addition to SWT.MouseVerticalWheel (= SWT.MouseWheel) events. Separated handling of mouse wheel events from handling of mouse events.
- Adjusted JFXPanel to use separate method to forward scroll event to EmbeddedScene.
- Added manual test for forwarding of scroll events.

diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/AbstractEvents.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/AbstractEvents.java
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/AbstractEvents.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/AbstractEvents.java
@@ -48,7 +48,8 @@
     public final static int MOUSEEVENT_EXITED = 4;
     public final static int MOUSEEVENT_MOVED = 5;
     public final static int MOUSEEVENT_DRAGGED = 6;
-    public final static int MOUSEEVENT_WHEEL = 7;
+    public final static int MOUSEEVENT_VERTICAL_WHEEL = 7;
+    public final static int MOUSEEVENT_HORIZONTAL_WHEEL = 8;
 
     public final static int MOUSEEVENT_NONE_BUTTON = 0;
     public final static int MOUSEEVENT_PRIMARY_BUTTON = 1;
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/embed/EmbeddedSceneInterface.java
@@ -71,7 +71,14 @@
                            boolean primaryBtnDown, boolean middleBtnDown, boolean secondaryBtnDown,
                            int x, int y, int xAbs, int yAbs,
                            boolean shift, boolean ctrl, boolean alt, boolean meta,
-                           int wheelRotation, boolean popupTrigger);
+                           boolean popupTrigger);
+    /*
+     * A notification about mouse wheel scroll events received by the host container;
+     */
+    public void scrollEvent(int type, double scrollX, double scrollY,
+                            double x, double y, double screenX, double screenY,
+                            boolean shift, boolean ctrl,
+                            boolean alt, boolean meta);
     /*
      * A notification about key event received by host container.
      */
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/EmbeddedScene.java
@@ -273,7 +273,7 @@
                            final boolean primaryBtnDown, final boolean middleBtnDown, final boolean secondaryBtnDown,
                            final int x, final int y, final int xAbs, final int yAbs,
                            final boolean shift, final boolean ctrl, final boolean alt, final boolean meta,
-                           final int wheelRotation, final boolean popupTrigger)
+                           final boolean popupTrigger)
     {
         Platform.runLater(() -> {
             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
@@ -282,24 +282,38 @@
                 }
                 // Click events are generated in Scene, so we don't expect them here
                 assert type != AbstractEvents.MOUSEEVENT_CLICKED;
-                if (type == AbstractEvents.MOUSEEVENT_WHEEL) {
-                    sceneListener.scrollEvent(ScrollEvent.SCROLL, 0, -wheelRotation, 0, 0, 40.0, 40.0,
-                            0, 0, 0, 0, 0,
-                            x, y, xAbs, yAbs, shift, ctrl, alt, meta, false, false);
-                } else {
-                    EventType<MouseEvent> eventType = AbstractEvents.mouseIDToFXEventID(type);
-                    sceneListener.mouseEvent(eventType, x, y, xAbs, yAbs,
+                EventType<MouseEvent> eventType = AbstractEvents.mouseIDToFXEventID(type);
+                sceneListener.mouseEvent(eventType, x, y, xAbs, yAbs,
                             AbstractEvents.mouseButtonToFXMouseButton(button),
                             popupTrigger, false, // do we know if it's synthesized? RT-20142
                             shift, ctrl, alt, meta,
                             primaryBtnDown, middleBtnDown, secondaryBtnDown);
-                }
                 return null;
             }, getAccessControlContext());
         });
     }
 
     @Override
+    public void scrollEvent(final int type, final double scrollX, final double scrollY,
+                            final double x, final double y,
+                            final double xAbs, final double yAbs,
+                            final boolean shift, final boolean ctrl, final boolean alt, final boolean meta) {
+        {
+            Platform.runLater(() -> {
+                AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
+                    if (sceneListener == null) {
+                        return null;
+                    }
+                    sceneListener.scrollEvent(ScrollEvent.SCROLL, scrollX, scrollY, 0, 0, 40.0, 40.0,
+                            0, 0, 0, 0, 0,
+                            x, y, xAbs, yAbs, shift, ctrl, alt, meta, false, false);
+                    return null;
+                }, getAccessControlContext());
+            });
+        }
+    }
+
+    @Override
     public void inputMethodEvent(final EventType<InputMethodEvent> type,
                                  final ObservableList<InputMethodTextRun> composed, final String committed,
                                  final int caretPosition) {
diff --git a/modules/javafx.swing/src/main/java/javafx/embed/swing/JFXPanel.java b/modules/javafx.swing/src/main/java/javafx/embed/swing/JFXPanel.java
--- a/modules/javafx.swing/src/main/java/javafx/embed/swing/JFXPanel.java
+++ b/modules/javafx.swing/src/main/java/javafx/embed/swing/JFXPanel.java
@@ -369,16 +369,27 @@
         if (e.getID() == MouseEvent.MOUSE_PRESSED || e.getID() == MouseEvent.MOUSE_RELEASED) {
             popupTrigger = e.isPopupTrigger();
         }
-        scenePeer.mouseEvent(
-                SwingEvents.mouseIDToEmbedMouseType(e.getID()),
-                SwingEvents.mouseButtonToEmbedMouseButton(e.getButton(), extModifiers),
-                primaryBtnDown, middleBtnDown, secondaryBtnDown,
-                e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(),
-                (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
-                (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
-                (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
-                (extModifiers & MouseEvent.META_DOWN_MASK) != 0,
-                SwingEvents.getWheelRotation(e), popupTrigger);
+
+        if(e.getID() == MouseEvent.MOUSE_WHEEL) {
+            scenePeer.scrollEvent(AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL,
+                    0, -SwingEvents.getWheelRotation(e),
+                    e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(),
+                    (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
+                    (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
+                    (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
+                    (extModifiers & MouseEvent.META_DOWN_MASK) != 0);
+        } else {
+            scenePeer.mouseEvent(
+                    SwingEvents.mouseIDToEmbedMouseType(e.getID()),
+                    SwingEvents.mouseButtonToEmbedMouseButton(e.getButton(), extModifiers),
+                    primaryBtnDown, middleBtnDown, secondaryBtnDown,
+                    e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(),
+                    (extModifiers & MouseEvent.SHIFT_DOWN_MASK) != 0,
+                    (extModifiers & MouseEvent.CTRL_DOWN_MASK) != 0,
+                    (extModifiers & MouseEvent.ALT_DOWN_MASK) != 0,
+                    (extModifiers & MouseEvent.META_DOWN_MASK) != 0,
+                    popupTrigger);
+        }
         if (e.isPopupTrigger()) {
             scenePeer.menuEvent(e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), false);
         }
diff --git a/modules/javafx.swing/src/main/java/javafx/embed/swing/SwingEvents.java b/modules/javafx.swing/src/main/java/javafx/embed/swing/SwingEvents.java
--- a/modules/javafx.swing/src/main/java/javafx/embed/swing/SwingEvents.java
+++ b/modules/javafx.swing/src/main/java/javafx/embed/swing/SwingEvents.java
@@ -56,8 +56,6 @@
                 return AbstractEvents.MOUSEEVENT_ENTERED;
             case MouseEvent.MOUSE_EXITED:
                 return AbstractEvents.MOUSEEVENT_EXITED;
-            case MouseWheelEvent.MOUSE_WHEEL:
-                return AbstractEvents.MOUSEEVENT_WHEEL;
         }
         return 0;
     }
diff --git a/modules/javafx.swt/src/main/java/javafx/embed/swt/FXCanvas.java b/modules/javafx.swt/src/main/java/javafx/embed/swt/FXCanvas.java
--- a/modules/javafx.swt/src/main/java/javafx/embed/swt/FXCanvas.java
+++ b/modules/javafx.swt/src/main/java/javafx/embed/swt/FXCanvas.java
@@ -89,12 +89,7 @@
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.RGB;
 import org.eclipse.swt.graphics.Rectangle;
-import org.eclipse.swt.widgets.Canvas;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Control;
-import org.eclipse.swt.widgets.Display;
-import org.eclipse.swt.widgets.Listener;
-import org.eclipse.swt.widgets.Shell;
+import org.eclipse.swt.widgets.*;
 
 /**
  * {@code FXCanvas} is a component to embed JavaFX content into
@@ -436,8 +431,11 @@
             }
         });
 
-        addMouseWheelListener(me -> {
-            FXCanvas.this.sendMouseEventToFX(me, AbstractEvents.MOUSEEVENT_WHEEL);
+        addListener(SWT.MouseVerticalWheel, e -> {
+            FXCanvas.this.sendScrollEventToFX(e, AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL);
+        });
+        addListener(SWT.MouseHorizontalWheel, e -> {
+            FXCanvas.this.sendScrollEventToFX(e, AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL);
         });
 
         addMouseTrackListener(new MouseTrackListener() {
@@ -646,10 +644,25 @@
                 me.x, me.y,
                 los.x, los.y,
                 shift, control, alt, meta,
-                SWTEvents.getWheelRotation(me, embedMouseType),
                 false);  // RT-32990: popup trigger not implemented
     }
 
+    private void sendScrollEventToFX(Event event, int type){
+        if (scenePeer == null) {
+            return;
+        }
+        Point los = toDisplay(event.x, event.y);
+        scenePeer.scrollEvent(type,
+                (type == AbstractEvents.MOUSEEVENT_HORIZONTAL_WHEEL) ? -SWTEvents.getWheelRotation(event) : 0,
+                (type == AbstractEvents.MOUSEEVENT_VERTICAL_WHEEL) ? -SWTEvents.getWheelRotation(event) : 0,
+                event.x, event.y,
+                los.x, los.y,
+                (event.stateMask & SWT.SHIFT) != 0,
+                (event.stateMask & SWT.CONTROL) != 0,
+                (event.stateMask & SWT.ALT) != 0,
+                (event.stateMask & SWT.COMMAND) != 0);
+    }
+
     private void sendKeyEventToFX(final KeyEvent e, int type) {
         if (scenePeer == null /*|| !isFxEnabled()*/) {
             return;
diff --git a/modules/javafx.swt/src/main/java/javafx/embed/swt/SWTEvents.java b/modules/javafx.swt/src/main/java/javafx/embed/swt/SWTEvents.java
--- a/modules/javafx.swt/src/main/java/javafx/embed/swt/SWTEvents.java
+++ b/modules/javafx.swt/src/main/java/javafx/embed/swt/SWTEvents.java
@@ -32,6 +32,8 @@
 
 //import com.sun.glass.events.KeyEvent;
 import com.sun.javafx.embed.AbstractEvents;
+import org.eclipse.swt.widgets.Event;
+
 import java.lang.reflect.InvocationTargetException;
 
 /**
@@ -58,8 +60,6 @@
                 return AbstractEvents.MOUSEEVENT_ENTERED;
             case MouseEvent.MOUSE_EXITED:
                 return AbstractEvents.MOUSEEVENT_EXITED;
-            case MouseWheelEvent.MOUSE_WHEEL:
-                return AbstractEvents.MOUSEEVENT_WHEEL;
         }
         return 0;
     }
@@ -73,33 +73,30 @@
         return AbstractEvents.MOUSEEVENT_NONE_BUTTON;
     }
 
-    static int getWheelRotation(MouseEvent e, int embedMouseType) {
+    static int getWheelRotation(Event e) {
         int divisor = 1;
-        if (embedMouseType == AbstractEvents.MOUSEEVENT_WHEEL) {
-            if ("win32".equals(SWT.getPlatform())) {
-                int [] linesToScroll = new int [1];
-                //OS.SystemParametersInfo (OS.SPI_GETWHEELSCROLLLINES, 0, linesToScroll, 0);
-                try {
-                    Class clazz = Class.forName("org.eclipse.swt.internal.win32.OS");
-                    Method method = clazz.getDeclaredMethod("SystemParametersInfo", new Class []{int.class, int.class, int [].class, int.class});
-                    method.invoke(clazz, 104 /*SPI_GETWHEELSCROLLLINES*/, 0, linesToScroll, 0);
-                } catch (IllegalAccessException iae) {
-                } catch (InvocationTargetException ite) {
-                } catch (NoSuchMethodException nme) {
-                } catch (ClassNotFoundException cfe) {
-                    //Fail silently
-                }
-                if (linesToScroll [0] != -1 /*OS.WHEEL_PAGESCROLL*/) {
-                    divisor = linesToScroll [0];
-                }
-            } else {
-                if ("gtk".equals(SWT.getPlatform())) {
-                    divisor = 3;
-                }
+        if ("win32".equals(SWT.getPlatform())) {
+            int [] linesToScroll = new int [1];
+            //OS.SystemParametersInfo (OS.SPI_GETWHEELSCROLLLINES, 0, linesToScroll, 0);
+            try {
+                Class clazz = Class.forName("org.eclipse.swt.internal.win32.OS");
+                Method method = clazz.getDeclaredMethod("SystemParametersInfo", new Class []{int.class, int.class, int [].class, int.class});
+                method.invoke(clazz, 104 /*SPI_GETWHEELSCROLLLINES*/, 0, linesToScroll, 0);
+            } catch (IllegalAccessException iae) {
+            } catch (InvocationTargetException ite) {
+            } catch (NoSuchMethodException nme) {
+            } catch (ClassNotFoundException cfe) {
+                //Fail silently
             }
-            return -e.count / Math.max(1, divisor);
+            if (linesToScroll [0] != -1 /*OS.WHEEL_PAGESCROLL*/) {
+                divisor = linesToScroll [0];
+            }
+        } else {
+            if ("gtk".equals(SWT.getPlatform())) {
+                divisor = 3;
+            }
         }
-        return 0;
+        return -e.count / Math.max(1, divisor);
     }
 
     static int keyIDToEmbedKeyType(int id) {
diff --git a/tests/manual/swt/FXCanvasMouseWheelEventsTest.java b/tests/manual/swt/FXCanvasMouseWheelEventsTest.java
new file mode 100644
--- /dev/null
+++ b/tests/manual/swt/FXCanvasMouseWheelEventsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javafx.application.Platform;
+import javafx.embed.swt.FXCanvas;
+import javafx.event.EventHandler;
+import javafx.scene.Group;
+import javafx.scene.ImageCursor;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.scene.control.TextArea;
+import javafx.scene.image.Image;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Rectangle;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+public class FXCanvasMouseWheelEventsTest {
+
+    static final String instructions =
+            "This tests that SWT mouse wheel events are properly transferred to SWT. " +
+                    "It passes if both, vertical and horizontal mouse wheel events are both recognized properly.";
+
+    private static TextArea createInfo(String msg) {
+        TextArea t = new TextArea(msg);
+        t.setWrapText(true);
+        t.setEditable(false);
+        t.setMaxWidth(400);
+        t.setMaxHeight(100);
+        return t;
+    }
+
+    public static void main(String[] args) {
+        final Display display = new Display();
+        final Shell shell = new Shell(display);
+        shell.setText("FXCanvasMouseWheelEventsTest");
+        shell.setSize(400, 200);
+        shell.setLayout(new FillLayout());
+        final FXCanvas canvas = new FXCanvas(shell, SWT.NONE);
+        shell.open();
+
+        // create and hook scene
+        Group root = new Group();
+
+        TextArea info = createInfo(instructions);
+        Label output = new Label("No events yet...");
+
+        VBox vbox = new VBox();
+        vbox.getChildren().addAll(info, output);
+        root.getChildren().add(vbox);
+
+        final Scene scene = new Scene(root, 200, 200);
+
+        final int[] eventCount = {0};
+        root.setOnScroll(scrollEvent -> {
+            output.setText("Scroll event #" + eventCount[0]++ + ": deltaX: " + scrollEvent.getDeltaX() + ", deltaY: " + scrollEvent.getDeltaY());
+        });
+        canvas.setScene(scene);
+
+        while (!shell.isDisposed()) {
+            // run SWT event loop
+            if (!display.readAndDispatch()) {
+                display.sleep();
+            }
+        }
+        display.dispose();
+    }
+}
