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