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                     Window w = view.getWindow();
 350                     double pScale = (w == null) ? 1.0 : w.getPlatformScale();
 351 
 352                     scene.sceneListener.mouseEvent(mouseEventType(type),
 353                             x / pScale, y / pScale, xAbs / pScale, yAbs / pScale,
 354                             mouseEventButton(button), isPopupTrigger, isSynthesized,
 355                             shiftDown, controlDown, altDown, metaDown,
 356                             primaryButtonDown, middleButtonDown, secondaryButtonDown);
 357                 }
 358             } finally {
 359                 if (stage != null) {
 360                     stage.setInEventHandler(false);
 361                 }
 362                 if (PULSE_LOGGING_ENABLED) {
 363                     PulseLogger.newInput(null);
 364                 }
 365             }
 366             return null;
 367         }
 368     }
 369 
 370     @Override
 371     public void handleMouseEvent(View view, long time, int type, int button,
 372                                  int x, int y, int xAbs, int yAbs,
 373                                  int modifiers, boolean isPopupTrigger, boolean isSynthesized)
 374     {
 375         mouseNotification.view = view;
 376         mouseNotification.time = time;
 377         mouseNotification.type = type;
 378         mouseNotification.button = button;
 379         mouseNotification.x = x;
 380         mouseNotification.y = y;
 381         mouseNotification.xAbs = xAbs;
 382         mouseNotification.yAbs = yAbs;
 383         mouseNotification.modifiers = modifiers;
 384         mouseNotification.isPopupTrigger = isPopupTrigger;
 385         mouseNotification.isSynthesized = isSynthesized;
 386 
 387         QuantumToolkit.runWithoutRenderLock(() -> {
 388             return AccessController.doPrivileged(mouseNotification, scene.getAccessControlContext());
 389         });
 390     }
 391 
 392     @Override public void handleMenuEvent(final View view,
 393                                           final int x, final int y, final int xAbs, final int yAbs,
 394                                           final boolean isKeyboardTrigger)
 395     {
 396         if (PULSE_LOGGING_ENABLED) {
 397             PulseLogger.newInput("MENU_EVENT");
 398         }
 399         WindowStage stage = scene.getWindowStage();
 400         try {
 401             if (stage != null) {
 402                 stage.setInEventHandler(true);
 403             }
 404             QuantumToolkit.runWithoutRenderLock(() -> {
 405                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 406                     if (scene.sceneListener != null) {
 407                         Window w = view.getWindow();
 408                         double pScale = (w == null) ? 1.0 : w.getPlatformScale();
 409                         scene.sceneListener.menuEvent(x / pScale, y / pScale,
 410                                                       xAbs / pScale, yAbs / pScale,
 411                                                       isKeyboardTrigger);
 412                     }
 413                     return null;
 414                 }, scene.getAccessControlContext());
 415             });
 416         } finally {
 417             if (stage != null) {
 418                 stage.setInEventHandler(false);
 419             }
 420             if (PULSE_LOGGING_ENABLED) {
 421                 PulseLogger.newInput(null);
 422             }
 423         }
 424     }
 425 
 426     @Override public void handleScrollEvent(final View view, final long time,
 427                                             final int x, final int y, final int xAbs, final int yAbs,
 428                                             final double deltaX, final double deltaY, final int modifiers,
 429                                             final int lines, final int chars,
 430                                             final int defaultLines, final int defaultChars,
 431                                             final double xMultiplier, final double yMultiplier)
 432     {
 433         if (PULSE_LOGGING_ENABLED) {
 434             PulseLogger.newInput("SCROLL_EVENT");
 435         }
 436         WindowStage stage = scene.getWindowStage();
 437         try {
 438             if (stage != null) {
 439                 stage.setInEventHandler(true);
 440             }
 441             QuantumToolkit.runWithoutRenderLock(() -> {
 442                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 443                     if (scene.sceneListener != null) {
 444                         Window w = view.getWindow();
 445                         double pScale = (w == null) ? 1.0 : w.getPlatformScale();
 446                         scene.sceneListener.scrollEvent(ScrollEvent.SCROLL,
 447                             deltaX / pScale, deltaY / pScale, 0, 0,
 448                             xMultiplier, yMultiplier,
 449                             0, // touchCount
 450                             chars, lines, defaultChars, defaultLines,
 451                             x / pScale, y / pScale, xAbs / pScale, yAbs / pScale,
 452                             (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
 453                             (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
 454                             (modifiers & KeyEvent.MODIFIER_ALT) != 0,
 455                             (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
 456                             false, // this is always indirect
 457                             false); // this has no inertia
 458                     }
 459                     return null;
 460                 }, scene.getAccessControlContext());
 461             });
 462         } finally {
 463             if (stage != null) {
 464                 stage.setInEventHandler(false);
 465             }
 466             if (PULSE_LOGGING_ENABLED) {
 467                 PulseLogger.newInput(null);
 468             }
 469         }
 470     }
 471 
 472     private static byte inputMethodEventAttrValue(int pos, int[] attrBoundary, byte[] attrValue) {
 473         if (attrBoundary != null) {
 474             for (int current = 0; current < attrBoundary.length-1; current++) {
 475                 if (pos >= attrBoundary[current] &&
 476                     pos < attrBoundary[current+1]) {
 477                     return attrValue[current];
 478                 }
 479             }
 480         }
 481         return View.IME_ATTR_INPUT_ERROR;
 482     }
 483 
 484     private static ObservableList<InputMethodTextRun> inputMethodEventComposed(
 485             String text, int commitCount, int[] clauseBoundary, int[] attrBoundary, byte[] attrValue)
 486     {
 487         ObservableList<InputMethodTextRun> composed = new TrackableObservableList<InputMethodTextRun>() {
 488             @Override
 489             protected void onChanged(ListChangeListener.Change<InputMethodTextRun> c) {
 490             }
 491         };
 492 
 493         if (commitCount < text.length()) {
 494             if (clauseBoundary == null) {
 495                 // Create one single segment as UNSELECTED_RAW
 496                 composed.add(new InputMethodTextRun(
 497                         text.substring(commitCount),
 498                         InputMethodHighlight.UNSELECTED_RAW));
 499             } else {
 500                 for (int current = 0; current < clauseBoundary.length-1; current++) {
 501                     if (clauseBoundary[current] < commitCount) {
 502                         continue;
 503                     }
 504 
 505                     InputMethodHighlight highlight;
 506                     switch (inputMethodEventAttrValue(clauseBoundary[current], attrBoundary, attrValue)) {
 507                         case View.IME_ATTR_TARGET_CONVERTED:
 508                             highlight = InputMethodHighlight.SELECTED_CONVERTED;
 509                             break;
 510                         case View.IME_ATTR_CONVERTED:
 511                             highlight = InputMethodHighlight.UNSELECTED_CONVERTED;
 512                             break;
 513                         case View.IME_ATTR_TARGET_NOTCONVERTED:
 514                             highlight = InputMethodHighlight.SELECTED_RAW;
 515                             break;
 516                         case View.IME_ATTR_INPUT:
 517                         case View.IME_ATTR_INPUT_ERROR:
 518                         default:
 519                             highlight = InputMethodHighlight.UNSELECTED_RAW;
 520                             break;
 521                     }
 522                     composed.add(new InputMethodTextRun(
 523                             text.substring(clauseBoundary[current],
 524                                            clauseBoundary[current+1]),
 525                             highlight));
 526                 }
 527             }
 528         }
 529         return composed;
 530     }
 531 
 532     @Override public void handleInputMethodEvent(final long time, final String text,
 533                                                  final int[] clauseBoundary,
 534                                                  final int[] attrBoundary, final byte[] attrValue,
 535                                                  final int commitCount, final int cursorPos)
 536     {
 537         if (PULSE_LOGGING_ENABLED) {
 538             PulseLogger.newInput("INPUT_METHOD_EVENT");
 539         }
 540         WindowStage stage = scene.getWindowStage();
 541         try {
 542             if (stage != null) {
 543                 stage.setInEventHandler(true);
 544             }
 545             QuantumToolkit.runWithoutRenderLock(() -> {
 546                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 547                     if (scene.sceneListener != null) {
 548                         String t = text != null ? text : "";
 549                         EventType<InputMethodEvent> eventType =
 550                                 InputMethodEvent.INPUT_METHOD_TEXT_CHANGED;
 551                         ObservableList<InputMethodTextRun> composed = inputMethodEventComposed(
 552                                 t, commitCount, clauseBoundary, attrBoundary, attrValue);
 553                         String committed = t.substring(0, commitCount);
 554                         scene.sceneListener.inputMethodEvent(eventType, composed, committed, cursorPos);
 555                     }
 556                     return null;
 557                 }, scene.getAccessControlContext());
 558             });
 559         } finally {
 560             if (stage != null) {
 561                 stage.setInEventHandler(false);
 562             }
 563             if (PULSE_LOGGING_ENABLED) {
 564                 PulseLogger.newInput(null);
 565             }
 566         }
 567     }
 568 
 569     @Override
 570     public double[] getInputMethodCandidatePos(int offset) {
 571         Point2D p2d = scene.inputMethodRequests.getTextLocation(offset);
 572         double[] ret = new double[2];
 573         ret[0] = p2d.getX();
 574         ret[1] = p2d.getY();
 575         return ret;
 576     }
 577 
 578     private static TransferMode actionToTransferMode(int dropActions) {
 579         if (dropActions == Clipboard.ACTION_NONE) {
 580             return null;
 581         } else if (dropActions == Clipboard.ACTION_COPY
 582                 //IE drop action for URL copy;  XXX: should be fixed in Glass
 583                 || dropActions == (Clipboard.ACTION_COPY | Clipboard.ACTION_REFERENCE) )
 584         {
 585             return TransferMode.COPY;
 586         } else if (dropActions == Clipboard.ACTION_MOVE
 587                 //IE drop action for URL move;  XXX: should be fixed in Glass
 588                 || dropActions == (Clipboard.ACTION_MOVE | Clipboard.ACTION_REFERENCE) )
 589         {
 590             return TransferMode.MOVE;
 591         } else if (dropActions == Clipboard.ACTION_REFERENCE) {
 592             return TransferMode.LINK;
 593         } else if (dropActions == Clipboard.ACTION_COPY_OR_MOVE) {
 594             if (QuantumToolkit.verbose) {
 595                 System.err.println("Ambiguous drop action: " + Integer.toHexString(dropActions));
 596             }
 597         } else {
 598             if (QuantumToolkit.verbose) {
 599                 System.err.println("Unknown drop action: " + Integer.toHexString(dropActions));
 600             }
 601         }
 602         return null;
 603     }
 604 
 605     private static int transferModeToAction(TransferMode tm) {
 606         if (tm == null) {
 607             return Clipboard.ACTION_NONE;
 608         }
 609 
 610         switch (tm) {
 611             case COPY:
 612                 return Clipboard.ACTION_COPY;
 613             case MOVE:
 614                 return Clipboard.ACTION_MOVE;
 615             case LINK:
 616                 return Clipboard.ACTION_REFERENCE;
 617             default:
 618                 return Clipboard.ACTION_NONE;
 619         }
 620     }
 621 
 622     @Override public int handleDragEnter(View view,
 623                                          final int x, final int y, final int xAbs, final int yAbs,
 624                                          final int recommendedDropAction,
 625                                          final ClipboardAssistance dropTargetAssistant)
 626     {
 627         if (PULSE_LOGGING_ENABLED) {
 628             PulseLogger.newInput("DRAG_ENTER");
 629         }
 630         TransferMode action;
 631         try {
 632             action = QuantumToolkit.runWithoutRenderLock(() -> {
 633                 return dndHandler.handleDragEnter(x, y, xAbs, yAbs,
 634                         actionToTransferMode(recommendedDropAction),
 635                         dropTargetAssistant);
 636             });
 637         } finally {
 638             if (PULSE_LOGGING_ENABLED) {
 639                 PulseLogger.newInput(null);
 640             }
 641         }
 642         return transferModeToAction(action);
 643     }
 644 
 645     @Override public void handleDragLeave(View view, final ClipboardAssistance dropTargetAssistant) {
 646         if (PULSE_LOGGING_ENABLED) {
 647             PulseLogger.newInput("DRAG_LEAVE");
 648         }
 649         try {
 650             QuantumToolkit.runWithoutRenderLock(() -> {
 651                 dndHandler.handleDragLeave(dropTargetAssistant);
 652                 return null;
 653             });
 654         } finally {
 655             if (PULSE_LOGGING_ENABLED) {
 656                 PulseLogger.newInput(null);
 657             }
 658         }
 659     }
 660 
 661     @Override public int handleDragDrop(View view,
 662                                         final int x, final int y, final int xAbs, final int yAbs,
 663                                         final int recommendedDropAction,
 664                                         final ClipboardAssistance dropTargetAssistant)
 665     {
 666         if (PULSE_LOGGING_ENABLED) {
 667             PulseLogger.newInput("DRAG_DROP");
 668         }
 669         TransferMode action;
 670         try {
 671             action = QuantumToolkit.runWithoutRenderLock(() -> {
 672                 return dndHandler.handleDragDrop(x, y, xAbs, yAbs,
 673                     actionToTransferMode(recommendedDropAction),
 674                     dropTargetAssistant);
 675             });
 676         } finally {
 677             if (PULSE_LOGGING_ENABLED) {
 678                 PulseLogger.newInput(null);
 679             }
 680         }
 681         return transferModeToAction(action);
 682     }
 683 
 684     @Override public int handleDragOver(View view,
 685                                         final int x, final int y, final int xAbs, final int yAbs,
 686                                         final int recommendedDropAction,
 687                                         final ClipboardAssistance dropTargetAssistant)
 688     {
 689         if (PULSE_LOGGING_ENABLED) {
 690             PulseLogger.newInput("DRAG_OVER");
 691         }
 692         TransferMode action;
 693         try {
 694             action = QuantumToolkit.runWithoutRenderLock(() -> {
 695                 return dndHandler.handleDragOver(x, y, xAbs, yAbs,
 696                     actionToTransferMode(recommendedDropAction),
 697                     dropTargetAssistant);
 698             });
 699         } finally {
 700             if (PULSE_LOGGING_ENABLED) {
 701                 PulseLogger.newInput(null);
 702             }
 703         }
 704         return transferModeToAction(action);
 705     }
 706 
 707     private ClipboardAssistance dropSourceAssistant;
 708 
 709     @Override public void handleDragStart(View view, final int button,
 710                                           final int x, final int y, final int xAbs, final int yAbs,
 711                                           final ClipboardAssistance assistant)
 712     {
 713         if (PULSE_LOGGING_ENABLED) {
 714             PulseLogger.newInput("DRAG_START");
 715         }
 716         dropSourceAssistant = assistant;
 717         try {
 718             QuantumToolkit.runWithoutRenderLock(() -> {
 719                 dndHandler.handleDragStart(button, x, y, xAbs, yAbs, assistant);
 720                 return null;
 721             });
 722         } finally {
 723             if (PULSE_LOGGING_ENABLED) {
 724                 PulseLogger.newInput(null);
 725             }
 726         }
 727     }
 728 
 729     @Override public void handleDragEnd(View view, final int performedAction) {
 730         if (PULSE_LOGGING_ENABLED) {
 731             PulseLogger.newInput("DRAG_END");
 732         }
 733         try {
 734             QuantumToolkit.runWithoutRenderLock(() -> {
 735                 dndHandler.handleDragEnd(actionToTransferMode(performedAction), dropSourceAssistant);
 736                 return null;
 737             });
 738         } finally {
 739             if (PULSE_LOGGING_ENABLED) {
 740                 PulseLogger.newInput(null);
 741             }
 742         }
 743     }
 744 
 745     // TODO - dropTargetListener.dropActionChanged
 746     // TODO - dragSourceListener.dropActionChanged
 747 
 748     private final ViewEventNotification viewNotification = new ViewEventNotification();
 749     private class ViewEventNotification implements PrivilegedAction<Void> {
 750         View view;
 751         long time;
 752         int type;
 753 
 754         @Override
 755         public Void run() {
 756             if (scene.sceneListener == null) {
 757                 return null;
 758             }
 759             switch (type) {
 760                 case ViewEvent.REPAINT: {
 761                     Window w = view.getWindow();
 762                     if (w != null && w.getMinimumWidth() == view.getWidth() && !w.isVisible()) {
 763                         // RT-21057 - ignore initial minimum size setting if not visible
 764                         break;
 765                     }
 766                     if (QuantumToolkit.drawInPaint && w != null && w.isVisible()) {
 767                         WindowStage stage = scene.getWindowStage();
 768                         if (stage != null && !stage.isApplet()) {
 769                             collector.liveRepaintRenderJob(scene);
 770                         }
 771                     }
 772                     scene.entireSceneNeedsRepaint();
 773                     break;
 774                 }
 775                 case ViewEvent.RESIZE: {
 776                     Window w = view.getWindow();
 777                     float pScale = (w == null) ? 1.0f : w.getPlatformScale();
 778                     scene.sceneListener.changedSize(view.getWidth()  / pScale,
 779                                                     view.getHeight() / pScale);
 780                     scene.entireSceneNeedsRepaint();
 781                     QuantumToolkit.runWithRenderLock(() -> {
 782                         scene.updateSceneState();
 783                         return null;
 784                     });
 785                     if (QuantumToolkit.liveResize && w != null && w.isVisible()) {
 786                         WindowStage stage = scene.getWindowStage();
 787                         if (stage != null && !stage.isApplet()) {
 788                             collector.liveRepaintRenderJob(scene);
 789                         }
 790                     }
 791                     break;
 792                 }
 793                 case ViewEvent.MOVE: {
 794                     // MOVE events can be "synthesized" and the window will
 795                     // be null if this is synthesized during a "REMOVE" event
 796                     Window w = view.getWindow();
 797                     float pScale = (w == null) ? 1.0f : w.getPlatformScale();
 798                     scene.sceneListener.changedLocation(view.getX() / pScale,
 799                                                         view.getY() / pScale);
 800                     break;
 801                 }
 802                 case ViewEvent.FULLSCREEN_ENTER:
 803                 case ViewEvent.FULLSCREEN_EXIT:
 804                     if (scene.getWindowStage() != null) {
 805                         scene.getWindowStage().fullscreenChanged(type == ViewEvent.FULLSCREEN_ENTER);
 806                     }
 807                     break;
 808                 case ViewEvent.ADD:
 809                 case ViewEvent.REMOVE:
 810                     // unhandled
 811                     break;
 812                 default:
 813                     throw new RuntimeException("handleViewEvent: unhandled type: " + type);
 814             }
 815             return null;
 816         }
 817     }
 818 
 819     @Override public void handleViewEvent(View view, long time, final int type) {
 820         if (PULSE_LOGGING_ENABLED) {
 821             PulseLogger.newInput("VIEW_EVENT: "+ViewEvent.getTypeString(type));
 822         }
 823         viewNotification.view = view;
 824         viewNotification.time = time;
 825         viewNotification.type = type;
 826         try {
 827             QuantumToolkit.runWithoutRenderLock(() -> {
 828                 return AccessController.doPrivileged(viewNotification, scene.getAccessControlContext());
 829             });
 830         }
 831         finally {
 832             if (PULSE_LOGGING_ENABLED) {
 833                 PulseLogger.newInput(null);
 834             }
 835         }
 836     }
 837 
 838     @Override public void handleScrollGestureEvent(
 839             View view, final long time, final int type,
 840             final int modifiers, final boolean isDirect, final boolean isInertia, final int touchCount,
 841             final int x, final int y, final int xAbs, final int yAbs, final double dx, final double dy,
 842             final double totaldx, final double totaldy, final double multiplierX, final double multiplierY)
 843     {
 844         if (PULSE_LOGGING_ENABLED) {
 845             PulseLogger.newInput("SCROLL_GESTURE_EVENT");
 846         }
 847         WindowStage stage = scene.getWindowStage();
 848         try {
 849             if (stage != null) {
 850                 stage.setInEventHandler(true);
 851             }
 852             QuantumToolkit.runWithoutRenderLock(() -> {
 853                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 854                     if (scene.sceneListener != null) {
 855                         EventType<ScrollEvent> eventType;
 856                         switch(type) {
 857                             case GestureEvent.GESTURE_STARTED:
 858                                 eventType = ScrollEvent.SCROLL_STARTED;
 859                                 break;
 860                             case GestureEvent.GESTURE_PERFORMED:
 861                                 eventType = ScrollEvent.SCROLL;
 862                                 break;
 863                             case GestureEvent.GESTURE_FINISHED:
 864                                 eventType = ScrollEvent.SCROLL_FINISHED;
 865                                 break;
 866                             default:
 867                                 throw new RuntimeException("Unknown scroll event type: " + type);
 868                         }
 869                         Window w = view.getWindow();
 870                         double pScale = (w == null) ? 1.0 : w.getPlatformScale();
 871                         scene.sceneListener.scrollEvent(eventType,
 872                                 dx / pScale, dy / pScale, totaldx / pScale, totaldy / pScale,
 873                                 multiplierX, multiplierY,
 874                                 touchCount,
 875                                 0, 0, 0, 0,
 876                                 x == View.GESTURE_NO_VALUE ? Double.NaN : x / pScale,
 877                                 y == View.GESTURE_NO_VALUE ? Double.NaN : y / pScale,
 878                                 xAbs == View.GESTURE_NO_VALUE ? Double.NaN : xAbs / pScale,
 879                                 yAbs == View.GESTURE_NO_VALUE ? Double.NaN : yAbs / pScale,
 880                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
 881                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
 882                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
 883                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
 884                                 isDirect, isInertia);
 885                     }
 886                     return null;
 887                 }, scene.getAccessControlContext());
 888             });
 889         } finally {
 890             if (stage != null) {
 891                 stage.setInEventHandler(false);
 892             }
 893             if (PULSE_LOGGING_ENABLED) {
 894                 PulseLogger.newInput(null);
 895             }
 896         }
 897     }
 898 
 899     @Override public void handleZoomGestureEvent(
 900             View view, final long time, final int type,
 901             final int modifiers, final boolean isDirect, final boolean isInertia,
 902             final int originx, final int originy,
 903             final int originxAbs, final int originyAbs,
 904             final double scale, double expansion,
 905             final double totalscale, double totalexpansion)
 906     {
 907         if (PULSE_LOGGING_ENABLED) {
 908             PulseLogger.newInput("ZOOM_GESTURE_EVENT");
 909         }
 910         WindowStage stage = scene.getWindowStage();
 911         try {
 912             if (stage != null) {
 913                 stage.setInEventHandler(true);
 914             }
 915             QuantumToolkit.runWithoutRenderLock(() -> {
 916                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 917                     if (scene.sceneListener != null) {
 918                         EventType<ZoomEvent> eventType;
 919                         switch (type) {
 920                             case GestureEvent.GESTURE_STARTED:
 921                                 eventType = ZoomEvent.ZOOM_STARTED;
 922                                 break;
 923                             case GestureEvent.GESTURE_PERFORMED:
 924                                 eventType = ZoomEvent.ZOOM;
 925                                 break;
 926                             case GestureEvent.GESTURE_FINISHED:
 927                                 eventType = ZoomEvent.ZOOM_FINISHED;
 928                                 break;
 929                             default:
 930                                 throw new RuntimeException("Unknown scroll event type: " + type);
 931                         }
 932                         Window w = view.getWindow();
 933                         double pScale = (w == null) ? 1.0 : w.getPlatformScale();
 934                         // REMIND: Scale the [total]scale params too?
 935                         scene.sceneListener.zoomEvent(eventType, scale, totalscale,
 936                                 originx == View.GESTURE_NO_VALUE ? Double.NaN : originx / pScale,
 937                                 originy == View.GESTURE_NO_VALUE ? Double.NaN : originy / pScale,
 938                                 originxAbs == View.GESTURE_NO_VALUE ? Double.NaN : originxAbs / pScale,
 939                                 originyAbs == View.GESTURE_NO_VALUE ? Double.NaN : originyAbs / pScale,
 940                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
 941                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
 942                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
 943                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
 944                                 isDirect, isInertia);
 945                     }
 946                     return null;
 947                 }, scene.getAccessControlContext());
 948             });
 949         } finally {
 950             if (stage != null) {
 951                 stage.setInEventHandler(false);
 952             }
 953             if (PULSE_LOGGING_ENABLED) {
 954                 PulseLogger.newInput(null);
 955             }
 956         }
 957     }
 958 
 959     @Override public void handleRotateGestureEvent(
 960             View view, final long time, final int type,
 961             final int modifiers, final boolean isDirect, final boolean isInertia,
 962             final int originx, final int originy,
 963             final int originxAbs, final int originyAbs,
 964             final double dangle, final double totalangle)
 965     {
 966         if (PULSE_LOGGING_ENABLED) {
 967             PulseLogger.newInput("ROTATE_GESTURE_EVENT");
 968         }
 969         WindowStage stage = scene.getWindowStage();
 970         try {
 971             if (stage != null) {
 972                 stage.setInEventHandler(true);
 973             }
 974             QuantumToolkit.runWithoutRenderLock(() -> {
 975                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
 976                     if (scene.sceneListener != null) {
 977                         EventType<RotateEvent> eventType;
 978                         switch (type) {
 979                             case GestureEvent.GESTURE_STARTED:
 980                                 eventType = RotateEvent.ROTATION_STARTED;
 981                                 break;
 982                             case GestureEvent.GESTURE_PERFORMED:
 983                                 eventType = RotateEvent.ROTATE;
 984                                 break;
 985                             case GestureEvent.GESTURE_FINISHED:
 986                                 eventType = RotateEvent.ROTATION_FINISHED;
 987                                 break;
 988                             default:
 989                                 throw new RuntimeException("Unknown scroll event type: " + type);
 990                         }
 991                         Window w = view.getWindow();
 992                         double pScale = (w == null) ? 1.0 : w.getPlatformScale();
 993                         scene.sceneListener.rotateEvent(eventType, dangle, totalangle,
 994                                 originx == View.GESTURE_NO_VALUE ? Double.NaN : originx / pScale,
 995                                 originy == View.GESTURE_NO_VALUE ? Double.NaN : originy / pScale,
 996                                 originxAbs == View.GESTURE_NO_VALUE ? Double.NaN : originxAbs / pScale,
 997                                 originyAbs == View.GESTURE_NO_VALUE ? Double.NaN : originyAbs / pScale,
 998                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
 999                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
1000                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
1001                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
1002                                 isDirect, isInertia);
1003                     }
1004                     return null;
1005                 }, scene.getAccessControlContext());
1006             });
1007         } finally {
1008             if (stage != null) {
1009                 stage.setInEventHandler(false);
1010             }
1011             if (PULSE_LOGGING_ENABLED) {
1012                 PulseLogger.newInput(null);
1013             }
1014         }
1015     }
1016 
1017     @Override public void handleSwipeGestureEvent(
1018             View view, final long time, int type,
1019             final int modifiers, final boolean isDirect,
1020             boolean isInertia, final int touchCount,
1021             final int dir, final int x, final int y, final int xAbs, final int yAbs)
1022     {
1023         if (PULSE_LOGGING_ENABLED) {
1024             PulseLogger.newInput("SWIPE_EVENT");
1025         }
1026         WindowStage stage = scene.getWindowStage();
1027         try {
1028             if (stage != null) {
1029                 stage.setInEventHandler(true);
1030             }
1031             QuantumToolkit.runWithoutRenderLock(() -> {
1032                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1033                     if (scene.sceneListener != null) {
1034                         EventType<SwipeEvent> eventType;
1035                         switch (dir) {
1036                             case SwipeGesture.DIR_UP:
1037                                 eventType = SwipeEvent.SWIPE_UP;
1038                                 break;
1039                             case SwipeGesture.DIR_DOWN:
1040                                 eventType = SwipeEvent.SWIPE_DOWN;
1041                                 break;
1042                             case SwipeGesture.DIR_LEFT:
1043                                 eventType = SwipeEvent.SWIPE_LEFT;
1044                                 break;
1045                             case SwipeGesture.DIR_RIGHT:
1046                                 eventType = SwipeEvent.SWIPE_RIGHT;
1047                                 break;
1048                             default:
1049                                 throw new RuntimeException("Unknown swipe event direction: " + dir);
1050                         }
1051                         Window w = view.getWindow();
1052                         double pScale = (w == null) ? 1.0 : w.getPlatformScale();
1053                         scene.sceneListener.swipeEvent(eventType, touchCount,
1054                                 x == View.GESTURE_NO_VALUE ? Double.NaN : x / pScale,
1055                                 y == View.GESTURE_NO_VALUE ? Double.NaN : y / pScale,
1056                                 xAbs == View.GESTURE_NO_VALUE ? Double.NaN : xAbs / pScale,
1057                                 yAbs == View.GESTURE_NO_VALUE ? Double.NaN : yAbs / pScale,
1058                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
1059                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
1060                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
1061                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0,
1062                                 isDirect);
1063                     }
1064                     return null;
1065                 }, scene.getAccessControlContext());
1066             });
1067         } finally {
1068             if (stage != null) {
1069                 stage.setInEventHandler(false);
1070             }
1071             if (PULSE_LOGGING_ENABLED) {
1072                 PulseLogger.newInput(null);
1073             }
1074         }
1075     }
1076 
1077     @Override public void handleBeginTouchEvent(
1078             View view, final long time, final int modifiers,
1079             final boolean isDirect, final int touchEventCount)
1080     {
1081         if (PULSE_LOGGING_ENABLED) {
1082             PulseLogger.newInput("BEGIN_TOUCH_EVENT");
1083         }
1084         WindowStage stage = scene.getWindowStage();
1085         try {
1086             if (stage != null) {
1087                 stage.setInEventHandler(true);
1088             }
1089             QuantumToolkit.runWithoutRenderLock(() -> {
1090                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1091                     if (scene.sceneListener != null) {
1092                         scene.sceneListener.touchEventBegin(time, touchEventCount,
1093                                 isDirect,
1094                                 (modifiers & KeyEvent.MODIFIER_SHIFT) != 0,
1095                                 (modifiers & KeyEvent.MODIFIER_CONTROL) != 0,
1096                                 (modifiers & KeyEvent.MODIFIER_ALT) != 0,
1097                                 (modifiers & KeyEvent.MODIFIER_WINDOWS) != 0);
1098                     }
1099                     return null;
1100                 }, scene.getAccessControlContext());
1101             });
1102         } finally {
1103             if (stage != null) {
1104                 stage.setInEventHandler(false);
1105             }
1106             if (PULSE_LOGGING_ENABLED) {
1107                 PulseLogger.newInput(null);
1108             }
1109         }
1110 
1111         gestures.notifyBeginTouchEvent(time, modifiers, isDirect, touchEventCount);
1112     }
1113 
1114     @Override public void handleNextTouchEvent(
1115             View view, final long time, final int type, final long touchId,
1116             final int x, final int y, final int xAbs, final int yAbs)
1117     {
1118         if (PULSE_LOGGING_ENABLED) {
1119             PulseLogger.newInput("NEXT_TOUCH_EVENT");
1120         }
1121         WindowStage stage = scene.getWindowStage();
1122         try {
1123             if (stage != null) {
1124                 stage.setInEventHandler(true);
1125             }
1126             QuantumToolkit.runWithoutRenderLock(() -> {
1127                     return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1128                     if (scene.sceneListener != null) {
1129                         TouchPoint.State state;
1130                         switch (type) {
1131                             case TouchEvent.TOUCH_PRESSED:
1132                                 state = TouchPoint.State.PRESSED;
1133                                 break;
1134                             case TouchEvent.TOUCH_MOVED:
1135                                 state = TouchPoint.State.MOVED;
1136                                 break;
1137                             case TouchEvent.TOUCH_STILL:
1138                                 state = TouchPoint.State.STATIONARY;
1139                                 break;
1140                             case TouchEvent.TOUCH_RELEASED:
1141                                 state = TouchPoint.State.RELEASED;
1142                                 break;
1143                             default:
1144                                 throw new RuntimeException("Unknown touch state: " + type);
1145                         }
1146                         Window w = view.getWindow();
1147                         double pScale = (w == null) ? 1.0 : w.getPlatformScale();
1148                         scene.sceneListener.touchEventNext(state, touchId,
1149                                 x / pScale, y / pScale, xAbs / pScale, yAbs / pScale);
1150                     }
1151                     return null;
1152                 }, scene.getAccessControlContext());
1153             });
1154         } finally {
1155             if (stage != null) {
1156                 stage.setInEventHandler(false);
1157             }
1158             if (PULSE_LOGGING_ENABLED) {
1159                 PulseLogger.newInput(null);
1160             }
1161         }
1162 
1163         gestures.notifyNextTouchEvent(time, type, touchId, x, y, xAbs, yAbs);
1164     }
1165 
1166     @Override public void handleEndTouchEvent(View view, long time) {
1167         if (PULSE_LOGGING_ENABLED) {
1168             PulseLogger.newInput("END_TOUCH_EVENT");
1169         }
1170         WindowStage stage = scene.getWindowStage();
1171         try {
1172             if (stage != null) {
1173                 stage.setInEventHandler(true);
1174             }
1175             QuantumToolkit.runWithoutRenderLock(() -> {
1176                 return AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1177                     if (scene.sceneListener != null) {
1178                         scene.sceneListener.touchEventEnd();
1179                     }
1180                     return null;
1181                 }, scene.getAccessControlContext());
1182             });
1183         } finally {
1184             if (stage != null) {
1185                 stage.setInEventHandler(false);
1186             }
1187             if (PULSE_LOGGING_ENABLED) {
1188                 PulseLogger.newInput(null);
1189             }
1190         }
1191 
1192         gestures.notifyEndTouchEvent(time);
1193     }
1194 
1195     @Override
1196     public Accessible getSceneAccessible() {
1197         if (scene != null && scene.sceneListener != null) {
1198             return scene.sceneListener.getSceneAccessible();
1199         }
1200         return null;
1201     }
1202 }