1 /*
   2  * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.javafx.tk.quantum;
  27 
  28 import com.sun.glass.events.GestureEvent;
  29 import com.sun.glass.events.KeyEvent;
  30 import com.sun.glass.events.MouseEvent;
  31 import com.sun.glass.events.ViewEvent;
  32 import com.sun.glass.events.TouchEvent;
  33 import com.sun.glass.events.SwipeGesture;
  34 import com.sun.glass.ui.Accessible;
  35 import com.sun.glass.ui.Clipboard;
  36 import com.sun.glass.ui.ClipboardAssistance;
  37 import com.sun.glass.ui.View;
  38 import com.sun.glass.ui.Window;
  39 import com.sun.javafx.PlatformUtil;
  40 import com.sun.javafx.collections.TrackableObservableList;
  41 import com.sun.javafx.logging.PulseLogger;
  42 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
  43 import com.sun.javafx.scene.input.KeyCodeMap;
  44 
  45 import javafx.collections.ListChangeListener;
  46 import javafx.collections.ObservableList;
  47 
  48 import javafx.event.EventType;
  49 import javafx.geometry.Point2D;
  50 import javafx.scene.input.InputMethodEvent;
  51 import javafx.scene.input.InputMethodHighlight;
  52 import javafx.scene.input.InputMethodTextRun;
  53 import javafx.scene.input.MouseButton;
  54 import javafx.scene.input.RotateEvent;
  55 import javafx.scene.input.ScrollEvent;
  56 import javafx.scene.input.SwipeEvent;
  57 import javafx.scene.input.TouchPoint;
  58 import javafx.scene.input.TransferMode;
  59 import javafx.scene.input.ZoomEvent;
  60 
  61 import java.security.AccessController;
  62 import java.security.PrivilegedAction;
  63 
  64 class GlassViewEventHandler extends View.EventHandler {
  65 
  66     static boolean zoomGestureEnabled;
  67     static boolean rotateGestureEnabled;
  68     static boolean scrollGestureEnabled;
  69     static {
  70         AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
  71             zoomGestureEnabled = Boolean.valueOf(System.getProperty("com.sun.javafx.gestures.zoom", "false"));
  72             rotateGestureEnabled = Boolean.valueOf(System.getProperty("com.sun.javafx.gestures.rotate", "false"));
  73             scrollGestureEnabled = Boolean.valueOf(System.getProperty("com.sun.javafx.gestures.scroll", "false"));
  74             return null;
  75         });
  76     }
  77 
  78     private ViewScene scene;
  79     private final GlassSceneDnDEventHandler dndHandler;
  80     private final GestureRecognizers gestures;
  81 
  82     public GlassViewEventHandler(final ViewScene scene) {
  83         this.scene = scene;
  84 
  85         dndHandler = new GlassSceneDnDEventHandler(scene);
  86 
  87         gestures = new GestureRecognizers();
  88         if (PlatformUtil.isWindows() || PlatformUtil.isIOS() || PlatformUtil.isEmbedded()) {
  89             gestures.add(new SwipeGestureRecognizer(scene));
  90         }
  91         if (zoomGestureEnabled) {
  92             gestures.add(new ZoomGestureRecognizer(scene));
  93         }
  94         if (rotateGestureEnabled) {
  95             gestures.add(new RotateGestureRecognizer(scene));
  96         }
  97         if (scrollGestureEnabled) {
  98             gestures.add(new ScrollGestureRecognizer(scene));
  99         }
 100     }
 101 
 102     // Default fullscreen allows limited keyboard input.
 103     // It will only receive events from the following keys:
 104     // DOWN, UP, LEFT, RIGHT, SPACE, TAB, PAGE_UP, PAGE_DOWN,
 105     // HOME, END, ENTER.
 106     private static boolean allowableFullScreenKeys(int key) {
 107         switch (key) {
 108             case KeyEvent.VK_DOWN:
 109             case KeyEvent.VK_UP:
 110             case KeyEvent.VK_LEFT:
 111             case KeyEvent.VK_RIGHT:
 112             case KeyEvent.VK_SPACE:
 113             case KeyEvent.VK_TAB:
 114             case KeyEvent.VK_PAGE_DOWN:
 115             case KeyEvent.VK_PAGE_UP:
 116             case KeyEvent.VK_HOME:
 117             case KeyEvent.VK_END:
 118             case KeyEvent.VK_ENTER:
 119                 return true;
 120         }
 121         return false;
 122     }
 123 
 124     private boolean checkFullScreenKeyEvent(int type, int key, char chars[], int modifiers) {
 125         return scene.getWindowStage().isTrustedFullScreen() || allowableFullScreenKeys(key);
 126     }
 127 
 128     private final PaintCollector collector = PaintCollector.getInstance();
 129 
 130     private static EventType<javafx.scene.input.KeyEvent> keyEventType(int glassType) {
 131         switch (glassType) {
 132             case com.sun.glass.events.KeyEvent.PRESS:
 133                 return javafx.scene.input.KeyEvent.KEY_PRESSED;
 134             case com.sun.glass.events.KeyEvent.RELEASE:
 135                 return javafx.scene.input.KeyEvent.KEY_RELEASED;
 136             case com.sun.glass.events.KeyEvent.TYPED:
 137                 return javafx.scene.input.KeyEvent.KEY_TYPED;
 138             default:
 139                 if (QuantumToolkit.verbose) {
 140                     System.err.println("Unknown Glass key event type: " + glassType);
 141                 }
 142                 return null;
 143         }
 144     }
 145 
 146     private final KeyEventNotification keyNotification = new KeyEventNotification();
 147     private class KeyEventNotification implements PrivilegedAction<Void> {
 148         View view;
 149         long time;
 150         int type;
 151         int key;
 152         char[] chars;
 153         int modifiers;
 154 
 155         @Override
 156         public Void run() {
 157             if (PULSE_LOGGING_ENABLED) {
 158                 PulseLogger.newInput(keyEventType(type).toString());
 159             }
 160             WindowStage stage = scene.getWindowStage();
 161             try {
 162                 if (stage != null) {
 163                     stage.setInEventHandler(true);
 164                 }
 165 
 166                 boolean shiftDown = (modifiers & KeyEvent.MODIFIER_SHIFT) != 0;
 167                 boolean controlDown = (modifiers & KeyEvent.MODIFIER_CONTROL) != 0;
 168                 boolean altDown = (modifiers & KeyEvent.MODIFIER_ALT) != 0;
 169                 boolean metaDown = (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0;
 170 
 171                 String str = new String(chars);
 172                 String text = str; // TODO: this must be a text like "HOME", "F1", or "A"
 173 
 174                 javafx.scene.input.KeyEvent keyEvent = new javafx.scene.input.KeyEvent(
 175                         keyEventType(type),
 176                         str, text,
 177                         KeyCodeMap.valueOf(key) ,
 178                         shiftDown, controlDown, altDown, metaDown);
 179 
 180                 switch (type) {
 181                     case com.sun.glass.events.KeyEvent.PRESS:
 182                         if (view.isInFullscreen() && stage != null) {
 183                             if (stage.getSavedFullScreenExitKey() != null
 184                                 && stage.getSavedFullScreenExitKey().match(keyEvent)) {
 185                                 stage.exitFullScreen();
 186                             }
 187                         }
 188                         /* NOBREAK */
 189                     case com.sun.glass.events.KeyEvent.RELEASE:
 190                     case com.sun.glass.events.KeyEvent.TYPED:
 191                         if (view.isInFullscreen()) {
 192                             if (!checkFullScreenKeyEvent(type, key, chars, modifiers)) {
 193                                 break;
 194                             }
 195                         }
 196                         if (scene.sceneListener != null) {
 197                             scene.sceneListener.keyEvent(keyEvent);
 198                         }
 199                         break;
 200                     default:
 201                         if (QuantumToolkit.verbose) {
 202                             System.out.println("handleKeyEvent: unhandled type: " + type);
 203                         }
 204                 }
 205             } finally {
 206                 if (stage != null) {
 207                     stage.setInEventHandler(false);
 208                 }
 209                 if (PULSE_LOGGING_ENABLED) {
 210                     PulseLogger.newInput(null);
 211                 }
 212             }
 213             return null;
 214         }
 215     }
 216 
 217     @Override public void handleKeyEvent(View view, long time, int type, int key,
 218                                          char[] chars, int modifiers)
 219     {
 220         keyNotification.view = view;
 221         keyNotification.time = time;
 222         keyNotification.type = type;
 223         keyNotification.key = key;
 224         keyNotification.chars = chars;
 225         keyNotification.modifiers = modifiers;
 226 
 227         QuantumToolkit.runWithoutRenderLock(() -> {
 228             return AccessController.doPrivileged(keyNotification, scene.getAccessControlContext());
 229         });
 230     }
 231 
 232     private static EventType<javafx.scene.input.MouseEvent> mouseEventType(int glassType) {
 233         switch (glassType) {
 234             case com.sun.glass.events.MouseEvent.DOWN:
 235                 return javafx.scene.input.MouseEvent.MOUSE_PRESSED;
 236             case com.sun.glass.events.MouseEvent.UP:
 237                 return javafx.scene.input.MouseEvent.MOUSE_RELEASED;
 238             case com.sun.glass.events.MouseEvent.ENTER:
 239                 return javafx.scene.input.MouseEvent.MOUSE_ENTERED;
 240             case com.sun.glass.events.MouseEvent.EXIT:
 241                 return javafx.scene.input.MouseEvent.MOUSE_EXITED;
 242             case com.sun.glass.events.MouseEvent.MOVE:
 243                 return javafx.scene.input.MouseEvent.MOUSE_MOVED;
 244             case com.sun.glass.events.MouseEvent.DRAG:
 245                 return javafx.scene.input.MouseEvent.MOUSE_DRAGGED;
 246             case com.sun.glass.events.MouseEvent.WHEEL:
 247                 throw new IllegalArgumentException("WHEEL event cannot "
 248                         + "be translated to MouseEvent, must be translated to "
 249                         + "ScrollEvent");
 250             default:
 251                 if (QuantumToolkit.verbose) {
 252                     System.err.println("Unknown Glass mouse event type: " + glassType);
 253                 }
 254                 return null;
 255         }
 256     }
 257 
 258     private static MouseButton mouseEventButton(int glassButton) {
 259         switch (glassButton) {
 260             case com.sun.glass.events.MouseEvent.BUTTON_LEFT:
 261                 return MouseButton.PRIMARY;
 262             case com.sun.glass.events.MouseEvent.BUTTON_RIGHT:
 263                 return MouseButton.SECONDARY;
 264             case com.sun.glass.events.MouseEvent.BUTTON_OTHER:
 265                 return MouseButton.MIDDLE;
 266             default:
 267                 return MouseButton.NONE;
 268         }
 269     }
 270 
 271     // Used to determine whether a DRAG operation has been initiated on this window
 272     private int mouseButtonPressedMask = 0;
 273 
 274     private final MouseEventNotification mouseNotification = new MouseEventNotification();
 275     private class MouseEventNotification implements PrivilegedAction<Void> {
 276         View view;
 277         long time;
 278         int type;
 279         int button;
 280         int x, y, xAbs, yAbs;
 281         int modifiers;
 282         boolean isPopupTrigger;
 283         boolean isSynthesized;
 284 
 285         @Override
 286         public Void run() {
 287             if (PULSE_LOGGING_ENABLED) {
 288                 PulseLogger.newInput(mouseEventType(type).toString());
 289             }
 290 
 291             int buttonMask;
 292             switch (button) {
 293                 case MouseEvent.BUTTON_LEFT:
 294                     buttonMask = KeyEvent.MODIFIER_BUTTON_PRIMARY;
 295                     break;
 296                 case MouseEvent.BUTTON_OTHER:
 297                     buttonMask = KeyEvent.MODIFIER_BUTTON_MIDDLE;
 298                     break;
 299                 case MouseEvent.BUTTON_RIGHT:
 300                     buttonMask = KeyEvent.MODIFIER_BUTTON_SECONDARY;
 301                     break;
 302                 default:
 303                     buttonMask = 0;
 304                     break;
 305             }
 306 
 307             switch (type) {
 308                 case MouseEvent.MOVE:
 309                     if (button != MouseEvent.BUTTON_NONE) {
 310                         //RT-11305: the drag hasn't been started on this window -- ignore the event
 311                         return null;
 312                     }
 313                     break;
 314                 case MouseEvent.UP:
 315                     if ((mouseButtonPressedMask & buttonMask) == 0) {
 316                         //RT-11305: the mouse button hasn't been pressed on this window -- ignore the event
 317                         return null;
 318                     }
 319                     mouseButtonPressedMask &= ~buttonMask;
 320                     break;
 321                 case MouseEvent.DOWN:
 322                     mouseButtonPressedMask |= buttonMask;
 323                     break;
 324                 case MouseEvent.ENTER:
 325                 case MouseEvent.EXIT:
 326                     break;
 327                 case MouseEvent.CLICK:
 328                     // Don't send click events to FX, as they are generated in Scene
 329                     return null;
 330                 default:
 331                     if (QuantumToolkit.verbose) {
 332                         System.out.println("handleMouseEvent: unhandled type: " + type);
 333                     }
 334             }
 335 
 336             WindowStage stage = scene.getWindowStage();
 337             try {
 338                 if (stage != null) {
 339                     stage.setInEventHandler(true);
 340                 }
 341                 if (scene.sceneListener != null) {
 342                     boolean shiftDown = (modifiers & KeyEvent.MODIFIER_SHIFT) != 0;
 343                     boolean controlDown = (modifiers & KeyEvent.MODIFIER_CONTROL) != 0;
 344                     boolean altDown = (modifiers & KeyEvent.MODIFIER_ALT) != 0;
 345                     boolean metaDown = (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0;
 346                     boolean primaryButtonDown = (modifiers & KeyEvent.MODIFIER_BUTTON_PRIMARY) != 0;
 347                     boolean middleButtonDown = (modifiers & KeyEvent.MODIFIER_BUTTON_MIDDLE) != 0;
 348                     boolean secondaryButtonDown = (modifiers & KeyEvent.MODIFIER_BUTTON_SECONDARY) != 0;
 349                     final Window w = view.getWindow();
 350                     double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
 351                     double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
 352 
 353                     scene.sceneListener.mouseEvent(mouseEventType(type),
 354                             x / pScaleX, y / pScaleY, xAbs / pScaleX, yAbs / pScaleY,
 355                             mouseEventButton(button), isPopupTrigger, isSynthesized,
 356                             shiftDown, controlDown, altDown, metaDown,
 357                             primaryButtonDown, middleButtonDown, secondaryButtonDown);
 358                 }
 359             } finally {
 360                 if (stage != null) {
 361                     stage.setInEventHandler(false);
 362                 }
 363                 if (PULSE_LOGGING_ENABLED) {
 364                     PulseLogger.newInput(null);
 365                 }
 366             }
 367             return null;
 368         }
 369     }
 370 
 371     @Override
 372     public void handleMouseEvent(View view, long time, int type, int button,
 373                                  int x, int y, int xAbs, int yAbs,
 374                                  int modifiers, boolean isPopupTrigger, boolean isSynthesized)
 375     {
 376         mouseNotification.view = view;
 377         mouseNotification.time = time;
 378         mouseNotification.type = type;
 379         mouseNotification.button = button;
 380         mouseNotification.x = x;
 381         mouseNotification.y = y;
 382         mouseNotification.xAbs = xAbs;
 383         mouseNotification.yAbs = yAbs;
 384         mouseNotification.modifiers = modifiers;
 385         mouseNotification.isPopupTrigger = isPopupTrigger;
 386         mouseNotification.isSynthesized = isSynthesized;
 387 
 388         QuantumToolkit.runWithoutRenderLock(() -> {
 389             return AccessController.doPrivileged(mouseNotification, scene.getAccessControlContext());
 390         });
 391     }
 392 
 393     @Override public void handleMenuEvent(final View view,
 394                                           final int x, final int y, final int xAbs, final int yAbs,
 395                                           final boolean isKeyboardTrigger)
 396     {
 397         if (PULSE_LOGGING_ENABLED) {
 398             PulseLogger.newInput("MENU_EVENT");
 399         }
 400         WindowStage stage = scene.getWindowStage();
 401         try {
 402             if (stage != null) {
 403                 stage.setInEventHandler(true);
 404             }
 405             QuantumToolkit.runWithoutRenderLock(() -> {
 406                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 407                     if (scene.sceneListener != null) {
 408                         final Window w = view.getWindow();
 409                         double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
 410                         double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
 411                         scene.sceneListener.menuEvent(x / pScaleX, y / pScaleY,
 412                                                       xAbs / pScaleX, yAbs / pScaleY,
 413                                                       isKeyboardTrigger);
 414                     }
 415                     return null;
 416                 }, scene.getAccessControlContext());
 417             });
 418         } finally {
 419             if (stage != null) {
 420                 stage.setInEventHandler(false);
 421             }
 422             if (PULSE_LOGGING_ENABLED) {
 423                 PulseLogger.newInput(null);
 424             }
 425         }
 426     }
 427 
 428     @Override public void handleScrollEvent(final View view, final long time,
 429                                             final int x, final int y, final int xAbs, final int yAbs,
 430                                             final double deltaX, final double deltaY, final int modifiers,
 431                                             final int lines, final int chars,
 432                                             final int defaultLines, final int defaultChars,
 433                                             final double xMultiplier, final double yMultiplier)
 434     {
 435         if (PULSE_LOGGING_ENABLED) {
 436             PulseLogger.newInput("SCROLL_EVENT");
 437         }
 438         WindowStage stage = scene.getWindowStage();
 439         try {
 440             if (stage != null) {
 441                 stage.setInEventHandler(true);
 442             }
 443             QuantumToolkit.runWithoutRenderLock(() -> {
 444                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 445                     if (scene.sceneListener != null) {
 446                         final Window w = view.getWindow();
 447                         double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
 448                         double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
 449                         scene.sceneListener.scrollEvent(ScrollEvent.SCROLL,
 450                             deltaX / pScaleX, deltaY / pScaleY, 0, 0,
 451                             xMultiplier, yMultiplier,
 452                             0, // touchCount
 453                             chars, lines, defaultChars, defaultLines,
 454                             x / pScaleX, y / pScaleY, xAbs / pScaleX, yAbs / pScaleY,
 455                             (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
 456                             (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
 457                             (modifiers & KeyEvent.MODIFIER_ALT) != 0,
 458                             (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
 459                             false, // this is always indirect
 460                             false); // this has no inertia
 461                     }
 462                     return null;
 463                 }, scene.getAccessControlContext());
 464             });
 465         } finally {
 466             if (stage != null) {
 467                 stage.setInEventHandler(false);
 468             }
 469             if (PULSE_LOGGING_ENABLED) {
 470                 PulseLogger.newInput(null);
 471             }
 472         }
 473     }
 474 
 475     private static byte inputMethodEventAttrValue(int pos, int[] attrBoundary, byte[] attrValue) {
 476         if (attrBoundary != null) {
 477             for (int current = 0; current < attrBoundary.length-1; current++) {
 478                 if (pos >= attrBoundary[current] &&
 479                     pos < attrBoundary[current+1]) {
 480                     return attrValue[current];
 481                 }
 482             }
 483         }
 484         return View.IME_ATTR_INPUT_ERROR;
 485     }
 486 
 487     private static ObservableList<InputMethodTextRun> inputMethodEventComposed(
 488             String text, int commitCount, int[] clauseBoundary, int[] attrBoundary, byte[] attrValue)
 489     {
 490         ObservableList<InputMethodTextRun> composed = new TrackableObservableList<InputMethodTextRun>() {
 491             @Override
 492             protected void onChanged(ListChangeListener.Change<InputMethodTextRun> c) {
 493             }
 494         };
 495 
 496         if (commitCount < text.length()) {
 497             if (clauseBoundary == null) {
 498                 // Create one single segment as UNSELECTED_RAW
 499                 composed.add(new InputMethodTextRun(
 500                         text.substring(commitCount),
 501                         InputMethodHighlight.UNSELECTED_RAW));
 502             } else {
 503                 for (int current = 0; current < clauseBoundary.length-1; current++) {
 504                     if (clauseBoundary[current] < commitCount) {
 505                         continue;
 506                     }
 507 
 508                     InputMethodHighlight highlight;
 509                     switch (inputMethodEventAttrValue(clauseBoundary[current], attrBoundary, attrValue)) {
 510                         case View.IME_ATTR_TARGET_CONVERTED:
 511                             highlight = InputMethodHighlight.SELECTED_CONVERTED;
 512                             break;
 513                         case View.IME_ATTR_CONVERTED:
 514                             highlight = InputMethodHighlight.UNSELECTED_CONVERTED;
 515                             break;
 516                         case View.IME_ATTR_TARGET_NOTCONVERTED:
 517                             highlight = InputMethodHighlight.SELECTED_RAW;
 518                             break;
 519                         case View.IME_ATTR_INPUT:
 520                         case View.IME_ATTR_INPUT_ERROR:
 521                         default:
 522                             highlight = InputMethodHighlight.UNSELECTED_RAW;
 523                             break;
 524                     }
 525                     composed.add(new InputMethodTextRun(
 526                             text.substring(clauseBoundary[current],
 527                                            clauseBoundary[current+1]),
 528                             highlight));
 529                 }
 530             }
 531         }
 532         return composed;
 533     }
 534 
 535     @Override public void handleInputMethodEvent(final long time, final String text,
 536                                                  final int[] clauseBoundary,
 537                                                  final int[] attrBoundary, final byte[] attrValue,
 538                                                  final int commitCount, final int cursorPos)
 539     {
 540         if (PULSE_LOGGING_ENABLED) {
 541             PulseLogger.newInput("INPUT_METHOD_EVENT");
 542         }
 543         WindowStage stage = scene.getWindowStage();
 544         try {
 545             if (stage != null) {
 546                 stage.setInEventHandler(true);
 547             }
 548             QuantumToolkit.runWithoutRenderLock(() -> {
 549                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 550                     if (scene.sceneListener != null) {
 551                         String t = text != null ? text : "";
 552                         EventType<InputMethodEvent> eventType =
 553                                 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED;
 554                         ObservableList<InputMethodTextRun> composed = inputMethodEventComposed(
 555                                 t, commitCount, clauseBoundary, attrBoundary, attrValue);
 556                         String committed = t.substring(0, commitCount);
 557                         scene.sceneListener.inputMethodEvent(eventType, composed, committed, cursorPos);
 558                     }
 559                     return null;
 560                 }, scene.getAccessControlContext());
 561             });
 562         } finally {
 563             if (stage != null) {
 564                 stage.setInEventHandler(false);
 565             }
 566             if (PULSE_LOGGING_ENABLED) {
 567                 PulseLogger.newInput(null);
 568             }
 569         }
 570     }
 571 
 572     @Override
 573     public double[] getInputMethodCandidatePos(int offset) {
 574         Point2D p2d = scene.inputMethodRequests.getTextLocation(offset);
 575         double[] ret = new double[2];
 576         ret[0] = p2d.getX();
 577         ret[1] = p2d.getY();
 578         return ret;
 579     }
 580 
 581     private static TransferMode actionToTransferMode(int dropActions) {
 582         if (dropActions == Clipboard.ACTION_NONE) {
 583             return null;
 584         } else if (dropActions == Clipboard.ACTION_COPY
 585                 //IE drop action for URL copy;  XXX: should be fixed in Glass
 586                 || dropActions == (Clipboard.ACTION_COPY | Clipboard.ACTION_REFERENCE) )
 587         {
 588             return TransferMode.COPY;
 589         } else if (dropActions == Clipboard.ACTION_MOVE
 590                 //IE drop action for URL move;  XXX: should be fixed in Glass
 591                 || dropActions == (Clipboard.ACTION_MOVE | Clipboard.ACTION_REFERENCE) )
 592         {
 593             return TransferMode.MOVE;
 594         } else if (dropActions == Clipboard.ACTION_REFERENCE) {
 595             return TransferMode.LINK;
 596         } else if (dropActions == Clipboard.ACTION_COPY_OR_MOVE) {
 597             if (QuantumToolkit.verbose) {
 598                 System.err.println("Ambiguous drop action: " + Integer.toHexString(dropActions));
 599             }
 600         } else {
 601             if (QuantumToolkit.verbose) {
 602                 System.err.println("Unknown drop action: " + Integer.toHexString(dropActions));
 603             }
 604         }
 605         return null;
 606     }
 607 
 608     private static int transferModeToAction(TransferMode tm) {
 609         if (tm == null) {
 610             return Clipboard.ACTION_NONE;
 611         }
 612 
 613         switch (tm) {
 614             case COPY:
 615                 return Clipboard.ACTION_COPY;
 616             case MOVE:
 617                 return Clipboard.ACTION_MOVE;
 618             case LINK:
 619                 return Clipboard.ACTION_REFERENCE;
 620             default:
 621                 return Clipboard.ACTION_NONE;
 622         }
 623     }
 624 
 625     @Override public int handleDragEnter(View view,
 626                                          final int x, final int y, final int xAbs, final int yAbs,
 627                                          final int recommendedDropAction,
 628                                          final ClipboardAssistance dropTargetAssistant)
 629     {
 630         if (PULSE_LOGGING_ENABLED) {
 631             PulseLogger.newInput("DRAG_ENTER");
 632         }
 633         TransferMode action;
 634         try {
 635             action = QuantumToolkit.runWithoutRenderLock(() -> {
 636                 return dndHandler.handleDragEnter(x, y, xAbs, yAbs,
 637                         actionToTransferMode(recommendedDropAction),
 638                         dropTargetAssistant);
 639             });
 640         } finally {
 641             if (PULSE_LOGGING_ENABLED) {
 642                 PulseLogger.newInput(null);
 643             }
 644         }
 645         return transferModeToAction(action);
 646     }
 647 
 648     @Override public void handleDragLeave(View view, final ClipboardAssistance dropTargetAssistant) {
 649         if (PULSE_LOGGING_ENABLED) {
 650             PulseLogger.newInput("DRAG_LEAVE");
 651         }
 652         try {
 653             QuantumToolkit.runWithoutRenderLock(() -> {
 654                 dndHandler.handleDragLeave(dropTargetAssistant);
 655                 return null;
 656             });
 657         } finally {
 658             if (PULSE_LOGGING_ENABLED) {
 659                 PulseLogger.newInput(null);
 660             }
 661         }
 662     }
 663 
 664     @Override public int handleDragDrop(View view,
 665                                         final int x, final int y, final int xAbs, final int yAbs,
 666                                         final int recommendedDropAction,
 667                                         final ClipboardAssistance dropTargetAssistant)
 668     {
 669         if (PULSE_LOGGING_ENABLED) {
 670             PulseLogger.newInput("DRAG_DROP");
 671         }
 672         TransferMode action;
 673         try {
 674             action = QuantumToolkit.runWithoutRenderLock(() -> {
 675                 return dndHandler.handleDragDrop(x, y, xAbs, yAbs,
 676                     actionToTransferMode(recommendedDropAction),
 677                     dropTargetAssistant);
 678             });
 679         } finally {
 680             if (PULSE_LOGGING_ENABLED) {
 681                 PulseLogger.newInput(null);
 682             }
 683         }
 684         return transferModeToAction(action);
 685     }
 686 
 687     @Override public int handleDragOver(View view,
 688                                         final int x, final int y, final int xAbs, final int yAbs,
 689                                         final int recommendedDropAction,
 690                                         final ClipboardAssistance dropTargetAssistant)
 691     {
 692         if (PULSE_LOGGING_ENABLED) {
 693             PulseLogger.newInput("DRAG_OVER");
 694         }
 695         TransferMode action;
 696         try {
 697             action = QuantumToolkit.runWithoutRenderLock(() -> {
 698                 return dndHandler.handleDragOver(x, y, xAbs, yAbs,
 699                     actionToTransferMode(recommendedDropAction),
 700                     dropTargetAssistant);
 701             });
 702         } finally {
 703             if (PULSE_LOGGING_ENABLED) {
 704                 PulseLogger.newInput(null);
 705             }
 706         }
 707         return transferModeToAction(action);
 708     }
 709 
 710     private ClipboardAssistance dropSourceAssistant;
 711 
 712     @Override public void handleDragStart(View view, final int button,
 713                                           final int x, final int y, final int xAbs, final int yAbs,
 714                                           final ClipboardAssistance assistant)
 715     {
 716         if (PULSE_LOGGING_ENABLED) {
 717             PulseLogger.newInput("DRAG_START");
 718         }
 719         dropSourceAssistant = assistant;
 720         try {
 721             QuantumToolkit.runWithoutRenderLock(() -> {
 722                 dndHandler.handleDragStart(button, x, y, xAbs, yAbs, assistant);
 723                 return null;
 724             });
 725         } finally {
 726             if (PULSE_LOGGING_ENABLED) {
 727                 PulseLogger.newInput(null);
 728             }
 729         }
 730     }
 731 
 732     @Override public void handleDragEnd(View view, final int performedAction) {
 733         if (PULSE_LOGGING_ENABLED) {
 734             PulseLogger.newInput("DRAG_END");
 735         }
 736         try {
 737             QuantumToolkit.runWithoutRenderLock(() -> {
 738                 dndHandler.handleDragEnd(actionToTransferMode(performedAction), dropSourceAssistant);
 739                 return null;
 740             });
 741         } finally {
 742             if (PULSE_LOGGING_ENABLED) {
 743                 PulseLogger.newInput(null);
 744             }
 745         }
 746     }
 747 
 748     // TODO - dropTargetListener.dropActionChanged
 749     // TODO - dragSourceListener.dropActionChanged
 750 
 751     private final ViewEventNotification viewNotification = new ViewEventNotification();
 752     private class ViewEventNotification implements PrivilegedAction<Void> {
 753         View view;
 754         long time;
 755         int type;
 756 
 757         @Override
 758         public Void run() {
 759             if (scene.sceneListener == null) {
 760                 return null;
 761             }
 762             switch (type) {
 763                 case ViewEvent.REPAINT: {
 764                     Window w = view.getWindow();
 765                     if (w != null && w.getMinimumWidth() == view.getWidth() && !w.isVisible()) {
 766                         // RT-21057 - ignore initial minimum size setting if not visible
 767                         break;
 768                     }
 769                     if (QuantumToolkit.drawInPaint && w != null && w.isVisible()) {
 770                         WindowStage stage = scene.getWindowStage();
 771                         if (stage != null && !stage.isApplet()) {
 772                             collector.liveRepaintRenderJob(scene);
 773                         }
 774                     }
 775                     scene.entireSceneNeedsRepaint();
 776                     break;
 777                 }
 778                 case ViewEvent.RESIZE: {
 779                     final Window w = view.getWindow();
 780                     float pScaleX = (w == null) ? 1.0f : w.getPlatformScaleX();
 781                     float pScaleY = (w == null) ? 1.0f : w.getPlatformScaleY();
 782                     scene.sceneListener.changedSize(view.getWidth()  / pScaleX,
 783                                                     view.getHeight() / pScaleY);
 784                     scene.entireSceneNeedsRepaint();
 785                     QuantumToolkit.runWithRenderLock(() -> {
 786                         scene.updateSceneState();
 787                         return null;
 788                     });
 789                     if (QuantumToolkit.liveResize && w != null && w.isVisible()) {
 790                         WindowStage stage = scene.getWindowStage();
 791                         if (stage != null && !stage.isApplet()) {
 792                             collector.liveRepaintRenderJob(scene);
 793                         }
 794                     }
 795                     break;
 796                 }
 797                 case ViewEvent.MOVE: {
 798                     // MOVE events can be "synthesized" and the window will
 799                     // be null if this is synthesized during a "REMOVE" event
 800                     final Window w = view.getWindow();
 801                     float pScaleX = (w == null) ? 1.0f : w.getPlatformScaleX();
 802                     float pScaleY = (w == null) ? 1.0f : w.getPlatformScaleY();
 803                     scene.sceneListener.changedLocation(view.getX() / pScaleX,
 804                                                         view.getY() / pScaleY);
 805                     break;
 806                 }
 807                 case ViewEvent.FULLSCREEN_ENTER:
 808                 case ViewEvent.FULLSCREEN_EXIT:
 809                     if (scene.getWindowStage() != null) {
 810                         scene.getWindowStage().fullscreenChanged(type == ViewEvent.FULLSCREEN_ENTER);
 811                     }
 812                     break;
 813                 case ViewEvent.ADD:
 814                 case ViewEvent.REMOVE:
 815                     // unhandled
 816                     break;
 817                 default:
 818                     throw new RuntimeException("handleViewEvent: unhandled type: " + type);
 819             }
 820             return null;
 821         }
 822     }
 823 
 824     @Override public void handleViewEvent(View view, long time, final int type) {
 825         if (PULSE_LOGGING_ENABLED) {
 826             PulseLogger.newInput("VIEW_EVENT: "+ViewEvent.getTypeString(type));
 827         }
 828         viewNotification.view = view;
 829         viewNotification.time = time;
 830         viewNotification.type = type;
 831         try {
 832             QuantumToolkit.runWithoutRenderLock(() -> {
 833                 return AccessController.doPrivileged(viewNotification, scene.getAccessControlContext());
 834             });
 835         }
 836         finally {
 837             if (PULSE_LOGGING_ENABLED) {
 838                 PulseLogger.newInput(null);
 839             }
 840         }
 841     }
 842 
 843     @Override public void handleScrollGestureEvent(
 844             View view, final long time, final int type,
 845             final int modifiers, final boolean isDirect, final boolean isInertia, final int touchCount,
 846             final int x, final int y, final int xAbs, final int yAbs, final double dx, final double dy,
 847             final double totaldx, final double totaldy, final double multiplierX, final double multiplierY)
 848     {
 849         if (PULSE_LOGGING_ENABLED) {
 850             PulseLogger.newInput("SCROLL_GESTURE_EVENT");
 851         }
 852         WindowStage stage = scene.getWindowStage();
 853         try {
 854             if (stage != null) {
 855                 stage.setInEventHandler(true);
 856             }
 857             QuantumToolkit.runWithoutRenderLock(() -> {
 858                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 859                     if (scene.sceneListener != null) {
 860                         EventType<ScrollEvent> eventType;
 861                         switch(type) {
 862                             case GestureEvent.GESTURE_STARTED:
 863                                 eventType = ScrollEvent.SCROLL_STARTED;
 864                                 break;
 865                             case GestureEvent.GESTURE_PERFORMED:
 866                                 eventType = ScrollEvent.SCROLL;
 867                                 break;
 868                             case GestureEvent.GESTURE_FINISHED:
 869                                 eventType = ScrollEvent.SCROLL_FINISHED;
 870                                 break;
 871                             default:
 872                                 throw new RuntimeException("Unknown scroll event type: " + type);
 873                         }
 874                         final Window w = view.getWindow();
 875                         double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
 876                         double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
 877                         scene.sceneListener.scrollEvent(eventType,
 878                                 dx / pScaleX, dy / pScaleY, totaldx / pScaleX, totaldy / pScaleY,
 879                                 multiplierX, multiplierY,
 880                                 touchCount,
 881                                 0, 0, 0, 0,
 882                                 x == View.GESTURE_NO_VALUE ? Double.NaN : x / pScaleX,
 883                                 y == View.GESTURE_NO_VALUE ? Double.NaN : y / pScaleY,
 884                                 xAbs == View.GESTURE_NO_VALUE ? Double.NaN : xAbs / pScaleX,
 885                                 yAbs == View.GESTURE_NO_VALUE ? Double.NaN : yAbs / pScaleY,
 886                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
 887                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
 888                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
 889                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
 890                                 isDirect, isInertia);
 891                     }
 892                     return null;
 893                 }, scene.getAccessControlContext());
 894             });
 895         } finally {
 896             if (stage != null) {
 897                 stage.setInEventHandler(false);
 898             }
 899             if (PULSE_LOGGING_ENABLED) {
 900                 PulseLogger.newInput(null);
 901             }
 902         }
 903     }
 904 
 905     @Override public void handleZoomGestureEvent(
 906             View view, final long time, final int type,
 907             final int modifiers, final boolean isDirect, final boolean isInertia,
 908             final int originx, final int originy,
 909             final int originxAbs, final int originyAbs,
 910             final double scale, double expansion,
 911             final double totalscale, double totalexpansion)
 912     {
 913         if (PULSE_LOGGING_ENABLED) {
 914             PulseLogger.newInput("ZOOM_GESTURE_EVENT");
 915         }
 916         WindowStage stage = scene.getWindowStage();
 917         try {
 918             if (stage != null) {
 919                 stage.setInEventHandler(true);
 920             }
 921             QuantumToolkit.runWithoutRenderLock(() -> {
 922                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 923                     if (scene.sceneListener != null) {
 924                         EventType<ZoomEvent> eventType;
 925                         switch (type) {
 926                             case GestureEvent.GESTURE_STARTED:
 927                                 eventType = ZoomEvent.ZOOM_STARTED;
 928                                 break;
 929                             case GestureEvent.GESTURE_PERFORMED:
 930                                 eventType = ZoomEvent.ZOOM;
 931                                 break;
 932                             case GestureEvent.GESTURE_FINISHED:
 933                                 eventType = ZoomEvent.ZOOM_FINISHED;
 934                                 break;
 935                             default:
 936                                 throw new RuntimeException("Unknown scroll event type: " + type);
 937                         }
 938                         final Window w = view.getWindow();
 939                         double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
 940                         double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
 941                         // REMIND: Scale the [total]scale params too?
 942                         scene.sceneListener.zoomEvent(eventType, scale, totalscale,
 943                                 originx == View.GESTURE_NO_VALUE ? Double.NaN : originx / pScaleX,
 944                                 originy == View.GESTURE_NO_VALUE ? Double.NaN : originy / pScaleY,
 945                                 originxAbs == View.GESTURE_NO_VALUE ? Double.NaN : originxAbs / pScaleX,
 946                                 originyAbs == View.GESTURE_NO_VALUE ? Double.NaN : originyAbs / pScaleY,
 947                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
 948                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
 949                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
 950                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
 951                                 isDirect, isInertia);
 952                     }
 953                     return null;
 954                 }, scene.getAccessControlContext());
 955             });
 956         } finally {
 957             if (stage != null) {
 958                 stage.setInEventHandler(false);
 959             }
 960             if (PULSE_LOGGING_ENABLED) {
 961                 PulseLogger.newInput(null);
 962             }
 963         }
 964     }
 965 
 966     @Override public void handleRotateGestureEvent(
 967             View view, final long time, final int type,
 968             final int modifiers, final boolean isDirect, final boolean isInertia,
 969             final int originx, final int originy,
 970             final int originxAbs, final int originyAbs,
 971             final double dangle, final double totalangle)
 972     {
 973         if (PULSE_LOGGING_ENABLED) {
 974             PulseLogger.newInput("ROTATE_GESTURE_EVENT");
 975         }
 976         WindowStage stage = scene.getWindowStage();
 977         try {
 978             if (stage != null) {
 979                 stage.setInEventHandler(true);
 980             }
 981             QuantumToolkit.runWithoutRenderLock(() -> {
 982                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 983                     if (scene.sceneListener != null) {
 984                         EventType<RotateEvent> eventType;
 985                         switch (type) {
 986                             case GestureEvent.GESTURE_STARTED:
 987                                 eventType = RotateEvent.ROTATION_STARTED;
 988                                 break;
 989                             case GestureEvent.GESTURE_PERFORMED:
 990                                 eventType = RotateEvent.ROTATE;
 991                                 break;
 992                             case GestureEvent.GESTURE_FINISHED:
 993                                 eventType = RotateEvent.ROTATION_FINISHED;
 994                                 break;
 995                             default:
 996                                 throw new RuntimeException("Unknown scroll event type: " + type);
 997                         }
 998                         final Window w = view.getWindow();
 999                         double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
1000                         double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
1001                         scene.sceneListener.rotateEvent(eventType, dangle, totalangle,
1002                                 originx == View.GESTURE_NO_VALUE ? Double.NaN : originx / pScaleX,
1003                                 originy == View.GESTURE_NO_VALUE ? Double.NaN : originy / pScaleY,
1004                                 originxAbs == View.GESTURE_NO_VALUE ? Double.NaN : originxAbs / pScaleX,
1005                                 originyAbs == View.GESTURE_NO_VALUE ? Double.NaN : originyAbs / pScaleY,
1006                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
1007                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
1008                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
1009                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
1010                                 isDirect, isInertia);
1011                     }
1012                     return null;
1013                 }, scene.getAccessControlContext());
1014             });
1015         } finally {
1016             if (stage != null) {
1017                 stage.setInEventHandler(false);
1018             }
1019             if (PULSE_LOGGING_ENABLED) {
1020                 PulseLogger.newInput(null);
1021             }
1022         }
1023     }
1024 
1025     @Override public void handleSwipeGestureEvent(
1026             View view, final long time, int type,
1027             final int modifiers, final boolean isDirect,
1028             boolean isInertia, final int touchCount,
1029             final int dir, final int x, final int y, final int xAbs, final int yAbs)
1030     {
1031         if (PULSE_LOGGING_ENABLED) {
1032             PulseLogger.newInput("SWIPE_EVENT");
1033         }
1034         WindowStage stage = scene.getWindowStage();
1035         try {
1036             if (stage != null) {
1037                 stage.setInEventHandler(true);
1038             }
1039             QuantumToolkit.runWithoutRenderLock(() -> {
1040                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1041                     if (scene.sceneListener != null) {
1042                         EventType<SwipeEvent> eventType;
1043                         switch (dir) {
1044                             case SwipeGesture.DIR_UP:
1045                                 eventType = SwipeEvent.SWIPE_UP;
1046                                 break;
1047                             case SwipeGesture.DIR_DOWN:
1048                                 eventType = SwipeEvent.SWIPE_DOWN;
1049                                 break;
1050                             case SwipeGesture.DIR_LEFT:
1051                                 eventType = SwipeEvent.SWIPE_LEFT;
1052                                 break;
1053                             case SwipeGesture.DIR_RIGHT:
1054                                 eventType = SwipeEvent.SWIPE_RIGHT;
1055                                 break;
1056                             default:
1057                                 throw new RuntimeException("Unknown swipe event direction: " + dir);
1058                         }
1059                         final Window w = view.getWindow();
1060                         double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
1061                         double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
1062                         scene.sceneListener.swipeEvent(eventType, touchCount,
1063                                 x == View.GESTURE_NO_VALUE ? Double.NaN : x / pScaleX,
1064                                 y == View.GESTURE_NO_VALUE ? Double.NaN : y / pScaleY,
1065                                 xAbs == View.GESTURE_NO_VALUE ? Double.NaN : xAbs / pScaleX,
1066                                 yAbs == View.GESTURE_NO_VALUE ? Double.NaN : yAbs / pScaleY,
1067                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
1068                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
1069                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
1070                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
1071                                 isDirect);
1072                     }
1073                     return null;
1074                 }, scene.getAccessControlContext());
1075             });
1076         } finally {
1077             if (stage != null) {
1078                 stage.setInEventHandler(false);
1079             }
1080             if (PULSE_LOGGING_ENABLED) {
1081                 PulseLogger.newInput(null);
1082             }
1083         }
1084     }
1085 
1086     @Override public void handleBeginTouchEvent(
1087             View view, final long time, final int modifiers,
1088             final boolean isDirect, final int touchEventCount)
1089     {
1090         if (PULSE_LOGGING_ENABLED) {
1091             PulseLogger.newInput("BEGIN_TOUCH_EVENT");
1092         }
1093         WindowStage stage = scene.getWindowStage();
1094         try {
1095             if (stage != null) {
1096                 stage.setInEventHandler(true);
1097             }
1098             QuantumToolkit.runWithoutRenderLock(() -> {
1099                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1100                     if (scene.sceneListener != null) {
1101                         scene.sceneListener.touchEventBegin(time, touchEventCount,
1102                                 isDirect,
1103                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
1104                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
1105                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
1106                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0);
1107                     }
1108                     return null;
1109                 }, scene.getAccessControlContext());
1110             });
1111         } finally {
1112             if (stage != null) {
1113                 stage.setInEventHandler(false);
1114             }
1115             if (PULSE_LOGGING_ENABLED) {
1116                 PulseLogger.newInput(null);
1117             }
1118         }
1119 
1120         gestures.notifyBeginTouchEvent(time, modifiers, isDirect, touchEventCount);
1121     }
1122 
1123     @Override public void handleNextTouchEvent(
1124             View view, final long time, final int type, final long touchId,
1125             final int x, final int y, final int xAbs, final int yAbs)
1126     {
1127         if (PULSE_LOGGING_ENABLED) {
1128             PulseLogger.newInput("NEXT_TOUCH_EVENT");
1129         }
1130         WindowStage stage = scene.getWindowStage();
1131         try {
1132             if (stage != null) {
1133                 stage.setInEventHandler(true);
1134             }
1135             QuantumToolkit.runWithoutRenderLock(() -> {
1136                     return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1137                     if (scene.sceneListener != null) {
1138                         TouchPoint.State state;
1139                         switch (type) {
1140                             case TouchEvent.TOUCH_PRESSED:
1141                                 state = TouchPoint.State.PRESSED;
1142                                 break;
1143                             case TouchEvent.TOUCH_MOVED:
1144                                 state = TouchPoint.State.MOVED;
1145                                 break;
1146                             case TouchEvent.TOUCH_STILL:
1147                                 state = TouchPoint.State.STATIONARY;
1148                                 break;
1149                             case TouchEvent.TOUCH_RELEASED:
1150                                 state = TouchPoint.State.RELEASED;
1151                                 break;
1152                             default:
1153                                 throw new RuntimeException("Unknown touch state: " + type);
1154                         }
1155                         final Window w = view.getWindow();
1156                         double pScaleX = (w == null) ? 1.0 : w.getPlatformScaleX();
1157                         double pScaleY = (w == null) ? 1.0 : w.getPlatformScaleY();
1158                         scene.sceneListener.touchEventNext(state, touchId,
1159                                 x / pScaleX, y / pScaleY, xAbs / pScaleX, yAbs / pScaleY);
1160                     }
1161                     return null;
1162                 }, scene.getAccessControlContext());
1163             });
1164         } finally {
1165             if (stage != null) {
1166                 stage.setInEventHandler(false);
1167             }
1168             if (PULSE_LOGGING_ENABLED) {
1169                 PulseLogger.newInput(null);
1170             }
1171         }
1172 
1173         gestures.notifyNextTouchEvent(time, type, touchId, x, y, xAbs, yAbs);
1174     }
1175 
1176     @Override public void handleEndTouchEvent(View view, long time) {
1177         if (PULSE_LOGGING_ENABLED) {
1178             PulseLogger.newInput("END_TOUCH_EVENT");
1179         }
1180         WindowStage stage = scene.getWindowStage();
1181         try {
1182             if (stage != null) {
1183                 stage.setInEventHandler(true);
1184             }
1185             QuantumToolkit.runWithoutRenderLock(() -> {
1186                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1187                     if (scene.sceneListener != null) {
1188                         scene.sceneListener.touchEventEnd();
1189                     }
1190                     return null;
1191                 }, scene.getAccessControlContext());
1192             });
1193         } finally {
1194             if (stage != null) {
1195                 stage.setInEventHandler(false);
1196             }
1197             if (PULSE_LOGGING_ENABLED) {
1198                 PulseLogger.newInput(null);
1199             }
1200         }
1201 
1202         gestures.notifyEndTouchEvent(time);
1203     }
1204 
1205     @Override
1206     public Accessible getSceneAccessible() {
1207         if (scene != null && scene.sceneListener != null) {
1208             return scene.sceneListener.getSceneAccessible();
1209         }
1210         return null;
1211     }
1212 }