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