1 /*
   2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene;
  27 
  28 import com.sun.glass.ui.Application;
  29 import com.sun.glass.ui.Accessible;
  30 import com.sun.javafx.util.Logging;
  31 import com.sun.javafx.util.Utils;
  32 import com.sun.javafx.application.PlatformImpl;
  33 import com.sun.javafx.collections.TrackableObservableList;
  34 import com.sun.javafx.css.StyleManager;
  35 import com.sun.javafx.cursor.CursorFrame;
  36 import com.sun.javafx.event.EventQueue;
  37 import com.sun.javafx.geom.PickRay;
  38 import com.sun.javafx.geom.Vec3d;
  39 import com.sun.javafx.geom.transform.BaseTransform;
  40 import com.sun.javafx.perf.PerformanceTracker;
  41 import com.sun.javafx.robot.impl.FXRobotHelper;
  42 import com.sun.javafx.runtime.SystemProperties;
  43 import com.sun.javafx.scene.CssFlags;
  44 import com.sun.javafx.scene.LayoutFlags;
  45 import com.sun.javafx.scene.SceneEventDispatcher;
  46 import com.sun.javafx.scene.SceneHelper;
  47 import com.sun.javafx.scene.input.DragboardHelper;
  48 import com.sun.javafx.scene.input.ExtendedInputMethodRequests;
  49 import com.sun.javafx.scene.input.InputEventUtils;
  50 import com.sun.javafx.scene.input.PickResultChooser;
  51 import com.sun.javafx.scene.traversal.Direction;
  52 import com.sun.javafx.scene.traversal.SceneTraversalEngine;
  53 import com.sun.javafx.scene.traversal.TopMostTraversalEngine;
  54 import com.sun.javafx.sg.prism.NGCamera;
  55 import com.sun.javafx.sg.prism.NGLightBase;
  56 import com.sun.javafx.tk.*;
  57 import com.sun.prism.impl.PrismSettings;
  58 
  59 import javafx.animation.KeyFrame;
  60 import javafx.animation.Timeline;
  61 import javafx.application.ConditionalFeature;
  62 import javafx.application.Platform;
  63 import javafx.beans.DefaultProperty;
  64 import javafx.beans.InvalidationListener;
  65 import javafx.beans.NamedArg;
  66 import javafx.beans.property.*;
  67 import javafx.collections.FXCollections;
  68 import javafx.collections.ListChangeListener.Change;
  69 import javafx.collections.ObservableList;
  70 import javafx.collections.ObservableMap;
  71 import javafx.css.CssMetaData;
  72 import javafx.css.StyleableObjectProperty;
  73 import javafx.event.*;
  74 import javafx.geometry.*;
  75 import javafx.scene.image.WritableImage;
  76 import javafx.scene.input.*;
  77 import javafx.scene.paint.Color;
  78 import javafx.scene.paint.Paint;
  79 import javafx.stage.PopupWindow;
  80 import javafx.stage.Stage;
  81 import javafx.stage.StageStyle;
  82 import javafx.stage.Window;
  83 import javafx.util.Callback;
  84 import javafx.util.Duration;
  85 import sun.util.logging.PlatformLogger;
  86 import sun.util.logging.PlatformLogger.Level;
  87 
  88 import java.security.AccessControlContext;
  89 import java.security.AccessController;
  90 import java.security.PrivilegedAction;
  91 import java.util.*;
  92 import java.util.concurrent.CopyOnWriteArrayList;
  93 
  94 import com.sun.javafx.logging.PulseLogger;
  95 
  96 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
  97 import com.sun.javafx.scene.input.ClipboardHelper;
  98 import com.sun.javafx.scene.input.TouchPointHelper;
  99 
 100 /**
 101  * The JavaFX {@code Scene} class is the container for all content in a scene graph.
 102  * The background of the scene is filled as specified by the {@code fill} property.
 103  * <p>
 104  * The application must specify the root {@code Node} for the scene graph by setting
 105  * the {@code root} property.   If a {@code Group} is used as the root, the
 106  * contents of the scene graph will be clipped by the scene's width and height and
 107  * changes to the scene's size (if user resizes the stage) will not alter the
 108  * layout of the scene graph.    If a resizable node (layout {@code Region} or
 109  * {@code Control} is set as the root, then the root's size will track the
 110  * scene's size, causing the contents to be relayed out as necessary.
 111  * <p>
 112  * The scene's size may be initialized by the application during construction.
 113  * If no size is specified, the scene will automatically compute its initial
 114  * size based on the preferred size of its content. If only one dimension is specified,
 115  * the other dimension is computed using the specified dimension, respecting content bias
 116  * of a root.
 117  * <p>
 118  * An application may request depth buffer support or scene anti-aliasing
 119  * support at the creation of a {@code Scene}. A scene with only 2D shapes and
 120  * without any 3D transforms does not need a depth buffer nor scene
 121  * anti-aliasing support. A scene containing 3D shapes or 2D shapes with 3D
 122  * transforms may use depth buffer support for proper depth sorted rendering; to
 123  * avoid depth fighting (also known as Z fighting), disable depth testing on 2D
 124  * shapes that have no 3D transforms. See
 125  * {@link Node#depthTestProperty depthTest} for more information. A scene with
 126  * 3D shapes may enable scene anti-aliasing to improve its rendering quality.
 127  * <p>
 128  * The depthBuffer and antiAliasing flags are conditional features. With the
 129  * respective default values of: false and {@code SceneAntialiasing.DISABLED}.
 130  * See {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 131  * for more information.
 132  * <p>
 133  * A default headlight will be added to a scene that contains one or more
 134  * {@code Shape3D} nodes, but no light nodes. This light source is a
 135  * {@code Color.WHITE} {@code PointLight} placed at the camera position.
 136  *
 137  * <p>
 138  * Scene objects must be constructed and modified on the
 139  * JavaFX Application Thread.
 140  * </p>
 141  *
 142  * <p>Example:</p>
 143  *
 144  * <p>
 145  * <pre>
 146 import javafx.scene.*;
 147 import javafx.scene.paint.*;
 148 import javafx.scene.shape.*;
 149 
 150 Group root = new Group();
 151 Scene s = new Scene(root, 300, 300, Color.BLACK);
 152 
 153 Rectangle r = new Rectangle(25,25,250,250);
 154 r.setFill(Color.BLUE);
 155 
 156 root.getChildren().add(r);
 157  * </pre>
 158  * </p>
 159  * @since JavaFX 2.0
 160  */
 161 @DefaultProperty("root")
 162 public class Scene implements EventTarget {
 163 
 164     private double widthSetByUser = -1.0;
 165     private double heightSetByUser = -1.0;
 166     private boolean sizeInitialized = false;
 167     private final boolean depthBuffer;
 168     private final SceneAntialiasing antiAliasing;
 169 
 170     private int dirtyBits;
 171 
 172     final AccessControlContext acc = AccessController.getContext();
 173 
 174     private Camera defaultCamera;
 175 
 176     /**
 177      * A node that is temporarily responsible for the FOCUS_NODE
 178      * accessibility attribute. E.g. a currently active MenuBar.
 179      */
 180     private Node transientFocusContainer;
 181 
 182     //Neither width nor height are initialized and will be calculated according to content when this Scene
 183     //is shown for the first time.
 184 //    public Scene() {
 185 //        //this(-1, -1, (Parent) new Group());
 186 //        this(-1, -1, (Parent)null);
 187 //    }
 188 
 189     /**
 190      * Creates a Scene for a specific root Node.
 191      *
 192      * @param root The root node of the scene graph
 193      *
 194      * @throws NullPointerException if root is null
 195      */
 196     public Scene(@NamedArg("root") Parent root) {
 197         this(root, -1, -1, Color.WHITE, false, SceneAntialiasing.DISABLED);
 198     }
 199 
 200 //Public constructor initializing public-init properties
 201 //When width < 0, and or height < 0 is passed, then width and/or height are understood as unitialized
 202 //Unitialized dimension is calculated when Scene is shown for the first time.
 203 //    public Scene(
 204 //            @Default("-1") double width,
 205 //            @Default("-1") double height) {
 206 //        //this(width, height, (Parent)new Group());
 207 //        this(width, height, (Parent)null);
 208 //    }
 209 //
 210 //    public Scene(double width, double height, Paint fill) {
 211 //        //this(width, height, (Parent) new Group());
 212 //        this(width, height, (Parent)null);
 213 //        setFill(fill);
 214 //    }
 215 
 216     /**
 217      * Creates a Scene for a specific root Node with a specific size.
 218      *
 219      * @param root The root node of the scene graph
 220      * @param width The width of the scene
 221      * @param height The height of the scene
 222      *
 223      * @throws NullPointerException if root is null
 224      */
 225     public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height) {
 226         this(root, width, height, Color.WHITE, false, SceneAntialiasing.DISABLED);
 227     }
 228 
 229     /**
 230      * Creates a Scene for a specific root Node with a fill.
 231      *
 232      * @param root The parent
 233      * @param fill The fill
 234      *
 235      * @throws NullPointerException if root is null
 236      */
 237     public Scene(@NamedArg("root") Parent root, @NamedArg(value="fill", defaultValue="WHITE") Paint fill) {
 238         this(root, -1, -1, fill, false, SceneAntialiasing.DISABLED);
 239     }
 240 
 241     /**
 242      * Creates a Scene for a specific root Node with a specific size and fill.
 243      *
 244      * @param root The root node of the scene graph
 245      * @param width The width of the scene
 246      * @param height The height of the scene
 247      * @param fill The fill
 248      *
 249      * @throws NullPointerException if root is null
 250      */
 251     public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height,
 252             @NamedArg(value="fill", defaultValue="WHITE") Paint fill) {
 253         this(root, width, height, fill, false, SceneAntialiasing.DISABLED);
 254     }
 255 
 256     /**
 257      * Constructs a scene consisting of a root, with a dimension of width and
 258      * height, and specifies whether a depth buffer is created for this scene.
 259      * <p>
 260      * A scene with only 2D shapes and without any 3D transforms does not need a
 261      * depth buffer. A scene containing 3D shapes or 2D shapes with 3D
 262      * transforms may use depth buffer support for proper depth sorted
 263      * rendering; to avoid depth fighting (also known as Z fighting), disable
 264      * depth testing on 2D shapes that have no 3D transforms. See
 265      * {@link Node#depthTestProperty depthTest} for more information.
 266      *
 267      * @param root The root node of the scene graph
 268      * @param width The width of the scene
 269      * @param height The height of the scene
 270      * @param depthBuffer The depth buffer flag
 271      * <p>
 272      * The depthBuffer flag is a conditional feature and its default value is
 273      * false. See
 274      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 275      * for more information.
 276      *
 277      * @throws NullPointerException if root is null
 278      *
 279      * @see javafx.scene.Node#setDepthTest(DepthTest)
 280      */
 281     public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height, @NamedArg("depthBuffer") boolean depthBuffer) {
 282         this(root, width, height, Color.WHITE, depthBuffer, SceneAntialiasing.DISABLED);
 283     }
 284 
 285     /**
 286      * Constructs a scene consisting of a root, with a dimension of width and
 287      * height, specifies whether a depth buffer is created for this scene and
 288      * specifies whether scene anti-aliasing is requested.
 289      * <p>
 290      * A scene with only 2D shapes and without any 3D transforms does not need a
 291      * depth buffer nor scene anti-aliasing support. A scene containing 3D
 292      * shapes or 2D shapes with 3D transforms may use depth buffer support for
 293      * proper depth sorted rendering; to avoid depth fighting (also known as Z
 294      * fighting), disable depth testing on 2D shapes that have no 3D transforms.
 295      * See {@link Node#depthTestProperty depthTest} for more information. A
 296      * scene with 3D shapes may enable scene anti-aliasing to improve its
 297      * rendering quality.
 298      * <p>
 299      * A Scene can be created and modified on any thread until it is attached to a {@code Window} that is showing
 300      * ({@link javafx.stage.Window#isShowing()}. This does not mean the Scene is thread-safe,
 301      * so manipulation from multiple threads at the same time is illegal, may lead to unexpected results and must be avoided.
 302      *
 303      * @param root The root node of the scene graph
 304      * @param width The width of the scene
 305      * @param height The height of the scene
 306      * @param depthBuffer The depth buffer flag
 307      * @param antiAliasing The scene anti-aliasing attribute. A value of
 308      * {@code null} is treated as DISABLED.
 309      * <p>
 310      * The depthBuffer and antiAliasing are conditional features. With the
 311      * respective default values of: false and {@code SceneAntialiasing.DISABLED}. See
 312      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 313      * for more information.
 314      *
 315      * @throws NullPointerException if root is null
 316      *
 317      * @see javafx.scene.Node#setDepthTest(DepthTest)
 318      * @since JavaFX 8.0
 319      */
 320     public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height,
 321             @NamedArg("depthBuffer") boolean depthBuffer,
 322             @NamedArg(value="antiAliasing", defaultValue="DISABLED") SceneAntialiasing antiAliasing) {
 323         this(root, width, height, Color.WHITE, depthBuffer, antiAliasing);
 324 
 325         if (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED &&
 326                 !Toolkit.getToolkit().isMSAASupported())
 327         {
 328             String logname = Scene.class.getName();
 329             PlatformLogger.getLogger(logname).warning("System can't support "
 330                 + "antiAliasing");
 331         }
 332     }
 333 
 334     private Scene(Parent root, double width, double height, Paint fill,
 335             boolean depthBuffer, SceneAntialiasing antiAliasing) {
 336         this.depthBuffer = depthBuffer;
 337         this.antiAliasing = antiAliasing;
 338         if (root == null) {
 339             throw new NullPointerException("Root cannot be null");
 340         }
 341 
 342         if ((depthBuffer || (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED))
 343                 && !Platform.isSupported(ConditionalFeature.SCENE3D)) {
 344             String logname = Scene.class.getName();
 345             PlatformLogger.getLogger(logname).warning("System can't support "
 346                     + "ConditionalFeature.SCENE3D");
 347         }
 348 
 349         init();
 350         setRoot(root);
 351         init(width, height);
 352         setFill(fill);
 353     }
 354 
 355     static {
 356             PerformanceTracker.setSceneAccessor(new PerformanceTracker.SceneAccessor() {
 357                 public void setPerfTracker(Scene scene, PerformanceTracker tracker) {
 358                     synchronized (trackerMonitor) {
 359                         scene.tracker = tracker;
 360                     }
 361                 }
 362                 public PerformanceTracker getPerfTracker(Scene scene) {
 363                     synchronized (trackerMonitor) {
 364                         return scene.tracker;
 365                     }
 366                 }
 367             });
 368             FXRobotHelper.setSceneAccessor(new FXRobotHelper.FXRobotSceneAccessor() {
 369                 public void processKeyEvent(Scene scene, KeyEvent keyEvent) {
 370                     scene.impl_processKeyEvent(keyEvent);
 371                 }
 372                 public void processMouseEvent(Scene scene, MouseEvent mouseEvent) {
 373                     scene.impl_processMouseEvent(mouseEvent);
 374                 }
 375                 public void processScrollEvent(Scene scene, ScrollEvent scrollEvent) {
 376                     scene.processGestureEvent(scrollEvent, scene.scrollGesture);
 377                 }
 378                 public ObservableList<Node> getChildren(Parent parent) {
 379                     return parent.getChildren(); //was impl_getChildren
 380                 }
 381                 public Object renderToImage(Scene scene, Object platformImage) {
 382                     return Toolkit.getImageAccessor().getPlatformImage(scene.snapshot(null));
 383                 }
 384             });
 385             SceneHelper.setSceneAccessor(
 386                     new SceneHelper.SceneAccessor() {
 387                         @Override
 388                         public void setPaused(boolean paused) {
 389                             Scene.paused = paused;
 390                         }
 391 
 392                         @Override
 393                         public void parentEffectiveOrientationInvalidated(
 394                                 final Scene scene) {
 395                             scene.parentEffectiveOrientationInvalidated();
 396                         }
 397 
 398                         @Override
 399                         public Camera getEffectiveCamera(Scene scene) {
 400                             return scene.getEffectiveCamera();
 401                         }
 402 
 403                         @Override
 404                         public Scene createPopupScene(Parent root) {
 405                             return new Scene(root) {
 406                                        @Override
 407                                        void doLayoutPass() {
 408                                            resizeRootToPreferredSize(getRoot());
 409                                            super.doLayoutPass();
 410                                        }
 411 
 412                                        @Override
 413                                        void resizeRootOnSceneSizeChange(
 414                                                double newWidth,
 415                                                double newHeight) {
 416                                            // don't resize
 417                                        }
 418                                    };
 419                         }
 420 
 421                         @Override
 422                         public void setTransientFocusContainer(Scene scene, Node node) {
 423                             if (scene != null) {
 424                                 scene.transientFocusContainer = node;
 425                             }
 426                         }
 427 
 428                         @Override
 429                         public Accessible getAccessible(Scene scene) {
 430                             return scene.getAccessible();
 431                         }
 432                     });
 433         }
 434 
 435         // Reserve space for 30 nodes in the dirtyNodes set.
 436         private static final int MIN_DIRTY_CAPACITY = 30;
 437 
 438         // For debugging
 439         private static boolean inSynchronizer = false;
 440         private static boolean inMousePick = false;
 441         private static boolean allowPGAccess = false;
 442         private static int pgAccessCount = 0;
 443 
 444         // Flag set by the Toolkit when we are paused for JMX debugging
 445         private static boolean paused = false;
 446 
 447         /**
 448          * Used for debugging purposes. Returns true if we are in either the
 449          * mouse event code (picking) or the synchronizer, or if the scene is
 450          * not yet initialized,
 451          *
 452          */
 453         static boolean isPGAccessAllowed() {
 454             return inSynchronizer || inMousePick || allowPGAccess;
 455         }
 456 
 457         /**
 458          * @treatAsPrivate implementation detail
 459          * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 460          */
 461         @Deprecated
 462         public static void impl_setAllowPGAccess(boolean flag) {
 463             if (Utils.assertionEnabled()) {
 464                 if (flag) {
 465                     pgAccessCount++;
 466                     allowPGAccess = true;
 467                 }
 468                 else {
 469                     if (pgAccessCount <= 0) {
 470                         throw new java.lang.AssertionError("*** pgAccessCount underflow");
 471                     }
 472                     if (--pgAccessCount == 0) {
 473                         allowPGAccess = false;
 474                     }
 475                 }
 476             }
 477         }
 478 
 479         /**
 480          * If true, use the platform's drag gesture detection
 481          * else use Scene-level detection as per DnDGesture.process(MouseEvent, List)
 482          */
 483         private static final boolean PLATFORM_DRAG_GESTURE_INITIATION = false;
 484 
 485     /**
 486      * Set of dirty nodes; processed once per frame by the synchronizer.
 487      * When a node's state changes such that it becomes "dirty" with respect
 488      * to the graphics stack and requires synchronization, then that node
 489      * is added to this list. Note that if state on the Node changes, but it
 490      * was already dirty, then the Node doesn't add itself again.
 491      * <p>
 492      * Because at initialization time every node in the scene graph is dirty,
 493      * we have a special state and special code path during initialization
 494      * that does not involve adding each node to the dirtyNodes list. When
 495      * dirtyNodes is null, that means this Scene has not yet been synchronized.
 496      * A good default size is then created for the dirtyNodes list.
 497      * <p>
 498      * We double-buffer the set so that we can add new nodes to the
 499      * set while processing the existing set. This avoids our having to
 500      * take a snapshot of the set (e.g., with toArray()) and reduces garbage.
 501      */
 502     private Node[] dirtyNodes;
 503     private int dirtyNodesSize;
 504 
 505     /**
 506      * Add the specified node to this scene's dirty list. Called by the
 507      * markDirty method in Node or when the Node's scene changes.
 508      */
 509     void addToDirtyList(Node n) {
 510         if (dirtyNodes == null || dirtyNodesSize == 0) {
 511             if (peer != null) {
 512                 Toolkit.getToolkit().requestNextPulse();
 513             }
 514         }
 515 
 516         if (dirtyNodes != null) {
 517             if (dirtyNodesSize == dirtyNodes.length) {
 518                 Node[] tmp = new Node[dirtyNodesSize + (dirtyNodesSize >> 1)];
 519                 System.arraycopy(dirtyNodes, 0, tmp, 0, dirtyNodesSize);
 520                 dirtyNodes = tmp;
 521             }
 522             dirtyNodes[dirtyNodesSize++] = n;
 523         }
 524     }
 525 
 526     private void doCSSPass() {
 527         final Parent sceneRoot = getRoot();
 528         //
 529         // RT-17547: when the tree is synchronized, the dirty bits are
 530         // are cleared but the cssFlag might still be something other than
 531         // clean.
 532         //
 533         // Before RT-17547, the code checked the dirty bit. But this is
 534         // superfluous since the dirty bit will be set if the flag is not clean,
 535         // but the flag will never be anything other than clean if the dirty
 536         // bit is not set. The dirty bit is still needed, however, since setting
 537         // it ensures a pulse if no other dirty bits have been set.
 538         //
 539         // For the purpose of showing the change, the dirty bit
 540         // check code was commented out and not removed.
 541         //
 542 //        if (sceneRoot.impl_isDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS)) {
 543         if (sceneRoot.cssFlag != CssFlags.CLEAN) {
 544             // The dirty bit isn't checked but we must ensure it is cleared.
 545             // The cssFlag is set to clean in either Node.processCSS or
 546             // Node.impl_processCSS(boolean)
 547             sceneRoot.impl_clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
 548             sceneRoot.processCSS();
 549         }
 550     }
 551 
 552     void doLayoutPass() {
 553         final Parent r = getRoot();
 554         if (r != null) {
 555             r.layout();
 556         }
 557     }
 558 
 559     /**
 560      * The peer of this scene
 561      */
 562     private TKScene peer;
 563 
 564     /**
 565      * Get Scene's peer
 566      *
 567      * @treatAsPrivate implementation detail
 568      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 569      */
 570     @Deprecated
 571     public TKScene impl_getPeer() {
 572         return peer;
 573     }
 574 
 575     /**
 576      * The scene pulse listener that gets called on toolkit pulses
 577      */
 578     ScenePulseListener scenePulseListener = new ScenePulseListener();
 579 
 580     /**
 581      * @treatAsPrivate implementation detail
 582      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 583      */
 584     @Deprecated
 585     public TKPulseListener impl_getScenePulseListener() {
 586         if (SystemProperties.isDebug()) {
 587             return scenePulseListener;
 588         }
 589         return null;
 590     }
 591 
 592     private List<Runnable> preLayoutPulseListeners;
 593     private List<Runnable> postLayoutPulseListeners;
 594 
 595     /**
 596      * Adds a new scene pre layout pulse listener to this scene. Every time a pulse occurs,
 597      * this listener will be called on the JavaFX Application Thread directly
 598      * <strong>before</strong> the CSS and layout passes, and also before
 599      * any rendering is done for
 600      * this frame. This scene pulse listener is suitable for knowing when a
 601      * scenegraph pulse is happening and also for modifying the scenegraph
 602      * (as it is called before CSS and layout, so any changes made will be properly
 603      * styled and positioned).
 604      *
 605      * This method must be called on the JavaFX Application thread.
 606      *
 607      * @param r The Runnable to be called when the pulse occurs.
 608      *
 609      * @throws IllegalStateException if this method is called on a thread
 610      * other than the JavaFX Application Thread.
 611      *
 612      * @throws NullPointerException if the provided Runnable is null.
 613      *
 614      * @since 9
 615      */
 616     public final void addPreLayoutPulseListener(Runnable r) {
 617         Toolkit.getToolkit().checkFxUserThread();
 618 
 619         if (r == null) {
 620             throw new NullPointerException("Scene pulse listener should not be null");
 621         }
 622         if (preLayoutPulseListeners == null) {
 623             preLayoutPulseListeners = new CopyOnWriteArrayList<>();
 624         }
 625         preLayoutPulseListeners.add(r);
 626     }
 627 
 628     /**
 629      * Removes a previously registered scene pre layout pulse listener from listening to
 630      * pulses in this scene. This method does nothing if the specified Runnable is
 631      * not already in the list.
 632      *
 633      * This method must be called on the JavaFX Application thread.
 634      *
 635      * @param r The Runnable that should no longer be called when the pulse
 636      * occurs for this scene.
 637      *
 638      * @throws IllegalStateException if this method is called on a thread
 639      * other than the JavaFX Application Thread.
 640      *
 641      * @since 9
 642      */
 643     public final void removePreLayoutPulseListener(Runnable r) {
 644         Toolkit.getToolkit().checkFxUserThread();
 645 
 646         if (preLayoutPulseListeners == null) {
 647             return;
 648         }
 649         preLayoutPulseListeners.remove(r);
 650     }
 651 
 652     /**
 653      * Adds a new scene post layout pulse listener to this scene. Every time a pulse occurs,
 654      * this listener will be called on the JavaFX Application Thread directly
 655      * <strong>after</strong> the CSS and layout passes, but before any rendering is done for
 656      * this frame. This scene pulse listener is suitable for knowing when a
 657      * scenegraph pulse is happening, but it is not suited to use cases related
 658      * to modifying the scenegraph (as it is called after CSS and layout, so
 659      * any changes will possibly be incorrect until the next pulse is run).
 660      * An alternative (and better) solution for situations where a scenegraph
 661      * modification is required to happen is to use either the
 662      * {@link #addPreLayoutPulseListener(Runnable)} API or the the
 663      * {@link javafx.animation.AnimationTimer} API.
 664      *
 665      * This method must be called on the JavaFX Application thread.
 666      *
 667      * @param r The Runnable to be called when the pulse occurs.
 668      *
 669      * @throws IllegalStateException if this method is called on a thread
 670      * other than the JavaFX Application Thread.
 671      *
 672      * @throws NullPointerException if the provided Runnable is null.
 673      *
 674      * @since 9
 675      */
 676     public final void addPostLayoutPulseListener(Runnable r) {
 677         Toolkit.getToolkit().checkFxUserThread();
 678 
 679         if (r == null) {
 680             throw new NullPointerException("Scene pulse listener should not be null");
 681         }
 682         if (postLayoutPulseListeners == null) {
 683             postLayoutPulseListeners = new CopyOnWriteArrayList<>();
 684         }
 685         postLayoutPulseListeners.add(r);
 686     }
 687 
 688     /**
 689      * Removes a previously registered scene post layout pulse listener from listening to
 690      * pulses in this scene. This method does nothing if the specified Runnable is
 691      * not already in the list.
 692      *
 693      * This method must be called on the JavaFX Application thread.
 694      *
 695      * @param r The Runnable that should no longer be called when the pulse
 696      * occurs for this scene.
 697      *
 698      * @throws IllegalStateException if this method is called on a thread
 699      * other than the JavaFX Application Thread.
 700      *
 701      * @since 9
 702      */
 703     public final void removePostLayoutPulseListener(Runnable r) {
 704         Toolkit.getToolkit().checkFxUserThread();
 705 
 706         if (postLayoutPulseListeners == null) {
 707             return;
 708         }
 709         postLayoutPulseListeners.remove(r);
 710     }
 711 
 712     /**
 713      * Return the defined {@code SceneAntialiasing} for this {@code Scene}.
 714      * <p>
 715      * Note: this is a conditional feature. See
 716      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 717      * and {@link javafx.scene.SceneAntialiasing SceneAntialiasing}
 718      * for more information.
 719      * @since JavaFX 8.0
 720      */
 721     public final SceneAntialiasing getAntiAliasing() {
 722         return antiAliasing;
 723     }
 724 
 725     private boolean getAntiAliasingInternal() {
 726         return (antiAliasing != null &&
 727                 Toolkit.getToolkit().isMSAASupported() &&
 728                 Platform.isSupported(ConditionalFeature.SCENE3D)) ?
 729                 antiAliasing != SceneAntialiasing.DISABLED : false;
 730     }
 731 
 732     /**
 733      * The {@code Window} for this {@code Scene}
 734      */
 735     private ReadOnlyObjectWrapper<Window> window;
 736 
 737     private void setWindow(Window value) {
 738         windowPropertyImpl().set(value);
 739     }
 740 
 741     public final Window getWindow() {
 742         return window == null ? null : window.get();
 743     }
 744 
 745     public final ReadOnlyObjectProperty<Window> windowProperty() {
 746         return windowPropertyImpl().getReadOnlyProperty();
 747     }
 748 
 749     private ReadOnlyObjectWrapper<Window> windowPropertyImpl() {
 750         if (window == null) {
 751             window = new ReadOnlyObjectWrapper<Window>() {
 752                 private Window oldWindow;
 753 
 754                 @Override protected void invalidated() {
 755                     final Window newWindow = get();
 756                     getKeyHandler().windowForSceneChanged(oldWindow, newWindow);
 757                     if (oldWindow != null) {
 758                         impl_disposePeer();
 759                     }
 760                     if (newWindow != null) {
 761                         impl_initPeer();
 762                     }
 763                     parentEffectiveOrientationInvalidated();
 764 
 765                     oldWindow = newWindow;
 766                 }
 767 
 768                 @Override
 769                 public Object getBean() {
 770                     return Scene.this;
 771                 }
 772 
 773                 @Override
 774                 public String getName() {
 775                     return "window";
 776                 }
 777             };
 778         }
 779         return window;
 780     }
 781 
 782     /**
 783      * @treatAsPrivate implementation detail
 784      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 785      */
 786     @Deprecated
 787     public void impl_setWindow(Window value) {
 788         setWindow(value);
 789     }
 790 
 791     /**
 792      * @treatAsPrivate implementation detail
 793      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 794      */
 795     @Deprecated
 796     public void impl_initPeer() {
 797         assert peer == null;
 798 
 799         Window window = getWindow();
 800         // impl_initPeer() is only called from Window, either when the window
 801         // is being shown, or the window scene is being changed. In any case
 802         // this scene's window cannot be null.
 803         assert window != null;
 804 
 805         TKStage windowPeer = window.impl_getPeer();
 806         if (windowPeer == null) {
 807             // This is fine, the window is not visible. impl_initPeer() will
 808             // be called again later, when the window is being shown.
 809             return;
 810         }
 811 
 812         final boolean isTransparentWindowsSupported = Platform.isSupported(ConditionalFeature.TRANSPARENT_WINDOW);
 813         if (!isTransparentWindowsSupported) {
 814             PlatformImpl.addNoTransparencyStylesheetToScene(this);
 815         }
 816 
 817         PerformanceTracker.logEvent("Scene.initPeer started");
 818 
 819         impl_setAllowPGAccess(true);
 820 
 821         Toolkit tk = Toolkit.getToolkit();
 822         peer = windowPeer.createTKScene(isDepthBufferInternal(), getAntiAliasingInternal(), acc);
 823         PerformanceTracker.logEvent("Scene.initPeer TKScene created");
 824         peer.setTKSceneListener(new ScenePeerListener());
 825         peer.setTKScenePaintListener(new ScenePeerPaintListener());
 826         PerformanceTracker.logEvent("Scene.initPeer TKScene set");
 827         peer.setRoot(getRoot().impl_getPeer());
 828         peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill()));
 829         getEffectiveCamera().impl_updatePeer();
 830         peer.setCamera((NGCamera) getEffectiveCamera().impl_getPeer());
 831         peer.markDirty();
 832         PerformanceTracker.logEvent("Scene.initPeer TKScene initialized");
 833 
 834         impl_setAllowPGAccess(false);
 835 
 836         tk.addSceneTkPulseListener(scenePulseListener);
 837         // listen to dnd gestures coming from the platform
 838         if (PLATFORM_DRAG_GESTURE_INITIATION) {
 839             if (dragGestureListener == null) {
 840                 dragGestureListener = new DragGestureListener();
 841             }
 842             tk.registerDragGestureListener(peer, EnumSet.allOf(TransferMode.class), dragGestureListener);
 843         }
 844         tk.enableDrop(peer, new DropTargetListener());
 845         tk.installInputMethodRequests(peer, new InputMethodRequestsDelegate());
 846 
 847         PerformanceTracker.logEvent("Scene.initPeer finished");
 848     }
 849 
 850     /**
 851      * @treatAsPrivate implementation detail
 852      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 853      */
 854     @Deprecated
 855     public void impl_disposePeer() {
 856         if (peer == null) {
 857             // This is fine, the window is either not shown yet and there is no
 858             // need in disposing scene peer, or is hidden and impl_disposePeer()
 859             // has already been called.
 860             return;
 861         }
 862 
 863         PerformanceTracker.logEvent("Scene.disposePeer started");
 864 
 865         Toolkit tk = Toolkit.getToolkit();
 866         tk.removeSceneTkPulseListener(scenePulseListener);
 867         if (accessible != null) {
 868             disposeAccessibles();
 869             Node root = getRoot();
 870             if (root != null) root.releaseAccessible();
 871             accessible.dispose();
 872             accessible = null;
 873         }
 874         peer.dispose();
 875         peer = null;
 876 
 877         PerformanceTracker.logEvent("Scene.disposePeer finished");
 878     }
 879 
 880     DnDGesture dndGesture = null;
 881     DragGestureListener dragGestureListener;
 882     /**
 883      * The horizontal location of this {@code Scene} on the {@code Window}.
 884      */
 885     private ReadOnlyDoubleWrapper x;
 886 
 887     private final void setX(double value) {
 888         xPropertyImpl().set(value);
 889     }
 890 
 891     public final double getX() {
 892         return x == null ? 0.0 : x.get();
 893     }
 894 
 895     public final ReadOnlyDoubleProperty xProperty() {
 896         return xPropertyImpl().getReadOnlyProperty();
 897     }
 898 
 899     private ReadOnlyDoubleWrapper xPropertyImpl() {
 900         if (x == null) {
 901             x = new ReadOnlyDoubleWrapper(this, "x");
 902         }
 903         return x;
 904     }
 905 
 906     /**
 907      * The vertical location of this {@code Scene} on the {@code Window}.
 908      */
 909     private ReadOnlyDoubleWrapper y;
 910 
 911     private final void setY(double value) {
 912         yPropertyImpl().set(value);
 913     }
 914 
 915     public final double getY() {
 916         return y == null ? 0.0 : y.get();
 917     }
 918 
 919     public final ReadOnlyDoubleProperty yProperty() {
 920         return yPropertyImpl().getReadOnlyProperty();
 921     }
 922 
 923     private ReadOnlyDoubleWrapper yPropertyImpl() {
 924         if (y == null) {
 925             y = new ReadOnlyDoubleWrapper(this, "y");
 926         }
 927         return y;
 928     }
 929 
 930     /**
 931      * The width of this {@code Scene}
 932      */
 933     private ReadOnlyDoubleWrapper width;
 934 
 935     private final void setWidth(double value) {
 936         widthPropertyImpl().set(value);
 937     }
 938 
 939     public final double getWidth() {
 940         return width == null ? 0.0 : width.get();
 941     }
 942 
 943     public final ReadOnlyDoubleProperty widthProperty() {
 944         return widthPropertyImpl().getReadOnlyProperty();
 945     }
 946 
 947     private ReadOnlyDoubleWrapper widthPropertyImpl() {
 948         if (width == null) {
 949             width = new ReadOnlyDoubleWrapper() {
 950 
 951                 @Override
 952                 protected void invalidated() {
 953                     final Parent _root = getRoot();
 954                     //TODO - use a better method to update mirroring
 955                     if (_root.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
 956                         _root.impl_transformsChanged();
 957                     }
 958                     if (_root.isResizable()) {
 959                         resizeRootOnSceneSizeChange(get() - _root.getLayoutX() - _root.getTranslateX(), _root.getLayoutBounds().getHeight());
 960                     }
 961 
 962                     getEffectiveCamera().setViewWidth(get());
 963                 }
 964 
 965                 @Override
 966                 public Object getBean() {
 967                     return Scene.this;
 968                 }
 969 
 970                 @Override
 971                 public String getName() {
 972                     return "width";
 973                 }
 974             };
 975         }
 976         return width;
 977     }
 978 
 979     /**
 980      * The height of this {@code Scene}
 981      */
 982     private ReadOnlyDoubleWrapper height;
 983 
 984     private final void setHeight(double value) {
 985         heightPropertyImpl().set(value);
 986     }
 987 
 988     public final double getHeight() {
 989         return height == null ? 0.0 : height.get();
 990     }
 991 
 992     public final ReadOnlyDoubleProperty heightProperty() {
 993         return heightPropertyImpl().getReadOnlyProperty();
 994     }
 995 
 996     private ReadOnlyDoubleWrapper heightPropertyImpl() {
 997         if (height == null) {
 998             height = new ReadOnlyDoubleWrapper() {
 999 
1000                 @Override
1001                 protected void invalidated() {
1002                     final Parent _root = getRoot();
1003                     if (_root.isResizable()) {
1004                         resizeRootOnSceneSizeChange(_root.getLayoutBounds().getWidth(), get() - _root.getLayoutY() - _root.getTranslateY());
1005                     }
1006 
1007                     getEffectiveCamera().setViewHeight(get());
1008                 }
1009 
1010                 @Override
1011                 public Object getBean() {
1012                     return Scene.this;
1013                 }
1014 
1015                 @Override
1016                 public String getName() {
1017                     return "height";
1018                 }
1019             };
1020         }
1021         return height;
1022     }
1023 
1024     void resizeRootOnSceneSizeChange(double newWidth, double newHeight) {
1025         getRoot().resize(newWidth, newHeight);
1026     }
1027 
1028     // Reusable target wrapper (to avoid creating new one for each picking)
1029     private TargetWrapper tmpTargetWrapper = new TargetWrapper();
1030 
1031     /**
1032      * Specifies the type of camera use for rendering this {@code Scene}.
1033      * If {@code camera} is null, a parallel camera is used for rendering.
1034      * It is illegal to set a camera that belongs to other {@code Scene}
1035      * or {@code SubScene}.
1036      * <p>
1037      * Note: this is a conditional feature. See
1038      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
1039      * for more information.
1040      *
1041      * @defaultValue null
1042      */
1043     private ObjectProperty<Camera> camera;
1044 
1045     public final void setCamera(Camera value) {
1046         cameraProperty().set(value);
1047     }
1048 
1049     public final Camera getCamera() {
1050         return camera == null ? null : camera.get();
1051     }
1052 
1053     public final ObjectProperty<Camera> cameraProperty() {
1054         if (camera == null) {
1055             camera = new ObjectPropertyBase<Camera>() {
1056                 Camera oldCamera = null;
1057 
1058                 @Override
1059                 protected void invalidated() {
1060                     Camera _value = get();
1061                     if (_value != null) {
1062                         if (_value instanceof PerspectiveCamera
1063                                 && !Platform.isSupported(ConditionalFeature.SCENE3D)) {
1064                             String logname = Scene.class.getName();
1065                             PlatformLogger.getLogger(logname).warning("System can't support "
1066                                     + "ConditionalFeature.SCENE3D");
1067                         }
1068                         // Illegal value if it belongs to other scene or any subscene
1069                         if ((_value.getScene() != null && _value.getScene() != Scene.this)
1070                                 || _value.getSubScene() != null) {
1071                             throw new IllegalArgumentException(_value
1072                                     + "is already part of other scene or subscene");
1073                         }
1074                         // throws exception if the camera already has a different owner
1075                         _value.setOwnerScene(Scene.this);
1076                         _value.setViewWidth(getWidth());
1077                         _value.setViewHeight(getHeight());
1078                     }
1079                     if (oldCamera != null && oldCamera != _value) {
1080                         oldCamera.setOwnerScene(null);
1081                     }
1082                     oldCamera = _value;
1083                 }
1084 
1085                 @Override
1086                 public Object getBean() {
1087                     return Scene.this;
1088                 }
1089 
1090                 @Override
1091                 public String getName() {
1092                     return "camera";
1093                 }
1094             };
1095         }
1096         return camera;
1097     }
1098 
1099     Camera getEffectiveCamera() {
1100         final Camera cam = getCamera();
1101         if (cam == null
1102                 || (cam instanceof PerspectiveCamera
1103                 && !Platform.isSupported(ConditionalFeature.SCENE3D))) {
1104             if (defaultCamera == null) {
1105                 defaultCamera = new ParallelCamera();
1106                 defaultCamera.setOwnerScene(this);
1107                 defaultCamera.setViewWidth(getWidth());
1108                 defaultCamera.setViewHeight(getHeight());
1109             }
1110             return defaultCamera;
1111         }
1112 
1113         return cam;
1114     }
1115 
1116     // Used by the camera
1117     void markCameraDirty() {
1118         markDirty(DirtyBits.CAMERA_DIRTY);
1119         setNeedsRepaint();
1120     }
1121 
1122     void markCursorDirty() {
1123         markDirty(DirtyBits.CURSOR_DIRTY);
1124     }
1125 
1126     /**
1127      * Defines the background fill of this {@code Scene}. Both a {@code null}
1128      * value meaning 'paint no background' and a {@link javafx.scene.paint.Paint}
1129      * with transparency are supported. The default fill of the Scene is
1130      * {@link Color#WHITE}, but it is more commonly the case that the initial
1131      * color shown to users is the background fill of the
1132      * {@link #rootProperty() root node} of the {@code Scene}, as it typically is
1133      * stretched to take up all available space in the {@code Scene}. The
1134      * root node of the {@code Scene} is given the CSS style class 'root', and
1135      * the default user agent stylesheets that ship with JavaFX (presently
1136      * Caspian and Modena) apply styling on to this root style class. In the
1137      * case of Caspian this does not impact the background fill color of the
1138      * root node, but in the case of Modena the default fill is set to be a
1139      * light gray color.
1140      *
1141      * @defaultValue WHITE
1142      */
1143     private ObjectProperty<Paint> fill;
1144 
1145     public final void setFill(Paint value) {
1146         fillProperty().set(value);
1147     }
1148 
1149     public final Paint getFill() {
1150         return fill == null ? Color.WHITE : fill.get();
1151     }
1152 
1153     public final ObjectProperty<Paint> fillProperty() {
1154         if (fill == null) {
1155             fill = new ObjectPropertyBase<Paint>(Color.WHITE) {
1156 
1157                 @Override
1158                 protected void invalidated() {
1159                     markDirty(DirtyBits.FILL_DIRTY);
1160                 }
1161 
1162                 @Override
1163                 public Object getBean() {
1164                     return Scene.this;
1165                 }
1166 
1167                 @Override
1168                 public String getName() {
1169                     return "fill";
1170                 }
1171             };
1172         }
1173         return fill;
1174     }
1175 
1176     /**
1177      * Defines the root {@code Node} of the scene graph.
1178      * If a {@code Group} is used as the root, the
1179      * contents of the scene graph will be clipped by the scene's width and height and
1180      * changes to the scene's size (if user resizes the stage) will not alter the
1181      * layout of the scene graph.    If a resizable node (layout {@code Region} or
1182      * {@code Control}) is set as the root, then the root's size will track the
1183      * scene's size, causing the contents to be relayed out as necessary.
1184      *
1185      * Scene doesn't accept null root.
1186      *
1187      */
1188     private ObjectProperty<Parent> root;
1189 
1190     public final void setRoot(Parent value) {
1191         rootProperty().set(value);
1192     }
1193 
1194     public final Parent getRoot() {
1195         return root == null ? null : root.get();
1196     }
1197 
1198     Parent oldRoot;
1199     public final ObjectProperty<Parent> rootProperty() {
1200         if (root == null) {
1201             root = new ObjectPropertyBase<Parent>() {
1202 
1203                 private void forceUnbind() {
1204                     System.err.println("Unbinding illegal root.");
1205                     unbind();
1206                 }
1207 
1208                 @Override
1209                 protected void invalidated() {
1210                     Parent _value = get();
1211 
1212                     if (_value == null) {
1213                         if (isBound()) forceUnbind();
1214                         throw new NullPointerException("Scene's root cannot be null");
1215                     }
1216 
1217                     if (_value.getParent() != null) {
1218                         if (isBound()) forceUnbind();
1219                         throw new IllegalArgumentException(_value +
1220                                 "is already inside a scene-graph and cannot be set as root");
1221                     }
1222                     if (_value.getClipParent() != null) {
1223                         if (isBound()) forceUnbind();
1224                         throw new IllegalArgumentException(_value +
1225                                 "is set as a clip on another node, so cannot be set as root");
1226                     }
1227                     if (_value.getScene() != null && _value.getScene().getRoot() == _value && _value.getScene() != Scene.this) {
1228                         if (isBound()) forceUnbind();
1229                         throw new IllegalArgumentException(_value +
1230                                 "is already set as root of another scene");
1231                     }
1232 
1233                     if (oldRoot != null) {
1234                         oldRoot.setScenes(null, null, /* reapplyCSS*/ false);
1235                     }
1236                     oldRoot = _value;
1237                     _value.getStyleClass().add(0, "root");
1238                     _value.setScenes(Scene.this, null, /* reapplyCSS*/ true);
1239                     markDirty(DirtyBits.ROOT_DIRTY);
1240                     _value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable
1241                     _value.requestLayout();
1242                 }
1243 
1244                 @Override
1245                 public Object getBean() {
1246                     return Scene.this;
1247                 }
1248 
1249                 @Override
1250                 public String getName() {
1251                     return "root";
1252                 }
1253             };
1254         }
1255         return root;
1256     }
1257 
1258     void setNeedsRepaint() {
1259         if (this.peer != null) {
1260             peer.entireSceneNeedsRepaint();
1261         }
1262     }
1263 
1264     // Process CSS and layout and sync the scene prior to the snapshot
1265     // operation of the given node for this scene (currently the node
1266     // is unused but could possibly be used in the future to optimize this)
1267     void doCSSLayoutSyncForSnapshot(Node node) {
1268         if (!sizeInitialized) {
1269             preferredSize();
1270         } else {
1271             doCSSPass();
1272         }
1273 
1274         // we do not need pulse in the snapshot code
1275         // because this scene can be stage-less
1276         doLayoutPass();
1277 
1278         if (!paused) {
1279             getRoot().updateBounds();
1280             if (peer != null) {
1281                 peer.waitForRenderingToComplete();
1282                 peer.waitForSynchronization();
1283                 try {
1284                     // Run the synchronizer while holding the render lock
1285                     scenePulseListener.synchronizeSceneNodes();
1286                 } finally {
1287                     peer.releaseSynchronization(false);
1288                 }
1289             } else {
1290                 scenePulseListener.synchronizeSceneNodes();
1291             }
1292         }
1293 
1294     }
1295 
1296     // Shared method for Scene.snapshot and Node.snapshot. It is static because
1297     // we might be doing a Node snapshot with a null scene
1298     static WritableImage doSnapshot(Scene scene,
1299             double x, double y, double w, double h,
1300             Node root, BaseTransform transform, boolean depthBuffer,
1301             Paint fill, Camera camera, WritableImage wimg) {
1302 
1303         Toolkit tk = Toolkit.getToolkit();
1304         Toolkit.ImageRenderingContext context = new Toolkit.ImageRenderingContext();
1305 
1306         int xMin = (int)Math.floor(x);
1307         int yMin = (int)Math.floor(y);
1308         int xMax = (int)Math.ceil(x + w);
1309         int yMax = (int)Math.ceil(y + h);
1310         int width = Math.max(xMax - xMin, 1);
1311         int height = Math.max(yMax - yMin, 1);
1312         if (wimg == null) {
1313             wimg = new WritableImage(width, height);
1314         } else {
1315             width = (int)wimg.getWidth();
1316             height = (int)wimg.getHeight();
1317         }
1318 
1319         impl_setAllowPGAccess(true);
1320         context.x = xMin;
1321         context.y = yMin;
1322         context.width = width;
1323         context.height = height;
1324         context.transform = transform;
1325         context.depthBuffer = depthBuffer;
1326         context.root = root.impl_getPeer();
1327         context.platformPaint = fill == null ? null : tk.getPaint(fill);
1328         double cameraViewWidth = 1.0;
1329         double cameraViewHeight = 1.0;
1330         if (camera != null) {
1331             // temporarily adjust camera viewport to the snapshot size
1332             cameraViewWidth = camera.getViewWidth();
1333             cameraViewHeight = camera.getViewHeight();
1334             camera.setViewWidth(width);
1335             camera.setViewHeight(height);
1336             camera.impl_updatePeer();
1337             context.camera = camera.impl_getPeer();
1338         } else {
1339             context.camera = null;
1340         }
1341 
1342         // Grab the lights from the scene
1343         context.lights = null;
1344         if (scene != null && !scene.lights.isEmpty()) {
1345             context.lights = new NGLightBase[scene.lights.size()];
1346             for (int i = 0; i < scene.lights.size(); i++) {
1347                 context.lights[i] = scene.lights.get(i).impl_getPeer();
1348             }
1349         }
1350 
1351         Toolkit.WritableImageAccessor accessor = Toolkit.getWritableImageAccessor();
1352         context.platformImage = accessor.getTkImageLoader(wimg);
1353         impl_setAllowPGAccess(false);
1354         Object tkImage = tk.renderToImage(context);
1355         accessor.loadTkImage(wimg, tkImage);
1356 
1357         if (camera != null) {
1358             impl_setAllowPGAccess(true);
1359             camera.setViewWidth(cameraViewWidth);
1360             camera.setViewHeight(cameraViewHeight);
1361             camera.impl_updatePeer();
1362             impl_setAllowPGAccess(false);
1363         }
1364 
1365         // if this scene belongs to some stage
1366         // we need to mark the entire scene as dirty
1367         // because dirty logic is buggy
1368         if (scene != null && scene.peer != null) {
1369             scene.setNeedsRepaint();
1370         }
1371 
1372         return wimg;
1373     }
1374 
1375     /**
1376      * Implementation method for snapshot
1377      */
1378     private WritableImage doSnapshot(WritableImage img) {
1379         // TODO: no need to do CSS, layout or sync in the deferred case,
1380         // if this scene is attached to a visible stage
1381         doCSSLayoutSyncForSnapshot(getRoot());
1382 
1383         double w = getWidth();
1384         double h = getHeight();
1385         BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
1386 
1387         return doSnapshot(this, 0, 0, w, h,
1388                 getRoot(), transform, isDepthBufferInternal(),
1389                 getFill(), getEffectiveCamera(), img);
1390     }
1391 
1392     // Pulse listener used to run all deferred (async) snapshot requests
1393     private static TKPulseListener snapshotPulseListener = null;
1394 
1395     private static List<Runnable> snapshotRunnableListA;
1396     private static List<Runnable> snapshotRunnableListB;
1397     private static List<Runnable> snapshotRunnableList;
1398 
1399     static void addSnapshotRunnable(final Runnable runnable) {
1400         Toolkit.getToolkit().checkFxUserThread();
1401 
1402         if (snapshotPulseListener == null) {
1403             snapshotRunnableListA = new ArrayList<Runnable>();
1404             snapshotRunnableListB = new ArrayList<Runnable>();
1405             snapshotRunnableList = snapshotRunnableListA;
1406 
1407             snapshotPulseListener = () -> {
1408                 if (snapshotRunnableList.size() > 0) {
1409                     List<Runnable> runnables = snapshotRunnableList;
1410                     if (snapshotRunnableList == snapshotRunnableListA) {
1411                         snapshotRunnableList = snapshotRunnableListB;
1412                     } else {
1413                         snapshotRunnableList = snapshotRunnableListA;
1414                     }
1415                     for (Runnable r : runnables) {
1416                         try {
1417                             r.run();
1418                         } catch (Throwable th) {
1419                             System.err.println("Exception in snapshot runnable");
1420                             th.printStackTrace(System.err);
1421                         }
1422                     }
1423                     runnables.clear();
1424                 }
1425             };
1426 
1427             // Add listener that will be called after all of the scenes have
1428             // had layout and CSS processing, and have been synced
1429             Toolkit.getToolkit().addPostSceneTkPulseListener(snapshotPulseListener);
1430         }
1431 
1432         final AccessControlContext acc = AccessController.getContext();
1433         snapshotRunnableList.add(() -> {
1434             AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
1435                 runnable.run();
1436                 return null;
1437             }, acc);
1438         });
1439         Toolkit.getToolkit().requestNextPulse();
1440     }
1441 
1442     /**
1443      * Takes a snapshot of this scene and returns the rendered image when
1444      * it is ready.
1445      * CSS and layout processing will be done for the scene prior to
1446      * rendering it.
1447      * The entire destination image is cleared using the fill {@code Paint}
1448      * of this scene. The nodes in the scene are then rendered to the image.
1449      * The point (0,0) in scene coordinates is mapped to (0,0) in the image.
1450      * If the image is smaller than the size of the scene, then the rendering
1451      * will be clipped by the image.
1452      *
1453      * <p>
1454      * When taking a snapshot of a scene that is being animated, either
1455      * explicitly by the application or implicitly (such as chart animation),
1456      * the snapshot will be rendered based on the state of the scene graph at
1457      * the moment the snapshot is taken and will not reflect any subsequent
1458      * animation changes.
1459      * </p>
1460      *
1461      * @param image the writable image that will be used to hold the rendered scene.
1462      * It may be null in which case a new WritableImage will be constructed.
1463      * If the image is non-null, the scene will be rendered into the
1464      * existing image.
1465      * In this case, the width and height of the image determine the area
1466      * that is rendered instead of the width and height of the scene.
1467      *
1468      * @throws IllegalStateException if this method is called on a thread
1469      *     other than the JavaFX Application Thread.
1470      *
1471      * @return the rendered image
1472      * @since JavaFX 2.2
1473      */
1474     public WritableImage snapshot(WritableImage image) {
1475         if (!paused) {
1476             Toolkit.getToolkit().checkFxUserThread();
1477         }
1478 
1479         return doSnapshot(image);
1480     }
1481 
1482     /**
1483      * Takes a snapshot of this scene at the next frame and calls the
1484      * specified callback method when the image is ready.
1485      * CSS and layout processing will be done for the scene prior to
1486      * rendering it.
1487      * The entire destination image is cleared using the fill {@code Paint}
1488      * of this scene. The nodes in the scene are then rendered to the image.
1489      * The point (0,0) in scene coordinates is mapped to (0,0) in the image.
1490      * If the image is smaller than the size of the scene, then the rendering
1491      * will be clipped by the image.
1492      *
1493      * <p>
1494      * This is an asynchronous call, which means that other
1495      * events or animation might be processed before the scene is rendered.
1496      * If any such events modify a node in the scene that modification will
1497      * be reflected in the rendered image (as it will also be reflected in
1498      * the frame rendered to the Stage).
1499      * </p>
1500      *
1501      * <p>
1502      * When taking a snapshot of a scene that is being animated, either
1503      * explicitly by the application or implicitly (such as chart animation),
1504      * the snapshot will be rendered based on the state of the scene graph at
1505      * the moment the snapshot is taken and will not reflect any subsequent
1506      * animation changes.
1507      * </p>
1508      *
1509      * @param callback a class whose call method will be called when the image
1510      * is ready. The SnapshotResult that is passed into the call method of
1511      * the callback will contain the rendered image and the source scene
1512      * that was rendered. The callback parameter must not be null.
1513      *
1514      * @param image the writable image that will be used to hold the rendered scene.
1515      * It may be null in which case a new WritableImage will be constructed.
1516      * If the image is non-null, the scene will be rendered into the
1517      * existing image.
1518      * In this case, the width and height of the image determine the area
1519      * that is rendered instead of the width and height of the scene.
1520      *
1521      * @throws IllegalStateException if this method is called on a thread
1522      *     other than the JavaFX Application Thread.
1523      *
1524      * @throws NullPointerException if the callback parameter is null.
1525      * @since JavaFX 2.2
1526      */
1527     public void snapshot(Callback<SnapshotResult, Void> callback, WritableImage image) {
1528         Toolkit.getToolkit().checkFxUserThread();
1529         if (callback == null) {
1530             throw new NullPointerException("The callback must not be null");
1531         }
1532 
1533         final Callback<SnapshotResult, Void> theCallback = callback;
1534         final WritableImage theImage = image;
1535 
1536         // Create a deferred runnable that will be run from a pulse listener
1537         // that is called after all of the scenes have been synced but before
1538         // any of them have been rendered.
1539         final Runnable snapshotRunnable = () -> {
1540             WritableImage img = doSnapshot(theImage);
1541 //                System.err.println("Calling snapshot callback");
1542             SnapshotResult result = new SnapshotResult(img, Scene.this, null);
1543             try {
1544                 Void v = theCallback.call(result);
1545             } catch (Throwable th) {
1546                 System.err.println("Exception in snapshot callback");
1547                 th.printStackTrace(System.err);
1548             }
1549         };
1550 //        System.err.println("Schedule a snapshot in the future");
1551         addSnapshotRunnable(snapshotRunnable);
1552     }
1553 
1554     /**
1555      * Defines the mouse cursor for this {@code Scene}.
1556      */
1557     private ObjectProperty<Cursor> cursor;
1558 
1559     public final void setCursor(Cursor value) {
1560         cursorProperty().set(value);
1561     }
1562 
1563     public final Cursor getCursor() {
1564         return cursor == null ? null : cursor.get();
1565     }
1566 
1567     public final ObjectProperty<Cursor> cursorProperty() {
1568         if (cursor == null) {
1569             cursor = new ObjectPropertyBase<Cursor>() {
1570                          @Override
1571                          protected void invalidated() {
1572                              markCursorDirty();
1573                          }
1574 
1575                          @Override
1576                          public Object getBean() {
1577                              return Scene.this;
1578                          }
1579 
1580                          @Override
1581                          public String getName() {
1582                              return "cursor";
1583                          }
1584                      };
1585         }
1586         return cursor;
1587     }
1588 
1589     /**
1590      * Looks for any node within the scene graph based on the specified CSS selector.
1591      * If more than one node matches the specified selector, this function
1592      * returns the first of them.
1593      * If no nodes are found with this id, then null is returned.
1594      *
1595      * @param selector The css selector to look up
1596      * @return the {@code Node} in the scene which matches the CSS {@code selector},
1597      * or {@code null} if none is found.
1598      */
1599      public Node lookup(String selector) {
1600          return getRoot().lookup(selector);
1601      }
1602     /**
1603      * A ObservableList of string URLs linking to the stylesheets to use with this scene's
1604      * contents. For additional information about using CSS with the
1605      * scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1606      * Guide</a>.
1607      */
1608     private final ObservableList<String> stylesheets  = new TrackableObservableList<String>() {
1609         @Override
1610         protected void onChanged(Change<String> c) {
1611             StyleManager.getInstance().stylesheetsChanged(Scene.this, c);
1612             // RT-9784 - if stylesheet is removed, reset styled properties to
1613             // their initial value.
1614             c.reset();
1615             while(c.next()) {
1616                 if (c.wasRemoved() == false) {
1617                     continue;
1618                 }
1619                 break; // no point in resetting more than once...
1620             }
1621             getRoot().impl_reapplyCSS();
1622         }
1623     };
1624 
1625     /**
1626      * Gets an observable list of string URLs linking to the stylesheets to use
1627      * with this scene's contents.
1628      * <p>
1629      * The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL
1630      * does not have a [scheme:] component, the URL is considered to be the [path] component only.
1631      * Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to
1632      * the root of the application's classpath.
1633      * </p>
1634      * <code><pre>
1635      *
1636      * package com.example.javafx.app;
1637      *
1638      * import javafx.application.Application;
1639      * import javafx.scene.Group;
1640      * import javafx.scene.Scene;
1641      * import javafx.stage.Stage;
1642      *
1643      * public class MyApp extends Application {
1644      *
1645      *     {@literal @}Override public void start(Stage stage) {
1646      *         Scene scene = new Scene(new Group());
1647      *         scene.getStylesheets().add("/com/example/javafx/app/mystyles.css");
1648      *         stage.setScene(scene);
1649      *         stage.show();
1650      *     }
1651      *
1652      *     public static void main(String[] args) {
1653      *         launch(args);
1654      *     }
1655      * }
1656      * </pre></code>
1657      * For additional information about using CSS with the scene graph,
1658      * see the <a href="doc-files/cssref.html">CSS Reference Guide</a>.
1659      *
1660      * @return the list of stylesheets to use with this scene
1661      */
1662     public final ObservableList<String> getStylesheets() { return stylesheets; }
1663 
1664     private ObjectProperty<String> userAgentStylesheet = null;
1665 
1666     /**
1667      * @return the userAgentStylesheet property.
1668      * @see #getUserAgentStylesheet()
1669      * @see #setUserAgentStylesheet(String)
1670      * @since  JavaFX 8u20
1671      */
1672     public final ObjectProperty<String> userAgentStylesheetProperty() {
1673         if (userAgentStylesheet == null) {
1674             userAgentStylesheet = new SimpleObjectProperty<String>(Scene.this, "userAgentStylesheet", null) {
1675                 @Override protected void invalidated() {
1676                     StyleManager.getInstance().forget(Scene.this);
1677                     getRoot().impl_reapplyCSS();
1678                 }
1679             };
1680         }
1681         return userAgentStylesheet;
1682     }
1683 
1684     /**
1685      * Get the URL of the user-agent stylesheet that will be used by this Scene. If the URL has not been set,
1686      * the platform-default user-agent stylesheet will be used.
1687      * <p>
1688      * For additional information about using CSS with the scene graph,
1689      * see the <a href="doc-files/cssref.html">CSS Reference Guide</a>.
1690      * </p>
1691      * @return The URL of the user-agent stylesheet that will be used by this Scene,
1692      * or null if has not been set.
1693      * @since  JavaFX 8u20
1694      */
1695     public final String getUserAgentStylesheet() {
1696         return userAgentStylesheet == null ? null : userAgentStylesheet.get();
1697     }
1698 
1699     /**
1700      * Set the URL of the user-agent stylesheet that will be used by this Scene in place of the
1701      * the platform-default user-agent stylesheet. If the URL does not resolve to a valid location,
1702      * the platform-default user-agent stylesheet will be used.
1703      * <p>
1704      * For additional information about using CSS with the scene graph,
1705      * see the <a href="doc-files/cssref.html">CSS Reference Guide</a>.
1706      * </p>
1707      * @param url The URL is a hierarchical URI of the form [scheme:][//authority][path]. If the URL
1708      * does not have a [scheme:] component, the URL is considered to be the [path] component only.
1709      * Any leading '/' character of the [path] is ignored and the [path] is treated as a path relative to
1710      * the root of the application's classpath.
1711      * @since  JavaFX 8u20
1712      */
1713     public final void setUserAgentStylesheet(String url) {
1714         userAgentStylesheetProperty().set(url);
1715     }
1716 
1717     /**
1718      * Retrieves the depth buffer attribute for this scene.
1719      * @return the depth buffer attribute.
1720      */
1721     public final boolean isDepthBuffer() {
1722         return depthBuffer;
1723     }
1724 
1725     boolean isDepthBufferInternal() {
1726         if (!Platform.isSupported(ConditionalFeature.SCENE3D)) {
1727             return false;
1728         }
1729         return depthBuffer;
1730     }
1731 
1732     private void init(double width, double height) {
1733         if (width >= 0) {
1734             widthSetByUser = width;
1735             setWidth((float)width);
1736         }
1737         if (height >= 0) {
1738             heightSetByUser = height;
1739             setHeight((float)height);
1740         }
1741         sizeInitialized = (widthSetByUser >= 0 && heightSetByUser >= 0);
1742     }
1743 
1744     private void init() {
1745         if (PerformanceTracker.isLoggingEnabled()) {
1746             PerformanceTracker.logEvent("Scene.init for [" + this + "]");
1747         }
1748         mouseHandler = new MouseHandler();
1749         clickGenerator = new ClickGenerator();
1750 
1751         if (PerformanceTracker.isLoggingEnabled()) {
1752             PerformanceTracker.logEvent("Scene.init for [" + this + "] - finished");
1753         }
1754     }
1755 
1756     private void preferredSize() {
1757         final Parent root = getRoot();
1758 
1759         // one or the other isn't initialized, need to perform layout in
1760         // order to ensure we can properly measure the preferred size of the
1761         // scene
1762         doCSSPass();
1763 
1764         resizeRootToPreferredSize(root);
1765         doLayoutPass();
1766 
1767         if (widthSetByUser < 0) {
1768             setWidth(root.isResizable()? root.getLayoutX() + root.getTranslateX() + root.getLayoutBounds().getWidth() :
1769                             root.getBoundsInParent().getMaxX());
1770         } else {
1771             setWidth(widthSetByUser);
1772         }
1773 
1774         if (heightSetByUser < 0) {
1775             setHeight(root.isResizable()? root.getLayoutY() + root.getTranslateY() + root.getLayoutBounds().getHeight() :
1776                             root.getBoundsInParent().getMaxY());
1777         } else {
1778             setHeight(heightSetByUser);
1779         }
1780 
1781         sizeInitialized = (getWidth() > 0) && (getHeight() > 0);
1782 
1783         PerformanceTracker.logEvent("Scene preferred bounds computation complete");
1784     }
1785 
1786     final void resizeRootToPreferredSize(Parent root) {
1787         final double preferredWidth;
1788         final double preferredHeight;
1789 
1790         final Orientation contentBias = root.getContentBias();
1791         if (contentBias == null) {
1792             preferredWidth = getPreferredWidth(root, widthSetByUser, -1);
1793             preferredHeight = getPreferredHeight(root, heightSetByUser, -1);
1794         } else if (contentBias == Orientation.HORIZONTAL) {
1795             // height depends on width
1796             preferredWidth = getPreferredWidth(root, widthSetByUser, -1);
1797             preferredHeight = getPreferredHeight(root, heightSetByUser,
1798                                                        preferredWidth);
1799         } else /* if (contentBias == Orientation.VERTICAL) */ {
1800             // width depends on height
1801             preferredHeight = getPreferredHeight(root, heightSetByUser, -1);
1802             preferredWidth = getPreferredWidth(root, widthSetByUser,
1803                                                      preferredHeight);
1804         }
1805 
1806         root.resize(preferredWidth, preferredHeight);
1807     }
1808 
1809     private static double getPreferredWidth(Parent root,
1810                                             double forcedWidth,
1811                                             double height) {
1812         if (forcedWidth >= 0) {
1813             return forcedWidth;
1814         }
1815         final double normalizedHeight = (height >= 0) ? height : -1;
1816         return root.boundedSize(root.prefWidth(normalizedHeight),
1817                                 root.minWidth(normalizedHeight),
1818                                 root.maxWidth(normalizedHeight));
1819     }
1820 
1821     private static double getPreferredHeight(Parent root,
1822                                              double forcedHeight,
1823                                              double width) {
1824         if (forcedHeight >= 0) {
1825             return forcedHeight;
1826         }
1827         final double normalizedWidth = (width >= 0) ? width : -1;
1828         return root.boundedSize(root.prefHeight(normalizedWidth),
1829                                 root.minHeight(normalizedWidth),
1830                                 root.maxHeight(normalizedWidth));
1831     }
1832 
1833     /**
1834      * @treatAsPrivate implementation detail
1835      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1836      */
1837     @Deprecated
1838     public void impl_preferredSize() {
1839         preferredSize();
1840     }
1841 
1842     private PerformanceTracker tracker;
1843     private static final Object trackerMonitor = new Object();
1844 
1845     // mouse events handling
1846     private MouseHandler mouseHandler;
1847     private ClickGenerator clickGenerator;
1848 
1849     // gesture events handling
1850     private Point2D cursorScreenPos;
1851     private Point2D cursorScenePos;
1852 
1853     private static class TouchGesture {
1854         EventTarget target;
1855         Point2D sceneCoords;
1856         Point2D screenCoords;
1857         boolean finished;
1858     }
1859 
1860     private final TouchGesture scrollGesture = new TouchGesture();
1861     private final TouchGesture zoomGesture = new TouchGesture();
1862     private final TouchGesture rotateGesture = new TouchGesture();
1863     private final TouchGesture swipeGesture = new TouchGesture();
1864 
1865     // touch events handling
1866     private TouchMap touchMap = new TouchMap();
1867     private TouchEvent nextTouchEvent = null;
1868     private TouchPoint[] touchPoints = null;
1869     private int touchEventSetId = 0;
1870     private int touchPointIndex = 0;
1871     private Map<Integer, EventTarget> touchTargets =
1872             new HashMap<Integer, EventTarget>();
1873 
1874     /**
1875      * @treatAsPrivate implementation detail
1876      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1877      */
1878     // SB-dependency: RT-22747 has been filed to track this
1879     @Deprecated
1880     public void impl_processMouseEvent(MouseEvent e) {
1881         mouseHandler.process(e, false);
1882     }
1883 
1884     private void processMenuEvent(double x2, double y2, double xAbs, double yAbs, boolean isKeyboardTrigger) {
1885         EventTarget eventTarget = null;
1886         Scene.inMousePick = true;
1887         if (isKeyboardTrigger) {
1888             Node sceneFocusOwner = getFocusOwner();
1889 
1890             // for keyboard triggers set coordinates inside focus owner
1891             final double xOffset = xAbs - x2;
1892             final double yOffset = yAbs - y2;
1893             if (sceneFocusOwner != null) {
1894                 final Bounds bounds = sceneFocusOwner.localToScene(
1895                         sceneFocusOwner.getBoundsInLocal());
1896                 x2 = bounds.getMinX() + bounds.getWidth() / 4;
1897                 y2 = bounds.getMinY() + bounds.getHeight() / 2;
1898                 eventTarget = sceneFocusOwner;
1899             } else {
1900                 x2 = Scene.this.getWidth() / 4;
1901                 y2 = Scene.this.getWidth() / 2;
1902                 eventTarget = Scene.this;
1903             }
1904 
1905             xAbs = x2 + xOffset;
1906             yAbs = y2 + yOffset;
1907         }
1908 
1909         final PickResult res = pick(x2, y2);
1910 
1911         if (!isKeyboardTrigger) {
1912             eventTarget = res.getIntersectedNode();
1913             if (eventTarget == null) {
1914                 eventTarget = this;
1915             }
1916         }
1917 
1918         if (eventTarget != null) {
1919             ContextMenuEvent context = new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED,
1920                     x2, y2, xAbs, yAbs, isKeyboardTrigger, res);
1921             Event.fireEvent(eventTarget, context);
1922         }
1923         Scene.inMousePick = false;
1924     }
1925 
1926     private void processGestureEvent(GestureEvent e, TouchGesture gesture) {
1927         EventTarget pickedTarget = null;
1928 
1929         if (e.getEventType() == ZoomEvent.ZOOM_STARTED ||
1930                 e.getEventType() == RotateEvent.ROTATION_STARTED ||
1931                 e.getEventType() == ScrollEvent.SCROLL_STARTED) {
1932             gesture.target = null;
1933             gesture.finished = false;
1934         }
1935 
1936         if (gesture.target != null && (!gesture.finished || e.isInertia())) {
1937             pickedTarget = gesture.target;
1938         } else {
1939             pickedTarget = e.getPickResult().getIntersectedNode();
1940             if (pickedTarget == null) {
1941                 pickedTarget = this;
1942             }
1943         }
1944 
1945         if (e.getEventType() == ZoomEvent.ZOOM_STARTED ||
1946                 e.getEventType() == RotateEvent.ROTATION_STARTED ||
1947                 e.getEventType() == ScrollEvent.SCROLL_STARTED) {
1948             gesture.target = pickedTarget;
1949         }
1950         if (e.getEventType() != ZoomEvent.ZOOM_FINISHED &&
1951                 e.getEventType() != RotateEvent.ROTATION_FINISHED &&
1952                 e.getEventType() != ScrollEvent.SCROLL_FINISHED &&
1953                 !e.isInertia()) {
1954             gesture.sceneCoords = new Point2D(e.getSceneX(), e.getSceneY());
1955             gesture.screenCoords = new Point2D(e.getScreenX(), e.getScreenY());
1956         }
1957 
1958         if (pickedTarget != null) {
1959             Event.fireEvent(pickedTarget, e);
1960         }
1961 
1962         if (e.getEventType() == ZoomEvent.ZOOM_FINISHED ||
1963                 e.getEventType() == RotateEvent.ROTATION_FINISHED ||
1964                 e.getEventType() == ScrollEvent.SCROLL_FINISHED) {
1965             gesture.finished = true;
1966         }
1967     }
1968 
1969     private void processTouchEvent(TouchEvent e, TouchPoint[] touchPoints) {
1970         inMousePick = true;
1971         touchEventSetId++;
1972 
1973         List<TouchPoint> touchList = Arrays.asList(touchPoints);
1974 
1975         // fire all the events
1976         for (TouchPoint tp : touchPoints) {
1977             if (tp.getTarget() != null) {
1978                 EventType<TouchEvent> type = null;
1979                 switch (tp.getState()) {
1980                     case MOVED:
1981                         type = TouchEvent.TOUCH_MOVED;
1982                         break;
1983                     case PRESSED:
1984                         type = TouchEvent.TOUCH_PRESSED;
1985                         break;
1986                     case RELEASED:
1987                         type = TouchEvent.TOUCH_RELEASED;
1988                         break;
1989                     case STATIONARY:
1990                         type = TouchEvent.TOUCH_STATIONARY;
1991                         break;
1992                 }
1993 
1994                 for (TouchPoint t : touchPoints) {
1995                     TouchPointHelper.reset(t);
1996                 }
1997 
1998                 TouchEvent te = new TouchEvent(type, tp, touchList,
1999                         touchEventSetId, e.isShiftDown(), e.isControlDown(),
2000                         e.isAltDown(), e.isMetaDown());
2001 
2002                 Event.fireEvent(tp.getTarget(), te);
2003             }
2004         }
2005 
2006         // process grabbing
2007         for (TouchPoint tp : touchPoints) {
2008             EventTarget grabbed = tp.getGrabbed();
2009             if (grabbed != null) {
2010                 touchTargets.put(tp.getId(), grabbed);
2011             };
2012 
2013             if (grabbed == null || tp.getState() == TouchPoint.State.RELEASED) {
2014                 touchTargets.remove(tp.getId());
2015             }
2016         }
2017 
2018         inMousePick = false;
2019     }
2020 
2021     /**
2022      * Note: The only user of this method is in unit test: PickAndContainTest.
2023      */
2024     Node test_pick(double x, double y) {
2025         inMousePick = true;
2026         PickResult result = mouseHandler.pickNode(new PickRay(x, y, 1,
2027                 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
2028         inMousePick = false;
2029         if (result != null) {
2030             return result.getIntersectedNode();
2031         }
2032         return null;
2033     }
2034 
2035     private PickResult pick(final double x, final double y) {
2036         pick(tmpTargetWrapper, x, y);
2037         return tmpTargetWrapper.getResult();
2038     }
2039 
2040     private boolean isInScene(double x, double y) {
2041         if (x < 0 || y < 0 || x > getWidth() || y > getHeight())  {
2042             return false;
2043         }
2044 
2045         Window w = getWindow();
2046         if (w instanceof Stage
2047                 && ((Stage) w).getStyle() == StageStyle.TRANSPARENT
2048                 && getFill() == null) {
2049             return false;
2050         }
2051 
2052         return true;
2053     }
2054 
2055     private void pick(TargetWrapper target, final double x, final double y) {
2056         final PickRay pickRay = getEffectiveCamera().computePickRay(
2057                 x, y, null);
2058 
2059         final double mag = pickRay.getDirectionNoClone().length();
2060         pickRay.getDirectionNoClone().normalize();
2061         final PickResult res = mouseHandler.pickNode(pickRay);
2062         if (res != null) {
2063             target.setNodeResult(res);
2064         } else {
2065             //TODO: is this the intersection with projection plane?
2066             Vec3d o = pickRay.getOriginNoClone();
2067             Vec3d d = pickRay.getDirectionNoClone();
2068             target.setSceneResult(new PickResult(
2069                     null, new Point3D(
2070                     o.x + mag * d.x,
2071                     o.y + mag * d.y,
2072                     o.z + mag * d.z),
2073                     mag),
2074                     isInScene(x, y) ? this : null);
2075         }
2076     }
2077 
2078     /***************************************************************************
2079      *                                                                         *
2080      * Key Events and Focus Traversal                                          *
2081      *                                                                         *
2082      **************************************************************************/
2083 
2084     /*
2085      * We cannot initialize keyHandler in init because some of the triggers
2086      * access it before the init block.
2087      * No clue why def keyHandler = bind lazy {KeyHandler{scene:this};}
2088      * does not compile.
2089      */
2090     private KeyHandler keyHandler = null;
2091     private KeyHandler getKeyHandler() {
2092         if (keyHandler == null) {
2093             keyHandler = new KeyHandler();
2094         }
2095         return keyHandler;
2096     }
2097     /**
2098      * Set to true if something has happened to the focused node that makes
2099      * it no longer eligible to have the focus.
2100      *
2101      */
2102     private boolean focusDirty = true;
2103 
2104     final void setFocusDirty(boolean value) {
2105         if (!focusDirty) {
2106             Toolkit.getToolkit().requestNextPulse();
2107         }
2108         focusDirty = value;
2109     }
2110 
2111     final boolean isFocusDirty() {
2112         return focusDirty;
2113     }
2114 
2115     private TopMostTraversalEngine traversalEngine = new SceneTraversalEngine(this);
2116 
2117     /**
2118      * Traverses focus from the given node in the given direction.
2119      */
2120     boolean traverse(Node node, Direction dir) {
2121         if (node.getSubScene() != null) {
2122             return node.getSubScene().traverse(node, dir);
2123         }
2124         return traversalEngine.trav(node, dir) != null;
2125     }
2126 
2127     /**
2128      * Moves the focus to a reasonable initial location. Called when a scene's
2129      * focus is dirty and there's no current owner, or if the owner has been
2130      * removed from the scene.
2131      */
2132     private void focusInitial() {
2133         traversalEngine.traverseToFirst();
2134     }
2135 
2136     /**
2137      * Moves the focus to a reasonble location "near" the given node.
2138      * Called when the focused node is no longer eligible to have
2139      * the focus because it has become invisible or disabled. This
2140      * function assumes that it is still a member of the same scene.
2141      */
2142     private void focusIneligible(Node node) {
2143         traverse(node, Direction.NEXT);
2144     }
2145 
2146     /**
2147      * @treatAsPrivate implementation detail
2148      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2149      */
2150     // SB-dependency: RT-24668 has been filed to track this
2151     @Deprecated
2152     public void impl_processKeyEvent(KeyEvent e) {
2153         if (dndGesture != null) {
2154             if (!dndGesture.processKey(e)) {
2155                 dndGesture = null;
2156             }
2157         }
2158 
2159         getKeyHandler().process(e);
2160     }
2161 
2162     void requestFocus(Node node) {
2163         getKeyHandler().requestFocus(node);
2164     }
2165 
2166     private Node oldFocusOwner;
2167 
2168     /**
2169       * The scene's current focus owner node. This node's "focused"
2170       * variable might be false if this scene has no window, or if the
2171       * window is inactive (window.focused == false).
2172       * @since JavaFX 2.2
2173       */
2174     private ReadOnlyObjectWrapper<Node> focusOwner = new ReadOnlyObjectWrapper<Node>(this, "focusOwner") {
2175 
2176         @Override
2177         protected void invalidated() {
2178             if (oldFocusOwner != null) {
2179                 ((Node.FocusedProperty) oldFocusOwner.focusedProperty()).store(false);
2180             }
2181             Node value = get();
2182             if (value != null) {
2183                 ((Node.FocusedProperty) value.focusedProperty()).store(keyHandler.windowFocused);
2184                 if (value != oldFocusOwner) {
2185                     value.getScene().impl_enableInputMethodEvents(
2186                             value.getInputMethodRequests() != null
2187                             && value.getOnInputMethodTextChanged() != null);
2188                 }
2189             }
2190             // for the rest of the method we need to update the oldFocusOwner
2191             // and use a local copy of it because the user handlers can cause
2192             // recurrent calls of requestFocus
2193             Node localOldOwner = oldFocusOwner;
2194             oldFocusOwner = value;
2195             if (localOldOwner != null) {
2196                 ((Node.FocusedProperty) localOldOwner.focusedProperty()).notifyListeners();
2197             }
2198             if (value != null) {
2199                 ((Node.FocusedProperty) value.focusedProperty()).notifyListeners();
2200             }
2201             PlatformLogger logger = Logging.getFocusLogger();
2202             if (logger.isLoggable(Level.FINE)) {
2203                 if (value == get()) {
2204                     logger.fine("Changed focus from "
2205                             + localOldOwner + " to " + value);
2206                 } else {
2207                     logger.fine("Changing focus from "
2208                             + localOldOwner + " to " + value
2209                             + " canceled by nested requestFocus");
2210                 }
2211             }
2212             if (accessible != null) {
2213                 accessible.sendNotification(AccessibleAttribute.FOCUS_NODE);
2214             }
2215         }
2216     };
2217 
2218     public final Node getFocusOwner() {
2219         return focusOwner.get();
2220     }
2221 
2222     public final ReadOnlyObjectProperty<Node> focusOwnerProperty() {
2223         return focusOwner.getReadOnlyProperty();
2224     }
2225 
2226     // For testing.
2227     void focusCleanup() {
2228         scenePulseListener.focusCleanup();
2229     }
2230 
2231     private void processInputMethodEvent(InputMethodEvent e) {
2232         Node node = getFocusOwner();
2233         if (node != null) {
2234             node.fireEvent(e);
2235         }
2236     }
2237 
2238     /**
2239      * @treatAsPrivate implementation detail
2240      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2241      */
2242     @Deprecated
2243     public void impl_enableInputMethodEvents(boolean enable) {
2244        if (peer != null) {
2245            peer.enableInputMethodEvents(enable);
2246        }
2247     }
2248 
2249     /**
2250      * Returns true if this scene is quiescent, i.e. it has no activity
2251      * pending on it such as CSS processing or layout requests.
2252      *
2253      * Intended to be used for tests only
2254      *
2255      * @return boolean indicating whether the scene is quiescent
2256      */
2257     boolean isQuiescent() {
2258         final Parent r = getRoot();
2259         return !isFocusDirty()
2260                && (r == null || (r.cssFlag == CssFlags.CLEAN &&
2261                 r.layoutFlag == LayoutFlags.CLEAN));
2262     }
2263 
2264     /**
2265      * A listener for pulses, used for testing. If non-null, this is called at
2266      * the very end of ScenePulseListener.pulse().
2267      *
2268      * Intended to be used for tests only
2269      */
2270     Runnable testPulseListener = null;
2271 
2272     /**
2273      * Set the specified dirty bit and mark the peer as dirty
2274      */
2275     private void markDirty(DirtyBits dirtyBit) {
2276         setDirty(dirtyBit);
2277         if (peer != null) {
2278             Toolkit.getToolkit().requestNextPulse();
2279         }
2280     }
2281 
2282     /**
2283      * Set the specified dirty bit
2284      */
2285     private void setDirty(DirtyBits dirtyBit) {
2286         dirtyBits |= dirtyBit.getMask();
2287     }
2288 
2289     /**
2290      * Test the specified dirty bit
2291      */
2292     private boolean isDirty(DirtyBits dirtyBit) {
2293         return ((dirtyBits & dirtyBit.getMask()) != 0);
2294     }
2295 
2296     /**
2297      * Test whether the dirty bits are empty
2298      */
2299     private boolean isDirtyEmpty() {
2300         return dirtyBits == 0;
2301     }
2302 
2303     /**
2304      * Clear all dirty bits
2305      */
2306     private void clearDirty() {
2307         dirtyBits = 0;
2308     }
2309 
2310     private enum DirtyBits {
2311         FILL_DIRTY,
2312         ROOT_DIRTY,
2313         CAMERA_DIRTY,
2314         LIGHTS_DIRTY,
2315         CURSOR_DIRTY;
2316 
2317         private int mask;
2318 
2319         private DirtyBits() {
2320             mask = 1 << ordinal();
2321         }
2322 
2323         public final int getMask() { return mask; }
2324     }
2325 
2326     private List<LightBase> lights = new ArrayList<>();
2327 
2328     // @param light must not be null
2329     final void addLight(LightBase light) {
2330         if (!lights.contains(light)) {
2331             lights.add(light);
2332             markDirty(DirtyBits.LIGHTS_DIRTY);
2333         }
2334     }
2335 
2336     final void removeLight(LightBase light) {
2337         if (lights.remove(light)) {
2338             markDirty(DirtyBits.LIGHTS_DIRTY);
2339         }
2340     }
2341 
2342     /**
2343      * PG Light synchronizer.
2344      */
2345     private void syncLights() {
2346         if (!isDirty(DirtyBits.LIGHTS_DIRTY)) {
2347             return;
2348         }
2349         inSynchronizer = true;
2350         NGLightBase peerLights[] = peer.getLights();
2351         if (!lights.isEmpty() || (peerLights != null)) {
2352             if (lights.isEmpty()) {
2353                 peer.setLights(null);
2354             } else {
2355                 if (peerLights == null || peerLights.length < lights.size()) {
2356                     peerLights = new NGLightBase[lights.size()];
2357                 }
2358                 int i = 0;
2359                 for (; i < lights.size(); i++) {
2360                     peerLights[i] = lights.get(i).impl_getPeer();
2361                 }
2362                 // Clear the rest of the list
2363                 while (i < peerLights.length && peerLights[i] != null) {
2364                     peerLights[i++] = null;
2365                 }
2366                 peer.setLights(peerLights);
2367             }
2368         }
2369         inSynchronizer = false;
2370     }
2371 
2372     //INNER CLASSES
2373 
2374     /*******************************************************************************
2375      *                                                                             *
2376      * Scene Pulse Listener                                                        *
2377      *                                                                             *
2378      ******************************************************************************/
2379 
2380     class ScenePulseListener implements TKPulseListener {
2381 
2382         private boolean firstPulse = true;
2383 
2384         /**
2385          * PG synchronizer. Called once per frame from the pulse listener.
2386          * This function calls the synchronizePGNode method on each node in
2387          * the dirty list.
2388          */
2389         private void synchronizeSceneNodes() {
2390             Toolkit.getToolkit().checkFxUserThread();
2391 
2392             Scene.inSynchronizer = true;
2393 
2394             // if dirtyNodes is null then that means this Scene has not yet been
2395             // synchronized, and so we will simply synchronize every node in the
2396             // scene and then create the dirty nodes array list
2397             if (Scene.this.dirtyNodes == null) {
2398                 // must do this recursively
2399                 syncAll(getRoot());
2400                 dirtyNodes = new Node[MIN_DIRTY_CAPACITY];
2401 
2402             } else {
2403                 // This is not the first time this scene has been synchronized,
2404                 // so we will only synchronize those nodes that need it
2405                 for (int i = 0 ; i < dirtyNodesSize; ++i) {
2406                     Node node = dirtyNodes[i];
2407                     dirtyNodes[i] = null;
2408                     if (node.getScene() == Scene.this) {
2409                             node.impl_syncPeer();
2410                         }
2411                     }
2412                 dirtyNodesSize = 0;
2413             }
2414 
2415             Scene.inSynchronizer = false;
2416         }
2417 
2418         /**
2419          * Recursive function for synchronizing every node in the scenegraph.
2420          * The return value is the number of nodes in the graph.
2421          */
2422         private int syncAll(Node node) {
2423             node.impl_syncPeer();
2424             int size = 1;
2425             if (node instanceof Parent) {
2426                 Parent p = (Parent) node;
2427                 final int childrenCount = p.getChildren().size();
2428 
2429                 for (int i = 0; i < childrenCount; i++) {
2430                     Node n = p.getChildren().get(i);
2431                     if (n != null) {
2432                         size += syncAll(n);
2433                     }
2434                 }
2435             } else if (node instanceof SubScene) {
2436                 SubScene subScene = (SubScene)node;
2437                 size += syncAll(subScene.getRoot());
2438             }
2439             if (node.getClip() != null) {
2440                 size += syncAll(node.getClip());
2441             }
2442 
2443             return size;
2444         }
2445 
2446         private void synchronizeSceneProperties() {
2447             inSynchronizer = true;
2448             if (isDirty(DirtyBits.ROOT_DIRTY)) {
2449                 peer.setRoot(getRoot().impl_getPeer());
2450             }
2451 
2452             if (isDirty(DirtyBits.FILL_DIRTY)) {
2453                 Toolkit tk = Toolkit.getToolkit();
2454                 peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill()));
2455             }
2456 
2457             // new camera was set on the scene or old camera changed
2458             final Camera cam = getEffectiveCamera();
2459             if (isDirty(DirtyBits.CAMERA_DIRTY)) {
2460                 cam.impl_updatePeer();
2461                 peer.setCamera((NGCamera) cam.impl_getPeer());
2462             }
2463 
2464             if (isDirty(DirtyBits.CURSOR_DIRTY)) {
2465                 mouseHandler.updateCursor(getCursor());
2466             }
2467 
2468             clearDirty();
2469             inSynchronizer = false;
2470         }
2471 
2472         /**
2473          * The focus is considered dirty if something happened to
2474          * the scene graph that may require the focus to be moved.
2475          * This must handle cases where (a) the focus owner may have
2476          * become ineligible to have the focus, and (b) where the focus
2477          * owner is null and a node may have become traversable and eligible.
2478          */
2479         private void focusCleanup() {
2480             if (Scene.this.isFocusDirty()) {
2481                 final Node oldOwner = Scene.this.getFocusOwner();
2482                 if (oldOwner == null) {
2483                     Scene.this.focusInitial();
2484                 } else if (oldOwner.getScene() != Scene.this) {
2485                     Scene.this.requestFocus(null);
2486                     Scene.this.focusInitial();
2487                 } else if (!oldOwner.isCanReceiveFocus()) {
2488                     Scene.this.requestFocus(null);
2489                     Scene.this.focusIneligible(oldOwner);
2490                 }
2491                 Scene.this.setFocusDirty(false);
2492             }
2493         }
2494 
2495         @Override
2496         public void pulse() {
2497             if (Scene.this.tracker != null) {
2498                 Scene.this.tracker.pulse();
2499             }
2500             if (firstPulse) {
2501                 PerformanceTracker.logEvent("Scene - first repaint");
2502             }
2503 
2504             focusCleanup();
2505 
2506             disposeAccessibles();
2507 
2508             // run any scene pre pulse listeners immediately _before_ css / layout,
2509             // and before scene synchronization
2510             if (preLayoutPulseListeners != null) {
2511                 for (Runnable r : preLayoutPulseListeners) {
2512                     r.run();
2513                 }
2514             }
2515 
2516             if (PULSE_LOGGING_ENABLED) {
2517                 PulseLogger.newPhase("CSS Pass");
2518             }
2519             Scene.this.doCSSPass();
2520 
2521             if (PULSE_LOGGING_ENABLED) {
2522                 PulseLogger.newPhase("Layout Pass");
2523             }
2524             Scene.this.doLayoutPass();
2525 
2526             // run any scene post pulse listeners immediately _after_ css / layout,
2527             // and before scene synchronization
2528             if (postLayoutPulseListeners != null) {
2529                 for (Runnable r : postLayoutPulseListeners) {
2530                     r.run();
2531                 }
2532             }
2533 
2534             boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty();
2535             if (dirty) {
2536                 if (PULSE_LOGGING_ENABLED) {
2537                     PulseLogger.newPhase("Update bounds");
2538                 }
2539                 getRoot().updateBounds();
2540                 if (peer != null) {
2541                     try {
2542                         if (PULSE_LOGGING_ENABLED) {
2543                             PulseLogger.newPhase("Waiting for previous rendering");
2544                         }
2545                         peer.waitForRenderingToComplete();
2546                         peer.waitForSynchronization();
2547                         // synchronize scene properties
2548                         if (PULSE_LOGGING_ENABLED) {
2549                             PulseLogger.newPhase("Copy state to render graph");
2550                         }
2551                         syncLights();
2552                         synchronizeSceneProperties();
2553                         // Run the synchronizer
2554                         synchronizeSceneNodes();
2555                         Scene.this.mouseHandler.pulse();
2556                         // Tell the scene peer that it needs to repaint
2557                         peer.markDirty();
2558                     } finally {
2559                         peer.releaseSynchronization(true);
2560                     }
2561                 } else {
2562                     if (PULSE_LOGGING_ENABLED) {
2563                         PulseLogger.newPhase("Synchronize with null peer");
2564                     }
2565                     synchronizeSceneNodes();
2566                     Scene.this.mouseHandler.pulse();
2567                 }
2568 
2569                 if (Scene.this.getRoot().cssFlag != CssFlags.CLEAN) {
2570                     Scene.this.getRoot().impl_markDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
2571                 }
2572             }
2573 
2574             // required for image cursor created from animated image
2575             Scene.this.mouseHandler.updateCursorFrame();
2576 
2577             if (firstPulse) {
2578                 if (PerformanceTracker.isLoggingEnabled()) {
2579                     PerformanceTracker.logEvent("Scene - first repaint - layout complete");
2580                     if (PrismSettings.perfLogFirstPaintFlush) {
2581                         PerformanceTracker.outputLog();
2582                     }
2583                     if (PrismSettings.perfLogFirstPaintExit) {
2584                         System.exit(0);
2585                     }
2586                 }
2587                 firstPulse = false;
2588             }
2589 
2590             if (testPulseListener != null) {
2591                 testPulseListener.run();
2592             }
2593         }
2594     }
2595 
2596     /*******************************************************************************
2597      *                                                                             *
2598      * Scene Peer Listener                                                         *
2599      *                                                                             *
2600      ******************************************************************************/
2601 
2602     class ScenePeerListener implements TKSceneListener {
2603         @Override
2604         public void changedLocation(float x, float y) {
2605             if (x != Scene.this.getX()) {
2606                 Scene.this.setX(x);
2607             }
2608             if (y != Scene.this.getY()) {
2609                 Scene.this.setY(y);
2610             }
2611         }
2612 
2613         @Override
2614         public void changedSize(float w, float h) {
2615             if (w != Scene.this.getWidth()) Scene.this.setWidth(w);
2616             if (h != Scene.this.getHeight()) Scene.this.setHeight(h);
2617         }
2618 
2619         @Override
2620         public void mouseEvent(EventType<MouseEvent> type, double x, double y, double screenX, double screenY,
2621                                MouseButton button, boolean popupTrigger, boolean synthesized,
2622                                boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown,
2623                                boolean primaryDown, boolean middleDown, boolean secondaryDown)
2624         {
2625             MouseEvent mouseEvent = new MouseEvent(type, x, y, screenX, screenY, button,
2626                     0, // click count will be adjusted by clickGenerator later anyway
2627                     shiftDown, controlDown, altDown, metaDown,
2628                     primaryDown, middleDown, secondaryDown, synthesized, popupTrigger, false, null);
2629             impl_processMouseEvent(mouseEvent);
2630         }
2631 
2632 
2633         @Override
2634         public void keyEvent(KeyEvent keyEvent)
2635         {
2636             impl_processKeyEvent(keyEvent);
2637         }
2638 
2639         @Override
2640         public void inputMethodEvent(EventType<InputMethodEvent> type,
2641                                      ObservableList<InputMethodTextRun> composed, String committed,
2642                                      int caretPosition)
2643         {
2644             InputMethodEvent inputMethodEvent = new InputMethodEvent(
2645                type, composed, committed, caretPosition);
2646             processInputMethodEvent(inputMethodEvent);
2647         }
2648 
2649         public void menuEvent(double x, double y, double xAbs, double yAbs,
2650                 boolean isKeyboardTrigger) {
2651             Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger);
2652         }
2653 
2654         @Override
2655         public void scrollEvent(
2656                 EventType<ScrollEvent> eventType,
2657                 double scrollX, double scrollY,
2658                 double totalScrollX, double totalScrollY,
2659                 double xMultiplier, double yMultiplier,
2660                 int touchCount,
2661                 int scrollTextX, int scrollTextY,
2662                 int defaultTextX, int defaultTextY,
2663                 double x, double y, double screenX, double screenY,
2664                 boolean _shiftDown, boolean _controlDown,
2665                 boolean _altDown, boolean _metaDown,
2666                 boolean _direct, boolean _inertia) {
2667 
2668             ScrollEvent.HorizontalTextScrollUnits xUnits = scrollTextX > 0 ?
2669                     ScrollEvent.HorizontalTextScrollUnits.CHARACTERS :
2670                     ScrollEvent.HorizontalTextScrollUnits.NONE;
2671 
2672             double xText = scrollTextX < 0 ? 0 : scrollTextX * scrollX;
2673 
2674             ScrollEvent.VerticalTextScrollUnits yUnits = scrollTextY > 0 ?
2675                     ScrollEvent.VerticalTextScrollUnits.LINES :
2676                     (scrollTextY < 0 ?
2677                         ScrollEvent.VerticalTextScrollUnits.PAGES :
2678                         ScrollEvent.VerticalTextScrollUnits.NONE);
2679 
2680             double yText = scrollTextY < 0 ? scrollY : scrollTextY * scrollY;
2681 
2682             xMultiplier = defaultTextX > 0 && scrollTextX >= 0
2683                     ? Math.round(xMultiplier * scrollTextX / defaultTextX)
2684                     : xMultiplier;
2685 
2686             yMultiplier = defaultTextY > 0 && scrollTextY >= 0
2687                     ? Math.round(yMultiplier * scrollTextY / defaultTextY)
2688                     : yMultiplier;
2689 
2690             if (eventType == ScrollEvent.SCROLL_FINISHED) {
2691                 x = scrollGesture.sceneCoords.getX();
2692                 y = scrollGesture.sceneCoords.getY();
2693                 screenX = scrollGesture.screenCoords.getX();
2694                 screenY = scrollGesture.screenCoords.getY();
2695             } else if (Double.isNaN(x) || Double.isNaN(y) ||
2696                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2697                 if (cursorScenePos == null || cursorScreenPos == null) {
2698                     return;
2699                 }
2700                 x = cursorScenePos.getX();
2701                 y = cursorScenePos.getY();
2702                 screenX = cursorScreenPos.getX();
2703                 screenY = cursorScreenPos.getY();
2704             }
2705 
2706             inMousePick = true;
2707             Scene.this.processGestureEvent(new ScrollEvent(
2708                     eventType,
2709                     x, y, screenX, screenY,
2710                     _shiftDown, _controlDown, _altDown, _metaDown,
2711                     _direct, _inertia,
2712                     scrollX * xMultiplier, scrollY * yMultiplier,
2713                     totalScrollX * xMultiplier, totalScrollY * yMultiplier,
2714                     xMultiplier, yMultiplier,
2715                     xUnits, xText, yUnits, yText, touchCount, pick(x, y)),
2716                     scrollGesture);
2717             inMousePick = false;
2718         }
2719 
2720         @Override
2721         public void zoomEvent(
2722                 EventType<ZoomEvent> eventType,
2723                 double zoomFactor, double totalZoomFactor,
2724                 double x, double y, double screenX, double screenY,
2725                 boolean _shiftDown, boolean _controlDown,
2726                 boolean _altDown, boolean _metaDown,
2727                 boolean _direct, boolean _inertia) {
2728 
2729             if (eventType == ZoomEvent.ZOOM_FINISHED) {
2730                 x = zoomGesture.sceneCoords.getX();
2731                 y = zoomGesture.sceneCoords.getY();
2732                 screenX = zoomGesture.screenCoords.getX();
2733                 screenY = zoomGesture.screenCoords.getY();
2734             } else if (Double.isNaN(x) || Double.isNaN(y) ||
2735                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2736                 if (cursorScenePos == null || cursorScreenPos == null) {
2737                     return;
2738                 }
2739                 x = cursorScenePos.getX();
2740                 y = cursorScenePos.getY();
2741                 screenX = cursorScreenPos.getX();
2742                 screenY = cursorScreenPos.getY();
2743             }
2744 
2745             inMousePick = true;
2746             Scene.this.processGestureEvent(new ZoomEvent(eventType,
2747                     x, y, screenX, screenY,
2748                     _shiftDown, _controlDown, _altDown, _metaDown,
2749                     _direct, _inertia,
2750                     zoomFactor, totalZoomFactor, pick(x, y)),
2751                     zoomGesture);
2752             inMousePick = false;
2753         }
2754 
2755         @Override
2756         public void rotateEvent(
2757                 EventType<RotateEvent> eventType, double angle, double totalAngle,
2758                 double x, double y, double screenX, double screenY,
2759                 boolean _shiftDown, boolean _controlDown,
2760                 boolean _altDown, boolean _metaDown,
2761                 boolean _direct, boolean _inertia) {
2762 
2763             if (eventType == RotateEvent.ROTATION_FINISHED) {
2764                 x = rotateGesture.sceneCoords.getX();
2765                 y = rotateGesture.sceneCoords.getY();
2766                 screenX = rotateGesture.screenCoords.getX();
2767                 screenY = rotateGesture.screenCoords.getY();
2768             } else if (Double.isNaN(x) || Double.isNaN(y) ||
2769                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2770                 if (cursorScenePos == null || cursorScreenPos == null) {
2771                     return;
2772                 }
2773                 x = cursorScenePos.getX();
2774                 y = cursorScenePos.getY();
2775                 screenX = cursorScreenPos.getX();
2776                 screenY = cursorScreenPos.getY();
2777             }
2778 
2779             inMousePick = true;
2780             Scene.this.processGestureEvent(new RotateEvent(
2781                     eventType, x, y, screenX, screenY,
2782                     _shiftDown, _controlDown, _altDown, _metaDown,
2783                     _direct, _inertia, angle, totalAngle, pick(x, y)),
2784                     rotateGesture);
2785             inMousePick = false;
2786 
2787         }
2788 
2789         @Override
2790         public void swipeEvent(
2791                 EventType<SwipeEvent> eventType, int touchCount,
2792                 double x, double y, double screenX, double screenY,
2793                 boolean _shiftDown, boolean _controlDown,
2794                 boolean _altDown, boolean _metaDown, boolean _direct) {
2795 
2796             if (Double.isNaN(x) || Double.isNaN(y) ||
2797                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2798                 if (cursorScenePos == null || cursorScreenPos == null) {
2799                     return;
2800                 }
2801                 x = cursorScenePos.getX();
2802                 y = cursorScenePos.getY();
2803                 screenX = cursorScreenPos.getX();
2804                 screenY = cursorScreenPos.getY();
2805             }
2806 
2807             inMousePick = true;
2808             Scene.this.processGestureEvent(new SwipeEvent(
2809                     eventType, x, y, screenX, screenY,
2810                     _shiftDown, _controlDown, _altDown, _metaDown, _direct,
2811                     touchCount, pick(x, y)),
2812                     swipeGesture);
2813             inMousePick = false;
2814         }
2815 
2816         @Override
2817         public void touchEventBegin(
2818                 long time, int touchCount, boolean isDirect,
2819                 boolean _shiftDown, boolean _controlDown,
2820                 boolean _altDown, boolean _metaDown) {
2821 
2822             if (!isDirect) {
2823                 nextTouchEvent = null;
2824                 return;
2825             }
2826             nextTouchEvent = new TouchEvent(
2827                     TouchEvent.ANY, null, null, 0,
2828                     _shiftDown, _controlDown, _altDown, _metaDown);
2829             if (touchPoints == null || touchPoints.length != touchCount) {
2830                 touchPoints = new TouchPoint[touchCount];
2831             }
2832             touchPointIndex = 0;
2833         }
2834 
2835         @Override
2836         public void touchEventNext(
2837                 TouchPoint.State state, long touchId,
2838                 double x, double y, double screenX, double screenY) {
2839 
2840             inMousePick = true;
2841             if (nextTouchEvent == null) {
2842                 // ignore indirect touch events
2843                 return;
2844             }
2845             touchPointIndex++;
2846             int id = (state == TouchPoint.State.PRESSED
2847                     ? touchMap.add(touchId) :  touchMap.get(touchId));
2848             if (state == TouchPoint.State.RELEASED) {
2849                 touchMap.remove(touchId);
2850             }
2851             int order = touchMap.getOrder(id);
2852 
2853             if (order >= touchPoints.length) {
2854                 throw new RuntimeException("Too many touch points reported");
2855             }
2856 
2857             // pick target
2858             boolean isGrabbed = false;
2859             PickResult pickRes = pick(x, y);
2860             EventTarget pickedTarget = touchTargets.get(id);
2861             if (pickedTarget == null) {
2862                 pickedTarget = pickRes.getIntersectedNode();
2863                 if (pickedTarget == null) {
2864                     pickedTarget = Scene.this;
2865                 }
2866             } else {
2867                 isGrabbed = true;
2868             }
2869 
2870             TouchPoint tp = new TouchPoint(id, state,
2871                     x, y, screenX, screenY, pickedTarget, pickRes);
2872 
2873             touchPoints[order] = tp;
2874 
2875             if (isGrabbed) {
2876                 tp.grab(pickedTarget);
2877             }
2878             if (tp.getState() == TouchPoint.State.PRESSED) {
2879                 tp.grab(pickedTarget);
2880                 touchTargets.put(tp.getId(), pickedTarget);
2881             } else if (tp.getState() == TouchPoint.State.RELEASED) {
2882                 touchTargets.remove(tp.getId());
2883             }
2884             inMousePick = false;
2885         }
2886 
2887         @Override
2888         public void touchEventEnd() {
2889             if (nextTouchEvent == null) {
2890                 // ignore indirect touch events
2891                 return;
2892             }
2893 
2894             if (touchPointIndex != touchPoints.length) {
2895                 throw new RuntimeException("Wrong number of touch points reported");
2896             }
2897 
2898             Scene.this.processTouchEvent(nextTouchEvent, touchPoints);
2899 
2900             if (touchMap.cleanup()) {
2901                 // gesture finished
2902                 touchEventSetId = 0;
2903             }
2904         }
2905 
2906         @Override
2907         public Accessible getSceneAccessible() {
2908             return getAccessible();
2909         }
2910     }
2911 
2912     private class ScenePeerPaintListener implements TKScenePaintListener {
2913         @Override
2914         public void frameRendered() {
2915             // must use tracker with synchronization since this method is called on render thread
2916             synchronized (trackerMonitor) {
2917                 if (Scene.this.tracker != null) {
2918                     Scene.this.tracker.frameRendered();
2919                 }
2920             }
2921         }
2922     }
2923 
2924     /*******************************************************************************
2925      *                                                                             *
2926      * Drag and Drop                                                               *
2927      *                                                                             *
2928      ******************************************************************************/
2929 
2930     class DropTargetListener implements TKDropTargetListener {
2931 
2932         /*
2933          * This function is called when an drag operation enters a valid drop target.
2934          * This may be from either an internal or external dnd operation.
2935          */
2936         @Override
2937         public TransferMode dragEnter(double x, double y, double screenX, double screenY,
2938                                       TransferMode transferMode, TKClipboard dragboard)
2939         {
2940             if (dndGesture == null) {
2941                 dndGesture = new DnDGesture();
2942             }
2943             Dragboard db = DragboardHelper.createDragboard(dragboard);
2944             dndGesture.dragboard = db;
2945             DragEvent dragEvent =
2946                     new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
2947                             transferMode, null, null, pick(x, y));
2948             return dndGesture.processTargetEnterOver(dragEvent);
2949         }
2950 
2951         @Override
2952         public TransferMode dragOver(double x, double y, double screenX, double screenY,
2953                                      TransferMode transferMode)
2954         {
2955             if (Scene.this.dndGesture == null) {
2956                 System.err.println("GOT A dragOver when dndGesture is null!");
2957                 return null;
2958             } else {
2959                 if (dndGesture.dragboard == null) {
2960                     throw new RuntimeException("dndGesture.dragboard is null in dragOver");
2961                 }
2962                 DragEvent dragEvent =
2963                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
2964                                 transferMode, null, null, pick(x, y));
2965                 return dndGesture.processTargetEnterOver(dragEvent);
2966             }
2967         }
2968 
2969         @Override
2970         public void dragExit(double x, double y, double screenX, double screenY) {
2971             if (dndGesture == null) {
2972                 System.err.println("GOT A dragExit when dndGesture is null!");
2973             } else {
2974                 if (dndGesture.dragboard == null) {
2975                     throw new RuntimeException("dndGesture.dragboard is null in dragExit");
2976                 }
2977                 DragEvent dragEvent =
2978                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
2979                                 null, null, null, pick(x, y));
2980                 dndGesture.processTargetExit(dragEvent);
2981                 if (dndGesture.source == null) {
2982                     dndGesture.dragboard = null;
2983                     dndGesture = null;
2984                 }
2985             }
2986         }
2987 
2988 
2989         @Override
2990         public TransferMode drop(double x, double y, double screenX, double screenY,
2991                                   TransferMode transferMode)
2992         {
2993             if (dndGesture == null) {
2994                 System.err.println("GOT A drop when dndGesture is null!");
2995                 return null;
2996             } else {
2997                 if (dndGesture.dragboard == null) {
2998                     throw new RuntimeException("dndGesture.dragboard is null in dragDrop");
2999                 }
3000                 DragEvent dragEvent =
3001                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
3002                                 transferMode, null, null, pick(x, y));
3003                 // Data dropped to the app can be accessed without restriction
3004                 DragboardHelper.setDataAccessRestriction(dndGesture.dragboard, false);
3005 
3006                 TransferMode tm;
3007                 try {
3008                     tm = dndGesture.processTargetDrop(dragEvent);
3009                 } finally {
3010                     DragboardHelper.setDataAccessRestriction(
3011                             dndGesture.dragboard, true);
3012                 }
3013 
3014                 if (dndGesture.source == null) {
3015                     dndGesture.dragboard = null;
3016                     dndGesture = null;
3017                 }
3018                 return tm;
3019             }
3020         }
3021     }
3022 
3023     class DragGestureListener implements TKDragGestureListener {
3024 
3025        @Override
3026        public void dragGestureRecognized(double x, double y, double screenX, double screenY,
3027                                          int button, TKClipboard dragboard)
3028        {
3029            Dragboard db = DragboardHelper.createDragboard(dragboard);
3030            dndGesture = new DnDGesture();
3031            dndGesture.dragboard = db;
3032            // TODO: support mouse buttons in DragEvent
3033            DragEvent dragEvent = new DragEvent(DragEvent.ANY, db, x, y, screenX, screenY,
3034                    null, null, null, pick(x, y));
3035            dndGesture.processRecognized(dragEvent);
3036            dndGesture = null;
3037         }
3038     }
3039 
3040     /**
3041      * A Drag and Drop gesture has a lifespan that lasts from mouse
3042      * PRESSED event to mouse RELEASED event.
3043      */
3044     class DnDGesture {
3045         private final double hysteresisSizeX =
3046                 Toolkit.getToolkit().getMultiClickMaxX();
3047         private final double hysteresisSizeY =
3048                 Toolkit.getToolkit().getMultiClickMaxY();
3049 
3050         private EventTarget source = null;
3051         private Set<TransferMode> sourceTransferModes = null;
3052         private TransferMode acceptedTransferMode = null;
3053         private Dragboard dragboard = null;
3054         private EventTarget potentialTarget = null;
3055         private EventTarget target = null;
3056         private DragDetectedState dragDetected = DragDetectedState.NOT_YET;
3057         private double pressedX;
3058         private double pressedY;
3059         private List<EventTarget> currentTargets = new ArrayList<EventTarget>();
3060         private List<EventTarget> newTargets = new ArrayList<EventTarget>();
3061         private EventTarget fullPDRSource = null;
3062 
3063         /**
3064          * Fires event on a given target or on scene if the node is null
3065          */
3066         private void fireEvent(EventTarget target, Event e) {
3067             if (target != null) {
3068                 Event.fireEvent(target, e);
3069             }
3070         }
3071 
3072         /**
3073          * Called when DRAG_DETECTED event is going to be processed by
3074          * application
3075          */
3076         private void processingDragDetected() {
3077             dragDetected = DragDetectedState.PROCESSING;
3078         }
3079 
3080         /**
3081          * Called after DRAG_DETECTED event has been processed by application
3082          */
3083         private void dragDetectedProcessed() {
3084             dragDetected = DragDetectedState.DONE;
3085             final boolean hasContent = (dragboard != null) && (ClipboardHelper.contentPut(dragboard));
3086             if (hasContent) {
3087                 /* start DnD */
3088                 Toolkit.getToolkit().startDrag(Scene.this.peer,
3089                                                 sourceTransferModes,
3090                                                 new DragSourceListener(),
3091                                                 dragboard);
3092             } else if (fullPDRSource != null) {
3093                 /* start PDR */
3094                 Scene.this.mouseHandler.enterFullPDR(fullPDRSource);
3095             }
3096 
3097             fullPDRSource = null;
3098         }
3099 
3100         /**
3101          * Sets the default dragDetect value
3102          */
3103         private void processDragDetection(MouseEvent mouseEvent) {
3104 
3105             if (dragDetected != DragDetectedState.NOT_YET) {
3106                 mouseEvent.setDragDetect(false);
3107                 return;
3108             }
3109 
3110             if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
3111                 pressedX = mouseEvent.getSceneX();
3112                 pressedY = mouseEvent.getSceneY();
3113 
3114                 mouseEvent.setDragDetect(false);
3115 
3116             } else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) {
3117 
3118                 double deltaX = Math.abs(mouseEvent.getSceneX() - pressedX);
3119                 double deltaY = Math.abs(mouseEvent.getSceneY() - pressedY);
3120                 mouseEvent.setDragDetect(deltaX > hysteresisSizeX ||
3121                                          deltaY > hysteresisSizeY);
3122 
3123             }
3124         }
3125 
3126         /**
3127          * This function is useful for drag gesture recognition from
3128          * within this Scene (as opposed to in the TK implementation... by the platform)
3129          */
3130         private boolean process(MouseEvent mouseEvent, EventTarget target) {
3131             boolean continueProcessing = true;
3132             if (!PLATFORM_DRAG_GESTURE_INITIATION) {
3133 
3134                 if (dragDetected != DragDetectedState.DONE &&
3135                         (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED ||
3136                         mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) &&
3137                         mouseEvent.isDragDetect()) {
3138 
3139                     processingDragDetected();
3140 
3141                     if (target != null) {
3142                         final MouseEvent detectedEvent = mouseEvent.copyFor(
3143                                 mouseEvent.getSource(), target,
3144                                 MouseEvent.DRAG_DETECTED);
3145 
3146                         try {
3147                             fireEvent(target, detectedEvent);
3148                         } finally {
3149                             // Putting data to dragboard finished, restrict access to them
3150                             if (dragboard != null) {
3151                                 DragboardHelper.setDataAccessRestriction(
3152                                         dragboard, true);
3153                             }
3154                         }
3155                     }
3156 
3157                     dragDetectedProcessed();
3158                 }
3159 
3160                 if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED) {
3161                     continueProcessing = false;
3162                 }
3163             }
3164             return continueProcessing;
3165         }
3166 
3167         /*
3168          * Called when a drag source is recognized. This occurs at the very start of
3169          * the publicly visible drag and drop API, as it is responsible for calling
3170          * the Node.onDragSourceRecognized function.
3171          */
3172         private boolean processRecognized(DragEvent de) {
3173             MouseEvent me = new MouseEvent(
3174                     MouseEvent.DRAG_DETECTED, de.getX(), de.getY(),
3175                     de.getSceneX(), de.getScreenY(), MouseButton.PRIMARY, 1,
3176                     false, false, false, false, false, true, false, false, false,
3177                     false, de.getPickResult());
3178 
3179             processingDragDetected();
3180 
3181             final EventTarget target = de.getPickResult().getIntersectedNode();
3182             try {
3183                 fireEvent(target != null ? target : Scene.this, me);
3184             } finally {
3185                 // Putting data to dragboard finished, restrict access to them
3186                 if (dragboard != null) {
3187                     DragboardHelper.setDataAccessRestriction(
3188                             dragboard, true);
3189                 }
3190             }
3191 
3192             dragDetectedProcessed();
3193 
3194             final boolean hasContent = dragboard != null
3195                     && !dragboard.getContentTypes().isEmpty();
3196             return hasContent;
3197         }
3198 
3199         private void processDropEnd(DragEvent de) {
3200             if (source == null) {
3201                 System.out.println("Scene.DnDGesture.processDropEnd() - UNEXPECTD - source is NULL");
3202                 return;
3203             }
3204 
3205             de = new DragEvent(de.getSource(), source, DragEvent.DRAG_DONE,
3206                     de.getDragboard(), de.getSceneX(), de.getSceneY(),
3207                     de.getScreenX(), de.getScreenY(),
3208                     de.getTransferMode(), source, target, de.getPickResult());
3209 
3210             Event.fireEvent(source, de);
3211 
3212             tmpTargetWrapper.clear();
3213             handleExitEnter(de, tmpTargetWrapper);
3214 
3215             // at this point the drag and drop operation is completely over, so we
3216             // can tell the toolkit that it can clean up if needs be.
3217             Toolkit.getToolkit().stopDrag(dragboard);
3218         }
3219 
3220         private TransferMode processTargetEnterOver(DragEvent de) {
3221             pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY());
3222             final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget();
3223 
3224             if (dragboard == null) {
3225                 dragboard = createDragboard(de, false);
3226             }
3227 
3228             de = new DragEvent(de.getSource(), pickedTarget, de.getEventType(),
3229                     dragboard, de.getSceneX(), de.getSceneY(),
3230                     de.getScreenX(), de.getScreenY(),
3231                     de.getTransferMode(), source, potentialTarget, de.getPickResult());
3232 
3233             handleExitEnter(de, tmpTargetWrapper);
3234 
3235             de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_OVER,
3236                     de.getDragboard(), de.getSceneX(), de.getSceneY(),
3237                     de.getScreenX(), de.getScreenY(),
3238                     de.getTransferMode(), source, potentialTarget, de.getPickResult());
3239 
3240             fireEvent(pickedTarget, de);
3241 
3242             Object acceptingObject = de.getAcceptingObject();
3243             potentialTarget = acceptingObject instanceof EventTarget
3244                     ? (EventTarget) acceptingObject : null;
3245             acceptedTransferMode = de.getAcceptedTransferMode();
3246             return acceptedTransferMode;
3247         }
3248 
3249         private void processTargetActionChanged(DragEvent de) {
3250             // Do we want DRAG_TRANSFER_MODE_CHANGED event?
3251 //            final Node pickedNode = Scene.this.mouseHandler.pickNode(de.getX(), de.getY());
3252 //            if (pickedNode != null && pickedNode.impl_isTreeVisible()) {
3253 //                de = DragEvent.impl_copy(de.getSource(), pickedNode, source,
3254 //                        pickedNode, de, DragEvent.DRAG_TRANSFER_MODE_CHANGED);
3255 //
3256 //                if (dragboard == null) {
3257 //                    dragboard = createDragboard(de);
3258 //                }
3259 //                dragboard = de.impl_getPlatformDragboard();
3260 //
3261 //                fireEvent(pickedNode, de);
3262 //            }
3263         }
3264 
3265         private void processTargetExit(DragEvent de) {
3266             if (dragboard == null) {
3267                 // dragboard should have been created in processTargetEnterOver()
3268                 throw new NullPointerException("dragboard is null in processTargetExit()");
3269             }
3270 
3271             if (currentTargets.size() > 0) {
3272                 potentialTarget = null;
3273                 tmpTargetWrapper.clear();
3274                 handleExitEnter(de, tmpTargetWrapper);
3275             }
3276         }
3277 
3278         private TransferMode processTargetDrop(DragEvent de) {
3279             pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY());
3280             final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget();
3281 
3282             de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_DROPPED,
3283                     de.getDragboard(), de.getSceneX(), de.getSceneY(),
3284                     de.getScreenX(), de.getScreenY(),
3285                     acceptedTransferMode, source, potentialTarget, de.getPickResult());
3286 
3287             if (dragboard == null) {
3288                 // dragboard should have been created in processTargetEnterOver()
3289                 throw new NullPointerException("dragboard is null in processTargetDrop()");
3290             }
3291 
3292             handleExitEnter(de, tmpTargetWrapper);
3293 
3294             fireEvent(pickedTarget, de);
3295 
3296             Object acceptingObject = de.getAcceptingObject();
3297             potentialTarget = acceptingObject instanceof EventTarget
3298                     ? (EventTarget) acceptingObject : null;
3299             target = potentialTarget;
3300 
3301             TransferMode result = de.isDropCompleted() ?
3302                 de.getAcceptedTransferMode() : null;
3303 
3304             tmpTargetWrapper.clear();
3305             handleExitEnter(de, tmpTargetWrapper);
3306 
3307             return result;
3308         }
3309 
3310         private void handleExitEnter(DragEvent e, TargetWrapper target) {
3311             EventTarget currentTarget =
3312                     currentTargets.size() > 0 ? currentTargets.get(0) : null;
3313 
3314             if (target.getEventTarget() != currentTarget) {
3315 
3316                 target.fillHierarchy(newTargets);
3317 
3318                 int i = currentTargets.size() - 1;
3319                 int j = newTargets.size() - 1;
3320 
3321                 while (i >= 0 && j >= 0 && currentTargets.get(i) == newTargets.get(j)) {
3322                     i--;
3323                     j--;
3324                 }
3325 
3326                 for (; i >= 0; i--) {
3327                     EventTarget t = currentTargets.get(i);
3328                     if (potentialTarget == t) {
3329                         potentialTarget = null;
3330                     }
3331                     e = e.copyFor(e.getSource(), t, source,
3332                             potentialTarget, DragEvent.DRAG_EXITED_TARGET);
3333                     Event.fireEvent(t, e);
3334                 }
3335 
3336                 potentialTarget = null;
3337                 for (; j >= 0; j--) {
3338                     EventTarget t = newTargets.get(j);
3339                     e = e.copyFor(e.getSource(), t, source,
3340                             potentialTarget, DragEvent.DRAG_ENTERED_TARGET);
3341                     Object acceptingObject = e.getAcceptingObject();
3342                     if (acceptingObject instanceof EventTarget) {
3343                         potentialTarget = (EventTarget) acceptingObject;
3344                     }
3345                     Event.fireEvent(t, e);
3346                 }
3347 
3348                 currentTargets.clear();
3349                 currentTargets.addAll(newTargets);
3350                 newTargets.clear();
3351             }
3352         }
3353 
3354 //        function getIntendedTransferMode(e:MouseEvent):TransferMode {
3355 //            return if (e.altDown) TransferMode.COPY else TransferMode.MOVE;
3356 //        }
3357 
3358         /*
3359          * Function that hooks into the key processing code in Scene to handle the
3360          * situation where a drag and drop event is taking place and the user presses
3361          * the escape key to cancel the drag and drop operation.
3362          */
3363         private boolean processKey(KeyEvent e) {
3364             //note: this seems not to be called, the DnD cancelation is provided by platform
3365             if ((e.getEventType() == KeyEvent.KEY_PRESSED) && (e.getCode() == KeyCode.ESCAPE)) {
3366 
3367                 // cancel drag and drop
3368                 DragEvent de = new DragEvent(
3369                         source, source, DragEvent.DRAG_DONE, dragboard, 0, 0, 0, 0,
3370                         null, source, null, null);
3371                 if (source != null) {
3372                     Event.fireEvent(source, de);
3373                 }
3374 
3375                 tmpTargetWrapper.clear();
3376                 handleExitEnter(de, tmpTargetWrapper);
3377 
3378                 return false;
3379             }
3380             return true;
3381         }
3382 
3383         /*
3384          * This starts the drag gesture running, creating the dragboard used for
3385          * the remainder of this drag and drop operation.
3386          */
3387         private Dragboard startDrag(EventTarget source, Set<TransferMode> t) {
3388             if (dragDetected != DragDetectedState.PROCESSING) {
3389                 throw new IllegalStateException("Cannot start drag and drop "
3390                         + "outside of DRAG_DETECTED event handler");
3391             }
3392 
3393             if (t.isEmpty()) {
3394                 dragboard = null;
3395             } else if (dragboard == null) {
3396                 dragboard = createDragboard(null, true);
3397             }
3398 
3399             // The app can see what it puts to dragboard without restriction
3400             DragboardHelper.setDataAccessRestriction(dragboard, false);
3401 
3402             this.source = source;
3403             potentialTarget = source;
3404             sourceTransferModes = t;
3405             return dragboard;
3406         }
3407 
3408         /*
3409          * This starts the full PDR gesture.
3410          */
3411         private void startFullPDR(EventTarget source) {
3412             fullPDRSource = source;
3413         }
3414 
3415         private Dragboard createDragboard(final DragEvent de, boolean isDragSource) {
3416             Dragboard dragboard = null;
3417             if (de != null) {
3418                 dragboard = de.getDragboard();
3419                 if (dragboard != null) {
3420                     return dragboard;
3421                 }
3422             }
3423             TKClipboard dragboardPeer = peer.createDragboard(isDragSource);
3424             return DragboardHelper.createDragboard(dragboardPeer);
3425         }
3426     }
3427 
3428     /**
3429      * State of a drag gesture with regards to DRAG_DETECTED event.
3430      */
3431     private enum DragDetectedState {
3432         NOT_YET,
3433         PROCESSING,
3434         DONE
3435     }
3436 
3437     class DragSourceListener implements TKDragSourceListener {
3438 
3439         @Override
3440         public void dragDropEnd(double x, double y, double screenX, double screenY,
3441                                 TransferMode transferMode)
3442         {
3443             if (dndGesture != null) {
3444                 if (dndGesture.dragboard == null) {
3445                     throw new RuntimeException("dndGesture.dragboard is null in dragDropEnd");
3446                 }
3447                 DragEvent dragEvent =
3448                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
3449                         transferMode, null, null, null);
3450 
3451                 // DRAG_DONE event is delivered to gesture source, it can access
3452                 // its own data without restriction
3453                 DragboardHelper.setDataAccessRestriction(dndGesture.dragboard, false);
3454                 try {
3455                     dndGesture.processDropEnd(dragEvent);
3456                 } finally {
3457                     DragboardHelper.setDataAccessRestriction(dndGesture.dragboard, true);
3458                 }
3459                 dndGesture = null;
3460             }
3461         }
3462     }
3463 
3464     /*******************************************************************************
3465      *                                                                             *
3466      * Mouse Event Handling                                                        *
3467      *                                                                             *
3468      ******************************************************************************/
3469 
3470     static class ClickCounter {
3471         Toolkit toolkit = Toolkit.getToolkit();
3472         private int count;
3473         private boolean out;
3474         private boolean still;
3475         private Timeline timeout;
3476         private double pressedX, pressedY;
3477 
3478         private void inc() { count++; }
3479         private int get() { return count; }
3480         private boolean isStill() { return still; }
3481 
3482         private void clear() {
3483             count = 0;
3484             stopTimeout();
3485         }
3486 
3487         private void out() {
3488             out = true;
3489             stopTimeout();
3490         }
3491 
3492         private void applyOut() {
3493             if (out) clear();
3494             out = false;
3495         }
3496 
3497         private void moved(double x, double y) {
3498             if (Math.abs(x - pressedX) > toolkit.getMultiClickMaxX() ||
3499                     Math.abs(y - pressedY) > toolkit.getMultiClickMaxY()) {
3500                 out();
3501                 still = false;
3502             }
3503         }
3504 
3505         private void start(double x, double y) {
3506             pressedX = x;
3507             pressedY = y;
3508             out = false;
3509 
3510             if (timeout != null) {
3511                 timeout.stop();
3512             }
3513             timeout = new Timeline();
3514             timeout.getKeyFrames().add(
3515                     new KeyFrame(new Duration(toolkit.getMultiClickTime()),
3516                             event -> {
3517                                 out = true;
3518                                 timeout = null;
3519                             }
3520                     ));
3521             timeout.play();
3522             still = true;
3523         }
3524 
3525         private void stopTimeout() {
3526             if (timeout != null) {
3527                 timeout.stop();
3528                 timeout = null;
3529             }
3530         }
3531     }
3532 
3533     static class ClickGenerator {
3534         private ClickCounter lastPress = null;
3535 
3536         private Map<MouseButton, ClickCounter> counters =
3537                 new EnumMap<MouseButton, ClickCounter>(MouseButton.class);
3538         private List<EventTarget> pressedTargets = new ArrayList<EventTarget>();
3539         private List<EventTarget> releasedTargets = new ArrayList<EventTarget>();
3540 
3541         public ClickGenerator() {
3542             for (MouseButton mb : MouseButton.values()) {
3543                 if (mb != MouseButton.NONE) {
3544                     counters.put(mb, new ClickCounter());
3545                 }
3546             }
3547         }
3548 
3549         private MouseEvent preProcess(MouseEvent e) {
3550             for (ClickCounter cc : counters.values()) {
3551                 cc.moved(e.getSceneX(), e.getSceneY());
3552             }
3553 
3554             ClickCounter cc = counters.get(e.getButton());
3555             boolean still = lastPress != null ? lastPress.isStill() : false;
3556 
3557             if (e.getEventType() == MouseEvent.MOUSE_PRESSED) {
3558 
3559                 if (! e.isPrimaryButtonDown()) { counters.get(MouseButton.PRIMARY).clear(); }
3560                 if (! e.isSecondaryButtonDown()) { counters.get(MouseButton.SECONDARY).clear(); }
3561                 if (! e.isMiddleButtonDown()) { counters.get(MouseButton.MIDDLE).clear(); }
3562 
3563                 cc.applyOut();
3564                 cc.inc();
3565                 cc.start(e.getSceneX(), e.getSceneY());
3566                 lastPress = cc;
3567             }
3568 
3569             return new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(),
3570                     e.getScreenX(), e.getScreenY(), e.getButton(),
3571                     cc != null && e.getEventType() != MouseEvent.MOUSE_MOVED ? cc.get() : 0,
3572                     e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(),
3573                     e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(),
3574                     e.isSynthesized(), e.isPopupTrigger(), still, e.getPickResult());
3575         }
3576 
3577         private void postProcess(MouseEvent e, TargetWrapper target, TargetWrapper pickedTarget) {
3578 
3579             if (e.getEventType() == MouseEvent.MOUSE_RELEASED) {
3580                 ClickCounter cc = counters.get(e.getButton());
3581 
3582                 target.fillHierarchy(pressedTargets);
3583                 pickedTarget.fillHierarchy(releasedTargets);
3584                 int i = pressedTargets.size() - 1;
3585                 int j = releasedTargets.size() - 1;
3586 
3587                 EventTarget clickedTarget = null;
3588                 while (i >= 0 && j >= 0 && pressedTargets.get(i) == releasedTargets.get(j)) {
3589                     clickedTarget = pressedTargets.get(i);
3590                     i--;
3591                     j--;
3592                 }
3593 
3594                 pressedTargets.clear();
3595                 releasedTargets.clear();
3596 
3597                 if (clickedTarget != null && lastPress != null) {
3598                     MouseEvent click = new MouseEvent(null, clickedTarget,
3599                             MouseEvent.MOUSE_CLICKED, e.getSceneX(), e.getSceneY(),
3600                             e.getScreenX(), e.getScreenY(), e.getButton(),
3601                             cc.get(),
3602                             e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(),
3603                             e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(),
3604                             e.isSynthesized(), e.isPopupTrigger(), lastPress.isStill(), e.getPickResult());
3605                     Event.fireEvent(clickedTarget, click);
3606                 }
3607             }
3608         }
3609     }
3610 
3611     /**
3612      * Generates mouse exited event for a node which is going to be removed
3613      * and its children, where appropriate.
3614      * @param removing Node which is going to be removed
3615      */
3616     void generateMouseExited(Node removing) {
3617         mouseHandler.handleNodeRemoval(removing);
3618     }
3619 
3620     class MouseHandler {
3621         private TargetWrapper pdrEventTarget = new TargetWrapper(); // pdr - press-drag-release
3622         private boolean pdrInProgress = false;
3623         private boolean fullPDREntered = false;
3624 
3625         private EventTarget currentEventTarget = null;
3626         private MouseEvent lastEvent;
3627         private boolean hover = false;
3628 
3629         private boolean primaryButtonDown = false;
3630         private boolean secondaryButtonDown = false;
3631         private boolean middleButtonDown = false;
3632 
3633         private EventTarget fullPDRSource = null;
3634         private TargetWrapper fullPDRTmpTargetWrapper = new TargetWrapper();
3635 
3636         /* lists needed for enter/exit events generation */
3637         private final List<EventTarget> pdrEventTargets = new ArrayList<EventTarget>();
3638         private final List<EventTarget> currentEventTargets = new ArrayList<EventTarget>();
3639         private final List<EventTarget> newEventTargets = new ArrayList<EventTarget>();
3640 
3641         private final List<EventTarget> fullPDRCurrentEventTargets = new ArrayList<EventTarget>();
3642         private final List<EventTarget> fullPDRNewEventTargets = new ArrayList<EventTarget>();
3643         private EventTarget fullPDRCurrentTarget = null;
3644 
3645         private Cursor currCursor;
3646         private CursorFrame currCursorFrame;
3647         private EventQueue queue = new EventQueue();
3648 
3649         private Runnable pickProcess = new Runnable() {
3650 
3651             @Override
3652             public void run() {
3653                 // Make sure this is run only if the peer is still alive
3654                 // and there is an event to deliver
3655                 if (Scene.this.peer != null && lastEvent != null) {
3656                     process(lastEvent, true);
3657                 }
3658             }
3659         };
3660 
3661         private void pulse() {
3662             if (hover && lastEvent != null) {
3663                 //Shouldn't run user code directly. User can call stage.showAndWait() and block the pulse.
3664                 Platform.runLater(pickProcess);
3665             }
3666         }
3667 
3668         private void clearPDREventTargets() {
3669             pdrInProgress = false;
3670             currentEventTarget = currentEventTargets.size() > 0
3671                     ? currentEventTargets.get(0) : null;
3672             pdrEventTarget.clear();
3673         }
3674 
3675         public void enterFullPDR(EventTarget gestureSource) {
3676             fullPDREntered = true;
3677             fullPDRSource = gestureSource;
3678             fullPDRCurrentTarget = null;
3679             fullPDRCurrentEventTargets.clear();
3680         }
3681 
3682         public void exitFullPDR(MouseEvent e) {
3683             if (!fullPDREntered) {
3684                 return;
3685             }
3686             fullPDREntered = false;
3687             for (int i = fullPDRCurrentEventTargets.size() - 1; i >= 0; i--) {
3688                 EventTarget entered = fullPDRCurrentEventTargets.get(i);
3689                 Event.fireEvent(entered, MouseEvent.copyForMouseDragEvent(e,
3690                         entered, entered,
3691                         MouseDragEvent.MOUSE_DRAG_EXITED_TARGET,
3692                         fullPDRSource, e.getPickResult()));
3693             }
3694             fullPDRSource = null;
3695             fullPDRCurrentEventTargets.clear();
3696             fullPDRCurrentTarget = null;
3697         }
3698 
3699         private void handleNodeRemoval(Node removing) {
3700             if (lastEvent == null) {
3701                 // this can happen only if everything has been exited anyway
3702                 return;
3703             }
3704 
3705 
3706             if (currentEventTargets.contains(removing)) {
3707                 int i = 0;
3708                 EventTarget trg = null;
3709                 while(trg != removing) {
3710                     trg = currentEventTargets.get(i++);
3711 
3712                     queue.postEvent(lastEvent.copyFor(trg, trg,
3713                             MouseEvent.MOUSE_EXITED_TARGET));
3714                 }
3715                 currentEventTargets.subList(0, i).clear();
3716             }
3717 
3718             if (fullPDREntered && fullPDRCurrentEventTargets.contains(removing)) {
3719                 int i = 0;
3720                 EventTarget trg = null;
3721                 while (trg != removing) {
3722                     trg = fullPDRCurrentEventTargets.get(i++);
3723 
3724                     queue.postEvent(
3725                             MouseEvent.copyForMouseDragEvent(lastEvent, trg, trg,
3726                             MouseDragEvent.MOUSE_DRAG_EXITED_TARGET,
3727                             fullPDRSource, lastEvent.getPickResult()));
3728                 }
3729 
3730                 fullPDRCurrentEventTargets.subList(0, i).clear();
3731             }
3732 
3733             queue.fire();
3734 
3735             if (pdrInProgress && pdrEventTargets.contains(removing)) {
3736                 int i = 0;
3737                 EventTarget trg = null;
3738                 while (trg != removing) {
3739                     trg = pdrEventTargets.get(i++);
3740 
3741                     // trg.setHover(false) - already taken care of
3742                     // by the code above which sent a mouse exited event
3743                     ((Node) trg).setPressed(false);
3744                 }
3745                 pdrEventTargets.subList(0, i).clear();
3746 
3747                 trg = pdrEventTargets.get(0);
3748                 final PickResult res = pdrEventTarget.getResult();
3749                 if (trg instanceof Node) {
3750                     pdrEventTarget.setNodeResult(new PickResult((Node) trg,
3751                             res.getIntersectedPoint(), res.getIntersectedDistance()));
3752                 } else {
3753                     pdrEventTarget.setSceneResult(new PickResult(null,
3754                             res.getIntersectedPoint(), res.getIntersectedDistance()),
3755                             (Scene) trg);
3756                 }
3757             }
3758         }
3759 
3760         private void handleEnterExit(MouseEvent e, TargetWrapper pickedTarget) {
3761             if (pickedTarget.getEventTarget() != currentEventTarget ||
3762                     e.getEventType() == MouseEvent.MOUSE_EXITED) {
3763 
3764                 if (e.getEventType() == MouseEvent.MOUSE_EXITED) {
3765                     newEventTargets.clear();
3766                 } else {
3767                     pickedTarget.fillHierarchy(newEventTargets);
3768                 }
3769 
3770                 int newTargetsSize = newEventTargets.size();
3771                 int i = currentEventTargets.size() - 1;
3772                 int j = newTargetsSize - 1;
3773                 int k = pdrEventTargets.size() - 1;
3774 
3775                 while (i >= 0 && j >= 0 && currentEventTargets.get(i) == newEventTargets.get(j)) {
3776                     i--;
3777                     j--;
3778                     k--;
3779                 }
3780 
3781                 final int memk = k;
3782                 for (; i >= 0; i--, k--) {
3783                     final EventTarget exitedEventTarget = currentEventTargets.get(i);
3784                     if (pdrInProgress &&
3785                             (k < 0 || exitedEventTarget != pdrEventTargets.get(k))) {
3786                          break;
3787                     }
3788                     queue.postEvent(e.copyFor(
3789                             exitedEventTarget, exitedEventTarget,
3790                             MouseEvent.MOUSE_EXITED_TARGET));
3791                 }
3792 
3793                 k = memk;
3794                 for (; j >= 0; j--, k--) {
3795                     final EventTarget enteredEventTarget = newEventTargets.get(j);
3796                     if (pdrInProgress &&
3797                             (k < 0 || enteredEventTarget != pdrEventTargets.get(k))) {
3798                         break;
3799                     }
3800                     queue.postEvent(e.copyFor(
3801                             enteredEventTarget, enteredEventTarget,
3802                             MouseEvent.MOUSE_ENTERED_TARGET));
3803                 }
3804 
3805                 currentEventTarget = pickedTarget.getEventTarget();
3806                 currentEventTargets.clear();
3807                 for (j++; j < newTargetsSize; j++) {
3808                     currentEventTargets.add(newEventTargets.get(j));
3809                 }
3810             }
3811             queue.fire();
3812         }
3813 
3814         private void process(MouseEvent e, boolean onPulse) {
3815             Toolkit.getToolkit().checkFxUserThread();
3816             Scene.inMousePick = true;
3817 
3818             cursorScreenPos = new Point2D(e.getScreenX(), e.getScreenY());
3819             cursorScenePos = new Point2D(e.getSceneX(), e.getSceneY());
3820 
3821             boolean gestureStarted = false;
3822             if (!onPulse) {
3823                 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) {
3824                     if (!(primaryButtonDown || secondaryButtonDown || middleButtonDown)) {
3825                         //old gesture ended and new one started
3826                         gestureStarted = true;
3827                         if (!PLATFORM_DRAG_GESTURE_INITIATION) {
3828                             Scene.this.dndGesture = new DnDGesture();
3829                         }
3830                         clearPDREventTargets();
3831                     }
3832                 } else if (e.getEventType() == MouseEvent.MOUSE_MOVED) {
3833                     // gesture ended
3834                     clearPDREventTargets();
3835                 } else if (e.getEventType() == MouseEvent.MOUSE_ENTERED) {
3836                     hover = true;
3837                 } else if (e.getEventType() == MouseEvent.MOUSE_EXITED) {
3838                     hover = false;
3839                 }
3840 
3841                 primaryButtonDown = e.isPrimaryButtonDown();
3842                 secondaryButtonDown = e.isSecondaryButtonDown();
3843                 middleButtonDown = e.isMiddleButtonDown();
3844             }
3845 
3846             pick(tmpTargetWrapper, e.getSceneX(), e.getSceneY());
3847             PickResult res = tmpTargetWrapper.getResult();
3848             if (res != null) {
3849                 e = new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(),
3850                     e.getScreenX(), e.getScreenY(), e.getButton(), e.getClickCount(),
3851                     e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(),
3852                     e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(),
3853                     e.isSynthesized(), e.isPopupTrigger(), e.isStillSincePress(), res);
3854             }
3855 
3856             if (e.getEventType() == MouseEvent.MOUSE_EXITED) {
3857                 tmpTargetWrapper.clear();
3858             }
3859 
3860             TargetWrapper target;
3861             if (pdrInProgress) {
3862                 target = pdrEventTarget;
3863             } else {
3864                 target = tmpTargetWrapper;
3865             }
3866 
3867             if (gestureStarted) {
3868                 pdrEventTarget.copy(target);
3869                 pdrEventTarget.fillHierarchy(pdrEventTargets);
3870             }
3871 
3872             if (!onPulse) {
3873                 e = clickGenerator.preProcess(e);
3874             }
3875 
3876             // enter/exit handling
3877             handleEnterExit(e, tmpTargetWrapper);
3878 
3879             //deliver event to the target node
3880             if (Scene.this.dndGesture != null) {
3881                 Scene.this.dndGesture.processDragDetection(e);
3882             }
3883 
3884             if (fullPDREntered && e.getEventType() == MouseEvent.MOUSE_RELEASED) {
3885                 processFullPDR(e, onPulse);
3886             }
3887 
3888             if (target.getEventTarget() != null) {
3889                 if (e.getEventType() != MouseEvent.MOUSE_ENTERED
3890                         && e.getEventType() != MouseEvent.MOUSE_EXITED
3891                         && !onPulse) {
3892                     Event.fireEvent(target.getEventTarget(), e);
3893                 }
3894             }
3895 
3896             if (fullPDREntered && e.getEventType() != MouseEvent.MOUSE_RELEASED) {
3897                 processFullPDR(e, onPulse);
3898             }
3899 
3900             if (!onPulse) {
3901                 clickGenerator.postProcess(e, target, tmpTargetWrapper);
3902             }
3903 
3904             // handle drag and drop
3905 
3906             if (!PLATFORM_DRAG_GESTURE_INITIATION && !onPulse) {
3907                 if (Scene.this.dndGesture != null) {
3908                     if (!Scene.this.dndGesture.process(e, target.getEventTarget())) {
3909                         dndGesture = null;
3910                     }
3911                 }
3912             }
3913 
3914             Cursor cursor = target.getCursor();
3915             if (e.getEventType() != MouseEvent.MOUSE_EXITED) {
3916                 if (cursor == null && hover) {
3917                     cursor = Scene.this.getCursor();
3918                 }
3919 
3920                 updateCursor(cursor);
3921                 updateCursorFrame();
3922             }
3923 
3924             if (gestureStarted) {
3925                 pdrInProgress = true;
3926             }
3927 
3928             if (pdrInProgress &&
3929                     !(primaryButtonDown || secondaryButtonDown || middleButtonDown)) {
3930                 clearPDREventTargets();
3931                 exitFullPDR(e);
3932                 // we need to do new picking in case the originally picked node
3933                 // was moved or removed by the event handlers
3934                 pick(tmpTargetWrapper, e.getSceneX(), e.getSceneY());
3935                 handleEnterExit(e, tmpTargetWrapper);
3936             }
3937 
3938             lastEvent = e.getEventType() == MouseEvent.MOUSE_EXITED ? null : e;
3939             Scene.inMousePick = false;
3940         }
3941 
3942         private void processFullPDR(MouseEvent e, boolean onPulse) {
3943 
3944             pick(fullPDRTmpTargetWrapper, e.getSceneX(), e.getSceneY());
3945             final PickResult result = fullPDRTmpTargetWrapper.getResult();
3946 
3947             final EventTarget eventTarget = fullPDRTmpTargetWrapper.getEventTarget();
3948 
3949             // enter/exit handling
3950             if (eventTarget != fullPDRCurrentTarget) {
3951 
3952                 fullPDRTmpTargetWrapper.fillHierarchy(fullPDRNewEventTargets);
3953 
3954                 int newTargetsSize = fullPDRNewEventTargets.size();
3955                 int i = fullPDRCurrentEventTargets.size() - 1;
3956                 int j = newTargetsSize - 1;
3957 
3958                 while (i >= 0 && j >= 0 &&
3959                         fullPDRCurrentEventTargets.get(i) == fullPDRNewEventTargets.get(j)) {
3960                     i--;
3961                     j--;
3962                 }
3963 
3964                 for (; i >= 0; i--) {
3965                     final EventTarget exitedEventTarget = fullPDRCurrentEventTargets.get(i);
3966                     Event.fireEvent(exitedEventTarget, MouseEvent.copyForMouseDragEvent(e,
3967                             exitedEventTarget, exitedEventTarget,
3968                             MouseDragEvent.MOUSE_DRAG_EXITED_TARGET,
3969                             fullPDRSource, result));
3970                 }
3971 
3972                 for (; j >= 0; j--) {
3973                     final EventTarget enteredEventTarget = fullPDRNewEventTargets.get(j);
3974                     Event.fireEvent(enteredEventTarget, MouseEvent.copyForMouseDragEvent(e,
3975                             enteredEventTarget, enteredEventTarget,
3976                             MouseDragEvent.MOUSE_DRAG_ENTERED_TARGET,
3977                             fullPDRSource, result));
3978                 }
3979 
3980                 fullPDRCurrentTarget = eventTarget;
3981                 fullPDRCurrentEventTargets.clear();
3982                 fullPDRCurrentEventTargets.addAll(fullPDRNewEventTargets);
3983                 fullPDRNewEventTargets.clear();
3984             }
3985             // done enter/exit handling
3986 
3987             // event delivery
3988             if (eventTarget != null && !onPulse) {
3989                 if (e.getEventType() == MouseEvent.MOUSE_DRAGGED) {
3990                     Event.fireEvent(eventTarget, MouseEvent.copyForMouseDragEvent(e,
3991                             eventTarget, eventTarget,
3992                             MouseDragEvent.MOUSE_DRAG_OVER,
3993                             fullPDRSource, result));
3994                 }
3995                 if (e.getEventType() == MouseEvent.MOUSE_RELEASED) {
3996                     Event.fireEvent(eventTarget, MouseEvent.copyForMouseDragEvent(e,
3997                             eventTarget, eventTarget,
3998                             MouseDragEvent.MOUSE_DRAG_RELEASED,
3999                             fullPDRSource, result));
4000                 }
4001             }
4002         }
4003 
4004         private void updateCursor(Cursor newCursor) {
4005             if (currCursor != newCursor) {
4006                 if (currCursor != null) {
4007                     currCursor.deactivate();
4008                 }
4009 
4010                 if (newCursor != null) {
4011                     newCursor.activate();
4012                 }
4013 
4014                 currCursor = newCursor;
4015             }
4016         }
4017 
4018         public void updateCursorFrame() {
4019             final CursorFrame newCursorFrame =
4020                     (currCursor != null)
4021                            ? currCursor.getCurrentFrame()
4022                            : Cursor.DEFAULT.getCurrentFrame();
4023             if (currCursorFrame != newCursorFrame) {
4024                 if (Scene.this.peer != null) {
4025                     Scene.this.peer.setCursor(newCursorFrame);
4026                 }
4027 
4028                 currCursorFrame = newCursorFrame;
4029             }
4030         }
4031 
4032         private PickResult pickNode(PickRay pickRay) {
4033             PickResultChooser r = new PickResultChooser();
4034             Scene.this.getRoot().impl_pickNode(pickRay, r);
4035             return r.toPickResult();
4036         }
4037     }
4038 
4039     /*******************************************************************************
4040      *                                                                             *
4041      * Key Event Handling                                                          *
4042      *                                                                             *
4043      ******************************************************************************/
4044 
4045     class KeyHandler {
4046         private void setFocusOwner(final Node value) {
4047             // Cancel IM composition if there is one in progress.
4048             // This needs to be done before the focus owner is switched as it
4049             // generates event that needs to be delivered to the old focus owner.
4050             if (oldFocusOwner != null) {
4051                 final Scene s = oldFocusOwner.getScene();
4052                 if (s != null) {
4053                     final TKScene peer = s.impl_getPeer();
4054                     if (peer != null) {
4055                         peer.finishInputMethodComposition();
4056                     }
4057                 }
4058             }
4059             focusOwner.set(value);
4060         }
4061 
4062         private boolean windowFocused;
4063         protected boolean isWindowFocused() { return windowFocused; }
4064         protected void setWindowFocused(boolean value) {
4065             windowFocused = value;
4066             if (getFocusOwner() != null) {
4067                 getFocusOwner().setFocused(windowFocused);
4068             }
4069             if (windowFocused) {
4070                 if (accessible != null) {
4071                     accessible.sendNotification(AccessibleAttribute.FOCUS_NODE);
4072                 }
4073             }
4074         }
4075 
4076         private void windowForSceneChanged(Window oldWindow, Window window) {
4077             if (oldWindow != null) {
4078                 oldWindow.focusedProperty().removeListener(sceneWindowFocusedListener);
4079             }
4080 
4081             if (window != null) {
4082                 window.focusedProperty().addListener(sceneWindowFocusedListener);
4083                 setWindowFocused(window.isFocused());
4084             } else {
4085                 setWindowFocused(false);
4086             }
4087         }
4088 
4089         private final InvalidationListener sceneWindowFocusedListener = valueModel -> setWindowFocused(((ReadOnlyBooleanProperty)valueModel).get());
4090 
4091         private void process(KeyEvent e) {
4092             final Node sceneFocusOwner = getFocusOwner();
4093             final EventTarget eventTarget =
4094                     (sceneFocusOwner != null && sceneFocusOwner.getScene() == Scene.this) ? sceneFocusOwner
4095                                               : Scene.this;
4096 
4097             // send the key event to the current focus owner or to scene if
4098             // the focus owner is not set
4099             Event.fireEvent(eventTarget, e);
4100         }
4101 
4102         private void requestFocus(Node node) {
4103             if (getFocusOwner() == node || (node != null && !node.isCanReceiveFocus())) {
4104                 return;
4105             }
4106             setFocusOwner(node);
4107         }
4108     }
4109     /***************************************************************************
4110      *                                                                         *
4111      *                         Event Dispatch                                  *
4112      *                                                                         *
4113      **************************************************************************/
4114     // PENDING_DOC_REVIEW
4115     /**
4116      * Specifies the event dispatcher for this scene. When replacing the value
4117      * with a new {@code EventDispatcher}, the new dispatcher should forward
4118      * events to the replaced dispatcher to keep the scene's default event
4119      * handling behavior.
4120      */
4121     private ObjectProperty<EventDispatcher> eventDispatcher;
4122 
4123     public final void setEventDispatcher(EventDispatcher value) {
4124         eventDispatcherProperty().set(value);
4125     }
4126 
4127     public final EventDispatcher getEventDispatcher() {
4128         return eventDispatcherProperty().get();
4129     }
4130 
4131     public final ObjectProperty<EventDispatcher>
4132             eventDispatcherProperty() {
4133         initializeInternalEventDispatcher();
4134         return eventDispatcher;
4135     }
4136 
4137     private SceneEventDispatcher internalEventDispatcher;
4138 
4139     // Delegates requests from platform input method to the focused
4140     // node's one, if any.
4141     class InputMethodRequestsDelegate implements ExtendedInputMethodRequests {
4142         @Override
4143         public Point2D getTextLocation(int offset) {
4144             InputMethodRequests requests = getClientRequests();
4145             if (requests != null) {
4146                 return requests.getTextLocation(offset);
4147             } else {
4148                 return new Point2D(0, 0);
4149             }
4150         }
4151 
4152         @Override
4153         public int getLocationOffset(int x, int y) {
4154             InputMethodRequests requests = getClientRequests();
4155             if (requests != null) {
4156                 return requests.getLocationOffset(x, y);
4157             } else {
4158                 return 0;
4159             }
4160         }
4161 
4162         @Override
4163         public void cancelLatestCommittedText() {
4164             InputMethodRequests requests = getClientRequests();
4165             if (requests != null) {
4166                 requests.cancelLatestCommittedText();
4167             }
4168         }
4169 
4170         @Override
4171         public String getSelectedText() {
4172             InputMethodRequests requests = getClientRequests();
4173             if (requests != null) {
4174                 return requests.getSelectedText();
4175             }
4176             return null;
4177         }
4178 
4179         @Override
4180         public int getInsertPositionOffset() {
4181             InputMethodRequests requests = getClientRequests();
4182             if (requests != null && requests instanceof ExtendedInputMethodRequests) {
4183                 return ((ExtendedInputMethodRequests)requests).getInsertPositionOffset();
4184             }
4185             return 0;
4186         }
4187 
4188         @Override
4189         public String getCommittedText(int begin, int end) {
4190             InputMethodRequests requests = getClientRequests();
4191             if (requests != null && requests instanceof ExtendedInputMethodRequests) {
4192                 return ((ExtendedInputMethodRequests)requests).getCommittedText(begin, end);
4193             }
4194             return null;
4195         }
4196 
4197         @Override
4198         public int getCommittedTextLength() {
4199             InputMethodRequests requests = getClientRequests();
4200             if (requests != null && requests instanceof ExtendedInputMethodRequests) {
4201                 return ((ExtendedInputMethodRequests)requests).getCommittedTextLength();
4202             }
4203             return 0;
4204         }
4205 
4206         private InputMethodRequests getClientRequests() {
4207             Node focusOwner = getFocusOwner();
4208             if (focusOwner != null) {
4209                 return focusOwner.getInputMethodRequests();
4210             }
4211             return null;
4212         }
4213     }
4214 
4215     // PENDING_DOC_REVIEW
4216     /**
4217      * Registers an event handler to this scene. The handler is called when the
4218      * scene receives an {@code Event} of the specified type during the bubbling
4219      * phase of event delivery.
4220      *
4221      * @param <T> the specific event class of the handler
4222      * @param eventType the type of the events to receive by the handler
4223      * @param eventHandler the handler to register
4224      * @throws NullPointerException if the event type or handler is null
4225      */
4226     public final <T extends Event> void addEventHandler(
4227             final EventType<T> eventType,
4228             final EventHandler<? super T> eventHandler) {
4229         getInternalEventDispatcher().getEventHandlerManager()
4230                                     .addEventHandler(eventType, eventHandler);
4231     }
4232 
4233     // PENDING_DOC_REVIEW
4234     /**
4235      * Unregisters a previously registered event handler from this scene. One
4236      * handler might have been registered for different event types, so the
4237      * caller needs to specify the particular event type from which to
4238      * unregister the handler.
4239      *
4240      * @param <T> the specific event class of the handler
4241      * @param eventType the event type from which to unregister
4242      * @param eventHandler the handler to unregister
4243      * @throws NullPointerException if the event type or handler is null
4244      */
4245     public final <T extends Event> void removeEventHandler(
4246             final EventType<T> eventType,
4247             final EventHandler<? super T> eventHandler) {
4248         getInternalEventDispatcher().getEventHandlerManager()
4249                                     .removeEventHandler(eventType,
4250                                                         eventHandler);
4251     }
4252 
4253     // PENDING_DOC_REVIEW
4254     /**
4255      * Registers an event filter to this scene. The filter is called when the
4256      * scene receives an {@code Event} of the specified type during the
4257      * capturing phase of event delivery.
4258      *
4259      * @param <T> the specific event class of the filter
4260      * @param eventType the type of the events to receive by the filter
4261      * @param eventFilter the filter to register
4262      * @throws NullPointerException if the event type or filter is null
4263      */
4264     public final <T extends Event> void addEventFilter(
4265             final EventType<T> eventType,
4266             final EventHandler<? super T> eventFilter) {
4267         getInternalEventDispatcher().getEventHandlerManager()
4268                                     .addEventFilter(eventType, eventFilter);
4269     }
4270 
4271     // PENDING_DOC_REVIEW
4272     /**
4273      * Unregisters a previously registered event filter from this scene. One
4274      * filter might have been registered for different event types, so the
4275      * caller needs to specify the particular event type from which to
4276      * unregister the filter.
4277      *
4278      * @param <T> the specific event class of the filter
4279      * @param eventType the event type from which to unregister
4280      * @param eventFilter the filter to unregister
4281      * @throws NullPointerException if the event type or filter is null
4282      */
4283     public final <T extends Event> void removeEventFilter(
4284             final EventType<T> eventType,
4285             final EventHandler<? super T> eventFilter) {
4286         getInternalEventDispatcher().getEventHandlerManager()
4287                                     .removeEventFilter(eventType, eventFilter);
4288     }
4289 
4290     /**
4291      * Sets the handler to use for this event type. There can only be one such
4292      * handler specified at a time. This handler is guaranteed to be called
4293      * first. This is used for registering the user-defined onFoo event
4294      * handlers.
4295      *
4296      * @param <T> the specific event class of the handler
4297      * @param eventType the event type to associate with the given eventHandler
4298      * @param eventHandler the handler to register, or null to unregister
4299      * @throws NullPointerException if the event type is null
4300      */
4301     protected final <T extends Event> void setEventHandler(
4302             final EventType<T> eventType,
4303             final EventHandler<? super T> eventHandler) {
4304         getInternalEventDispatcher().getEventHandlerManager()
4305                                     .setEventHandler(eventType, eventHandler);
4306     }
4307 
4308     private SceneEventDispatcher getInternalEventDispatcher() {
4309         initializeInternalEventDispatcher();
4310         return internalEventDispatcher;
4311     }
4312 
4313     final void initializeInternalEventDispatcher() {
4314         if (internalEventDispatcher == null) {
4315             internalEventDispatcher = createInternalEventDispatcher();
4316             eventDispatcher = new SimpleObjectProperty<EventDispatcher>(
4317                                           this,
4318                                           "eventDispatcher",
4319                                           internalEventDispatcher);
4320         }
4321     }
4322 
4323     private SceneEventDispatcher createInternalEventDispatcher() {
4324         return new SceneEventDispatcher(this);
4325     }
4326 
4327     /**
4328      * Registers the specified mnemonic.
4329      *
4330      * @param m The mnemonic
4331      */
4332     public void addMnemonic(Mnemonic m) {
4333         getInternalEventDispatcher().getKeyboardShortcutsHandler()
4334                                     .addMnemonic(m);
4335     }
4336 
4337 
4338     /**
4339      * Unregisters the specified mnemonic.
4340      *
4341      * @param m The mnemonic
4342      */
4343     public void removeMnemonic(Mnemonic m) {
4344         getInternalEventDispatcher().getKeyboardShortcutsHandler()
4345                                     .removeMnemonic(m);
4346     }
4347 
4348     final void clearNodeMnemonics(Node node) {
4349         getInternalEventDispatcher().getKeyboardShortcutsHandler()
4350                 .clearNodeMnemonics(node);
4351     }
4352 
4353 
4354     /**
4355      * Gets the list of mnemonics for this {@code Scene}.
4356      *
4357      * @return the list of mnemonics
4358      */
4359     public ObservableMap<KeyCombination, ObservableList<Mnemonic>> getMnemonics() {
4360         return getInternalEventDispatcher().getKeyboardShortcutsHandler()
4361                                            .getMnemonics();
4362     }
4363 
4364     /**
4365      * Gets the list of accelerators for this {@code Scene}.
4366      *
4367      * @return the list of accelerators
4368      */
4369     public ObservableMap<KeyCombination, Runnable> getAccelerators() {
4370         return getInternalEventDispatcher().getKeyboardShortcutsHandler()
4371                                            .getAccelerators();
4372     }
4373 
4374     // PENDING_DOC_REVIEW
4375     /**
4376      * Construct an event dispatch chain for this scene. The event dispatch
4377      * chain contains all event dispatchers from the stage to this scene.
4378      *
4379      * @param tail the initial chain to build from
4380      * @return the resulting event dispatch chain for this scene
4381      */
4382     @Override
4383     public EventDispatchChain buildEventDispatchChain(
4384             EventDispatchChain tail) {
4385         if (eventDispatcher != null) {
4386             final EventDispatcher eventDispatcherValue = eventDispatcher.get();
4387             if (eventDispatcherValue != null) {
4388                 tail = tail.prepend(eventDispatcherValue);
4389             }
4390         }
4391 
4392         if (getWindow() != null) {
4393             tail = getWindow().buildEventDispatchChain(tail);
4394         }
4395 
4396         return tail;
4397     }
4398 
4399     /***************************************************************************
4400      *                                                                         *
4401      *                             Context Menus                               *
4402      *                                                                         *
4403      **************************************************************************/
4404 
4405     /**
4406      * Defines a function to be called when a mouse button has been clicked
4407      * (pressed and released) on this {@code Scene}.
4408      * @since JavaFX 2.1
4409      */
4410 
4411     private ObjectProperty<EventHandler<? super ContextMenuEvent>> onContextMenuRequested;
4412 
4413     public final void setOnContextMenuRequested(EventHandler<? super ContextMenuEvent> value) {
4414         onContextMenuRequestedProperty().set(value);
4415     }
4416 
4417     public final EventHandler<? super ContextMenuEvent> getOnContextMenuRequested() {
4418         return onContextMenuRequested == null ? null : onContextMenuRequested.get();
4419     }
4420 
4421     public final ObjectProperty<EventHandler<? super ContextMenuEvent>> onContextMenuRequestedProperty() {
4422         if (onContextMenuRequested == null) {
4423             onContextMenuRequested = new ObjectPropertyBase<EventHandler<? super ContextMenuEvent>>() {
4424 
4425                 @Override
4426                 protected void invalidated() {
4427                     setEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, get());
4428                 }
4429 
4430                 @Override
4431                 public Object getBean() {
4432                     return Scene.this;
4433                 }
4434 
4435                 @Override
4436                 public String getName() {
4437                     return "onContextMenuRequested";
4438                 }
4439             };
4440         }
4441         return onContextMenuRequested;
4442     }
4443 
4444     /***************************************************************************
4445      *                                                                         *
4446      *                             Mouse Handling                              *
4447      *                                                                         *
4448      **************************************************************************/
4449 
4450     /**
4451      * Defines a function to be called when a mouse button has been clicked
4452      * (pressed and released) on this {@code Scene}.
4453      */
4454     private ObjectProperty<EventHandler<? super MouseEvent>> onMouseClicked;
4455 
4456     public final void setOnMouseClicked(EventHandler<? super MouseEvent> value) {
4457         onMouseClickedProperty().set(value);
4458     }
4459 
4460     public final EventHandler<? super MouseEvent> getOnMouseClicked() {
4461         return onMouseClicked == null ? null : onMouseClicked.get();
4462     }
4463 
4464     public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseClickedProperty() {
4465         if (onMouseClicked == null) {
4466             onMouseClicked = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4467 
4468                 @Override
4469                 protected void invalidated() {
4470                     setEventHandler(MouseEvent.MOUSE_CLICKED, get());
4471                 }
4472 
4473                 @Override
4474                 public Object getBean() {
4475                     return Scene.this;
4476                 }
4477 
4478                 @Override
4479                 public String getName() {
4480                     return "onMouseClicked";
4481                 }
4482             };
4483         }
4484         return onMouseClicked;
4485     }
4486 
4487     /**
4488      * Defines a function to be called when a mouse button is pressed
4489      * on this {@code Scene} and then dragged.
4490      */
4491     private ObjectProperty<EventHandler<? super MouseEvent>> onMouseDragged;
4492 
4493     public final void setOnMouseDragged(EventHandler<? super MouseEvent> value) {
4494         onMouseDraggedProperty().set(value);
4495     }
4496 
4497     public final EventHandler<? super MouseEvent> getOnMouseDragged() {
4498         return onMouseDragged == null ? null : onMouseDragged.get();
4499     }
4500 
4501     public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseDraggedProperty() {
4502         if (onMouseDragged == null) {
4503             onMouseDragged = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4504 
4505                 @Override
4506                 protected void invalidated() {
4507                     setEventHandler(MouseEvent.MOUSE_DRAGGED, get());
4508                 }
4509 
4510                 @Override
4511                 public Object getBean() {
4512                     return Scene.this;
4513                 }
4514 
4515                 @Override
4516                 public String getName() {
4517                     return "onMouseDragged";
4518                 }
4519             };
4520         }
4521         return onMouseDragged;
4522     }
4523 
4524     /**
4525      * Defines a function to be called when the mouse enters this {@code Scene}.
4526      */
4527     private ObjectProperty<EventHandler<? super MouseEvent>> onMouseEntered;
4528 
4529     public final void setOnMouseEntered(EventHandler<? super MouseEvent> value) {
4530         onMouseEnteredProperty().set(value);
4531     }
4532 
4533     public final EventHandler<? super MouseEvent> getOnMouseEntered() {
4534         return onMouseEntered == null ? null : onMouseEntered.get();
4535     }
4536 
4537     public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseEnteredProperty() {
4538         if (onMouseEntered == null) {
4539             onMouseEntered = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4540 
4541                 @Override
4542                 protected void invalidated() {
4543                     setEventHandler(MouseEvent.MOUSE_ENTERED, get());
4544                 }
4545 
4546                 @Override
4547                 public Object getBean() {
4548                     return Scene.this;
4549                 }
4550 
4551                 @Override
4552                 public String getName() {
4553                     return "onMouseEntered";
4554                 }
4555             };
4556         }
4557         return onMouseEntered;
4558     }
4559 
4560     /**
4561      * Defines a function to be called when the mouse exits this {@code Scene}.
4562      */
4563     private ObjectProperty<EventHandler<? super MouseEvent>> onMouseExited;
4564 
4565     public final void setOnMouseExited(EventHandler<? super MouseEvent> value) {
4566         onMouseExitedProperty().set(value);
4567     }
4568 
4569     public final EventHandler<? super MouseEvent> getOnMouseExited() {
4570         return onMouseExited == null ? null : onMouseExited.get();
4571     }
4572 
4573     public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseExitedProperty() {
4574         if (onMouseExited == null) {
4575             onMouseExited = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4576 
4577                 @Override
4578                 protected void invalidated() {
4579                     setEventHandler(MouseEvent.MOUSE_EXITED, get());
4580                 }
4581 
4582                 @Override
4583                 public Object getBean() {
4584                     return Scene.this;
4585                 }
4586 
4587                 @Override
4588                 public String getName() {
4589                     return "onMouseExited";
4590                 }
4591             };
4592         }
4593         return onMouseExited;
4594     }
4595 
4596     /**
4597      * Defines a function to be called when mouse cursor moves within
4598      * this {@code Scene} but no buttons have been pushed.
4599      */
4600     private ObjectProperty<EventHandler<? super MouseEvent>> onMouseMoved;
4601 
4602     public final void setOnMouseMoved(EventHandler<? super MouseEvent> value) {
4603         onMouseMovedProperty().set(value);
4604     }
4605 
4606     public final EventHandler<? super MouseEvent> getOnMouseMoved() {
4607         return onMouseMoved == null ? null : onMouseMoved.get();
4608     }
4609 
4610     public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseMovedProperty() {
4611         if (onMouseMoved == null) {
4612             onMouseMoved = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4613 
4614                 @Override
4615                 protected void invalidated() {
4616                     setEventHandler(MouseEvent.MOUSE_MOVED, get());
4617                 }
4618 
4619                 @Override
4620                 public Object getBean() {
4621                     return Scene.this;
4622                 }
4623 
4624                 @Override
4625                 public String getName() {
4626                     return "onMouseMoved";
4627                 }
4628             };
4629         }
4630         return onMouseMoved;
4631     }
4632 
4633     /**
4634      * Defines a function to be called when a mouse button
4635      * has been pressed on this {@code Scene}.
4636      */
4637     private ObjectProperty<EventHandler<? super MouseEvent>> onMousePressed;
4638 
4639     public final void setOnMousePressed(EventHandler<? super MouseEvent> value) {
4640         onMousePressedProperty().set(value);
4641     }
4642 
4643     public final EventHandler<? super MouseEvent> getOnMousePressed() {
4644         return onMousePressed == null ? null : onMousePressed.get();
4645     }
4646 
4647     public final ObjectProperty<EventHandler<? super MouseEvent>> onMousePressedProperty() {
4648         if (onMousePressed == null) {
4649             onMousePressed = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4650 
4651                 @Override
4652                 protected void invalidated() {
4653                     setEventHandler(MouseEvent.MOUSE_PRESSED, get());
4654                 }
4655 
4656                 @Override
4657                 public Object getBean() {
4658                     return Scene.this;
4659                 }
4660 
4661                 @Override
4662                 public String getName() {
4663                     return "onMousePressed";
4664                 }
4665             };
4666         }
4667         return onMousePressed;
4668     }
4669 
4670     /**
4671      * Defines a function to be called when a mouse button
4672      * has been released on this {@code Scene}.
4673      */
4674     private ObjectProperty<EventHandler<? super MouseEvent>> onMouseReleased;
4675 
4676     public final void setOnMouseReleased(EventHandler<? super MouseEvent> value) {
4677         onMouseReleasedProperty().set(value);
4678     }
4679 
4680     public final EventHandler<? super MouseEvent> getOnMouseReleased() {
4681         return onMouseReleased == null ? null : onMouseReleased.get();
4682     }
4683 
4684     public final ObjectProperty<EventHandler<? super MouseEvent>> onMouseReleasedProperty() {
4685         if (onMouseReleased == null) {
4686             onMouseReleased = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4687 
4688                 @Override
4689                 protected void invalidated() {
4690                     setEventHandler(MouseEvent.MOUSE_RELEASED, get());
4691                 }
4692 
4693                 @Override
4694                 public Object getBean() {
4695                     return Scene.this;
4696                 }
4697 
4698                 @Override
4699                 public String getName() {
4700                     return "onMouseReleased";
4701                 }
4702             };
4703         }
4704         return onMouseReleased;
4705     }
4706 
4707     /**
4708      * Defines a function to be called when drag gesture has been
4709      * detected. This is the right place to start drag and drop operation.
4710      */
4711     private ObjectProperty<EventHandler<? super MouseEvent>> onDragDetected;
4712 
4713     public final void setOnDragDetected(EventHandler<? super MouseEvent> value) {
4714         onDragDetectedProperty().set(value);
4715     }
4716 
4717     public final EventHandler<? super MouseEvent> getOnDragDetected() {
4718         return onDragDetected == null ? null : onDragDetected.get();
4719     }
4720 
4721     public final ObjectProperty<EventHandler<? super MouseEvent>> onDragDetectedProperty() {
4722         if (onDragDetected == null) {
4723             onDragDetected = new ObjectPropertyBase<EventHandler<? super MouseEvent>>() {
4724 
4725                 @Override
4726                 protected void invalidated() {
4727                     setEventHandler(MouseEvent.DRAG_DETECTED, get());
4728                 }
4729 
4730                 @Override
4731                 public Object getBean() {
4732                     return Scene.this;
4733                 }
4734 
4735                 @Override
4736                 public String getName() {
4737                     return "onDragDetected";
4738                 }
4739             };
4740         }
4741         return onDragDetected;
4742     }
4743 
4744     /**
4745      * Defines a function to be called when a full press-drag-release gesture
4746      * progresses within this {@code Scene}.
4747      * @since JavaFX 2.1
4748      */
4749     private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragOver;
4750 
4751     public final void setOnMouseDragOver(EventHandler<? super MouseDragEvent> value) {
4752         onMouseDragOverProperty().set(value);
4753     }
4754 
4755     public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
4756         return onMouseDragOver == null ? null : onMouseDragOver.get();
4757     }
4758 
4759     public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragOverProperty() {
4760         if (onMouseDragOver == null) {
4761             onMouseDragOver = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() {
4762 
4763                 @Override
4764                 protected void invalidated() {
4765                     setEventHandler(MouseDragEvent.MOUSE_DRAG_OVER, get());
4766                 }
4767 
4768                 @Override
4769                 public Object getBean() {
4770                     return Scene.this;
4771                 }
4772 
4773                 @Override
4774                 public String getName() {
4775                     return "onMouseDragOver";
4776                 }
4777             };
4778         }
4779         return onMouseDragOver;
4780     }
4781 
4782     /**
4783      * Defines a function to be called when a full press-drag-release gesture
4784      * ends within this {@code Scene}.
4785      * @since JavaFX 2.1
4786      */
4787     private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragReleased;
4788 
4789     public final void setOnMouseDragReleased(EventHandler<? super MouseDragEvent> value) {
4790         onMouseDragReleasedProperty().set(value);
4791     }
4792 
4793     public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() {
4794         return onMouseDragReleased == null ? null : onMouseDragReleased.get();
4795     }
4796 
4797     public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragReleasedProperty() {
4798         if (onMouseDragReleased == null) {
4799             onMouseDragReleased = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() {
4800 
4801                 @Override
4802                 protected void invalidated() {
4803                     setEventHandler(MouseDragEvent.MOUSE_DRAG_RELEASED, get());
4804                 }
4805 
4806                 @Override
4807                 public Object getBean() {
4808                     return Scene.this;
4809                 }
4810 
4811                 @Override
4812                 public String getName() {
4813                     return "onMouseDragReleased";
4814                 }
4815             };
4816         }
4817         return onMouseDragReleased;
4818     }
4819 
4820     /**
4821      * Defines a function to be called when a full press-drag-release gesture
4822      * enters this {@code Scene}.
4823      * @since JavaFX 2.1
4824      */
4825     private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragEntered;
4826 
4827     public final void setOnMouseDragEntered(EventHandler<? super MouseDragEvent> value) {
4828         onMouseDragEnteredProperty().set(value);
4829     }
4830 
4831     public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() {
4832         return onMouseDragEntered == null ? null : onMouseDragEntered.get();
4833     }
4834 
4835     public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragEnteredProperty() {
4836         if (onMouseDragEntered == null) {
4837             onMouseDragEntered = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() {
4838 
4839                 @Override
4840                 protected void invalidated() {
4841                     setEventHandler(MouseDragEvent.MOUSE_DRAG_ENTERED, get());
4842                 }
4843 
4844                 @Override
4845                 public Object getBean() {
4846                     return Scene.this;
4847                 }
4848 
4849                 @Override
4850                 public String getName() {
4851                     return "onMouseDragEntered";
4852                 }
4853             };
4854         }
4855         return onMouseDragEntered;
4856     }
4857 
4858     /**
4859      * Defines a function to be called when a full press-drag-release gesture
4860      * exits this {@code Scene}.
4861      * @since JavaFX 2.1
4862      */
4863     private ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragExited;
4864 
4865     public final void setOnMouseDragExited(EventHandler<? super MouseDragEvent> value) {
4866         onMouseDragExitedProperty().set(value);
4867     }
4868 
4869     public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() {
4870         return onMouseDragExited == null ? null : onMouseDragExited.get();
4871     }
4872 
4873     public final ObjectProperty<EventHandler<? super MouseDragEvent>> onMouseDragExitedProperty() {
4874         if (onMouseDragExited == null) {
4875             onMouseDragExited = new ObjectPropertyBase<EventHandler<? super MouseDragEvent>>() {
4876 
4877                 @Override
4878                 protected void invalidated() {
4879                     setEventHandler(MouseDragEvent.MOUSE_DRAG_EXITED, get());
4880                 }
4881 
4882                 @Override
4883                 public Object getBean() {
4884                     return Scene.this;
4885                 }
4886 
4887                 @Override
4888                 public String getName() {
4889                     return "onMouseDragExited";
4890                 }
4891             };
4892         }
4893         return onMouseDragExited;
4894     }
4895 
4896 
4897     /***************************************************************************
4898      *                                                                         *
4899      *                           Gestures Handling                             *
4900      *                                                                         *
4901      **************************************************************************/
4902 
4903     /**
4904      * Defines a function to be called when a scrolling gesture is detected.
4905      * @since JavaFX 2.2
4906      */
4907     private ObjectProperty<EventHandler<? super ScrollEvent>> onScrollStarted;
4908 
4909     public final void setOnScrollStarted(EventHandler<? super ScrollEvent> value) {
4910         onScrollStartedProperty().set(value);
4911     }
4912 
4913     public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
4914         return onScrollStarted == null ? null : onScrollStarted.get();
4915     }
4916 
4917     public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollStartedProperty() {
4918         if (onScrollStarted == null) {
4919             onScrollStarted = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() {
4920 
4921                 @Override
4922                 protected void invalidated() {
4923                     setEventHandler(ScrollEvent.SCROLL_STARTED, get());
4924                 }
4925 
4926                 @Override
4927                 public Object getBean() {
4928                     return Scene.this;
4929                 }
4930 
4931                 @Override
4932                 public String getName() {
4933                     return "onScrollStarted";
4934                 }
4935             };
4936         }
4937         return onScrollStarted;
4938     }
4939 
4940     /**
4941      * Defines a function to be called when user performs a scrolling action.
4942      */
4943     private ObjectProperty<EventHandler<? super ScrollEvent>> onScroll;
4944 
4945     public final void setOnScroll(EventHandler<? super ScrollEvent> value) {
4946         onScrollProperty().set(value);
4947     }
4948 
4949     public final EventHandler<? super ScrollEvent> getOnScroll() {
4950         return onScroll == null ? null : onScroll.get();
4951     }
4952 
4953     public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollProperty() {
4954         if (onScroll == null) {
4955             onScroll = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() {
4956 
4957                 @Override
4958                 protected void invalidated() {
4959                     setEventHandler(ScrollEvent.SCROLL, get());
4960                 }
4961 
4962                 @Override
4963                 public Object getBean() {
4964                     return Scene.this;
4965                 }
4966 
4967                 @Override
4968                 public String getName() {
4969                     return "onScroll";
4970                 }
4971             };
4972         }
4973         return onScroll;
4974     }
4975 
4976     /**
4977      * Defines a function to be called when a scrolling gesture ends.
4978      * @since JavaFX 2.2
4979      */
4980     private ObjectProperty<EventHandler<? super ScrollEvent>> onScrollFinished;
4981 
4982     public final void setOnScrollFinished(EventHandler<? super ScrollEvent> value) {
4983         onScrollFinishedProperty().set(value);
4984     }
4985 
4986     public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
4987         return onScrollFinished == null ? null : onScrollFinished.get();
4988     }
4989 
4990     public final ObjectProperty<EventHandler<? super ScrollEvent>> onScrollFinishedProperty() {
4991         if (onScrollFinished == null) {
4992             onScrollFinished = new ObjectPropertyBase<EventHandler<? super ScrollEvent>>() {
4993 
4994                 @Override
4995                 protected void invalidated() {
4996                     setEventHandler(ScrollEvent.SCROLL_FINISHED, get());
4997                 }
4998 
4999                 @Override
5000                 public Object getBean() {
5001                     return Scene.this;
5002                 }
5003 
5004                 @Override
5005                 public String getName() {
5006                     return "onScrollFinished";
5007                 }
5008             };
5009         }
5010         return onScrollFinished;
5011     }
5012 
5013     /**
5014      * Defines a function to be called when a rotating gesture is detected.
5015      * @since JavaFX 2.2
5016      */
5017     private ObjectProperty<EventHandler<? super RotateEvent>> onRotationStarted;
5018 
5019     public final void setOnRotationStarted(EventHandler<? super RotateEvent> value) {
5020         onRotationStartedProperty().set(value);
5021     }
5022 
5023     public final EventHandler<? super RotateEvent> getOnRotationStarted() {
5024         return onRotationStarted == null ? null : onRotationStarted.get();
5025     }
5026 
5027     public final ObjectProperty<EventHandler<? super RotateEvent>> onRotationStartedProperty() {
5028         if (onRotationStarted == null) {
5029             onRotationStarted = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() {
5030 
5031                 @Override
5032                 protected void invalidated() {
5033                     setEventHandler(RotateEvent.ROTATION_STARTED, get());
5034                 }
5035 
5036                 @Override
5037                 public Object getBean() {
5038                     return Scene.this;
5039                 }
5040 
5041                 @Override
5042                 public String getName() {
5043                     return "onRotationStarted";
5044                 }
5045             };
5046         }
5047         return onRotationStarted;
5048     }
5049 
5050     /**
5051      * Defines a function to be called when user performs a rotating action.
5052      * @since JavaFX 2.2
5053      */
5054     private ObjectProperty<EventHandler<? super RotateEvent>> onRotate;
5055 
5056     public final void setOnRotate(EventHandler<? super RotateEvent> value) {
5057         onRotateProperty().set(value);
5058     }
5059 
5060     public final EventHandler<? super RotateEvent> getOnRotate() {
5061         return onRotate == null ? null : onRotate.get();
5062     }
5063 
5064     public final ObjectProperty<EventHandler<? super RotateEvent>> onRotateProperty() {
5065         if (onRotate == null) {
5066             onRotate = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() {
5067 
5068                 @Override
5069                 protected void invalidated() {
5070                     setEventHandler(RotateEvent.ROTATE, get());
5071                 }
5072 
5073                 @Override
5074                 public Object getBean() {
5075                     return Scene.this;
5076                 }
5077 
5078                 @Override
5079                 public String getName() {
5080                     return "onRotate";
5081                 }
5082             };
5083         }
5084         return onRotate;
5085     }
5086 
5087     /**
5088      * Defines a function to be called when a rotating gesture ends.
5089      * @since JavaFX 2.2
5090      */
5091     private ObjectProperty<EventHandler<? super RotateEvent>> onRotationFinished;
5092 
5093     public final void setOnRotationFinished(EventHandler<? super RotateEvent> value) {
5094         onRotationFinishedProperty().set(value);
5095     }
5096 
5097     public final EventHandler<? super RotateEvent> getOnRotationFinished() {
5098         return onRotationFinished == null ? null : onRotationFinished.get();
5099     }
5100 
5101     public final ObjectProperty<EventHandler<? super RotateEvent>> onRotationFinishedProperty() {
5102         if (onRotationFinished == null) {
5103             onRotationFinished = new ObjectPropertyBase<EventHandler<? super RotateEvent>>() {
5104 
5105                 @Override
5106                 protected void invalidated() {
5107                     setEventHandler(RotateEvent.ROTATION_FINISHED, get());
5108                 }
5109 
5110                 @Override
5111                 public Object getBean() {
5112                     return Scene.this;
5113                 }
5114 
5115                 @Override
5116                 public String getName() {
5117                     return "onRotationFinished";
5118                 }
5119             };
5120         }
5121         return onRotationFinished;
5122     }
5123 
5124     /**
5125      * Defines a function to be called when a zooming gesture is detected.
5126      * @since JavaFX 2.2
5127      */
5128     private ObjectProperty<EventHandler<? super ZoomEvent>> onZoomStarted;
5129 
5130     public final void setOnZoomStarted(EventHandler<? super ZoomEvent> value) {
5131         onZoomStartedProperty().set(value);
5132     }
5133 
5134     public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
5135         return onZoomStarted == null ? null : onZoomStarted.get();
5136     }
5137 
5138     public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomStartedProperty() {
5139         if (onZoomStarted == null) {
5140             onZoomStarted = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() {
5141 
5142                 @Override
5143                 protected void invalidated() {
5144                     setEventHandler(ZoomEvent.ZOOM_STARTED, get());
5145                 }
5146 
5147                 @Override
5148                 public Object getBean() {
5149                     return Scene.this;
5150                 }
5151 
5152                 @Override
5153                 public String getName() {
5154                     return "onZoomStarted";
5155                 }
5156             };
5157         }
5158         return onZoomStarted;
5159     }
5160 
5161     /**
5162      * Defines a function to be called when user performs a zooming action.
5163      * @since JavaFX 2.2
5164      */
5165     private ObjectProperty<EventHandler<? super ZoomEvent>> onZoom;
5166 
5167     public final void setOnZoom(EventHandler<? super ZoomEvent> value) {
5168         onZoomProperty().set(value);
5169     }
5170 
5171     public final EventHandler<? super ZoomEvent> getOnZoom() {
5172         return onZoom == null ? null : onZoom.get();
5173     }
5174 
5175     public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomProperty() {
5176         if (onZoom == null) {
5177             onZoom = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() {
5178 
5179                 @Override
5180                 protected void invalidated() {
5181                     setEventHandler(ZoomEvent.ZOOM, get());
5182                 }
5183 
5184                 @Override
5185                 public Object getBean() {
5186                     return Scene.this;
5187                 }
5188 
5189                 @Override
5190                 public String getName() {
5191                     return "onZoom";
5192                 }
5193             };
5194         }
5195         return onZoom;
5196     }
5197 
5198     /**
5199      * Defines a function to be called when a zooming gesture ends.
5200      * @since JavaFX 2.2
5201      */
5202     private ObjectProperty<EventHandler<? super ZoomEvent>> onZoomFinished;
5203 
5204     public final void setOnZoomFinished(EventHandler<? super ZoomEvent> value) {
5205         onZoomFinishedProperty().set(value);
5206     }
5207 
5208     public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
5209         return onZoomFinished == null ? null : onZoomFinished.get();
5210     }
5211 
5212     public final ObjectProperty<EventHandler<? super ZoomEvent>> onZoomFinishedProperty() {
5213         if (onZoomFinished == null) {
5214             onZoomFinished = new ObjectPropertyBase<EventHandler<? super ZoomEvent>>() {
5215 
5216                 @Override
5217                 protected void invalidated() {
5218                     setEventHandler(ZoomEvent.ZOOM_FINISHED, get());
5219                 }
5220 
5221                 @Override
5222                 public Object getBean() {
5223                     return Scene.this;
5224                 }
5225 
5226                 @Override
5227                 public String getName() {
5228                     return "onZoomFinished";
5229                 }
5230             };
5231         }
5232         return onZoomFinished;
5233     }
5234 
5235     /**
5236      * Defines a function to be called when an upward swipe gesture
5237      * happens in this scene.
5238      * @since JavaFX 2.2
5239      */
5240     private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeUp;
5241 
5242     public final void setOnSwipeUp(EventHandler<? super SwipeEvent> value) {
5243         onSwipeUpProperty().set(value);
5244     }
5245 
5246     public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
5247         return onSwipeUp == null ? null : onSwipeUp.get();
5248     }
5249 
5250     public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeUpProperty() {
5251         if (onSwipeUp == null) {
5252             onSwipeUp = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
5253 
5254                 @Override
5255                 protected void invalidated() {
5256                     setEventHandler(SwipeEvent.SWIPE_UP, get());
5257                 }
5258 
5259                 @Override
5260                 public Object getBean() {
5261                     return Scene.this;
5262                 }
5263 
5264                 @Override
5265                 public String getName() {
5266                     return "onSwipeUp";
5267                 }
5268             };
5269         }
5270         return onSwipeUp;
5271     }
5272 
5273     /**
5274      * Defines a function to be called when an downward swipe gesture
5275      * happens in this scene.
5276      * @since JavaFX 2.2
5277      */
5278     private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeDown;
5279 
5280     public final void setOnSwipeDown(EventHandler<? super SwipeEvent> value) {
5281         onSwipeDownProperty().set(value);
5282     }
5283 
5284     public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
5285         return onSwipeDown == null ? null : onSwipeDown.get();
5286     }
5287 
5288     public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeDownProperty() {
5289         if (onSwipeDown == null) {
5290             onSwipeDown = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
5291 
5292                 @Override
5293                 protected void invalidated() {
5294                     setEventHandler(SwipeEvent.SWIPE_DOWN, get());
5295                 }
5296 
5297                 @Override
5298                 public Object getBean() {
5299                     return Scene.this;
5300                 }
5301 
5302                 @Override
5303                 public String getName() {
5304                     return "onSwipeDown";
5305                 }
5306             };
5307         }
5308         return onSwipeDown;
5309     }
5310 
5311     /**
5312      * Defines a function to be called when an leftward swipe gesture
5313      * happens in this scene.
5314      * @since JavaFX 2.2
5315      */
5316     private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeLeft;
5317 
5318     public final void setOnSwipeLeft(EventHandler<? super SwipeEvent> value) {
5319         onSwipeLeftProperty().set(value);
5320     }
5321 
5322     public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
5323         return onSwipeLeft == null ? null : onSwipeLeft.get();
5324     }
5325 
5326     public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeLeftProperty() {
5327         if (onSwipeLeft == null) {
5328             onSwipeLeft = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
5329 
5330                 @Override
5331                 protected void invalidated() {
5332                     setEventHandler(SwipeEvent.SWIPE_LEFT, get());
5333                 }
5334 
5335                 @Override
5336                 public Object getBean() {
5337                     return Scene.this;
5338                 }
5339 
5340                 @Override
5341                 public String getName() {
5342                     return "onSwipeLeft";
5343                 }
5344             };
5345         }
5346         return onSwipeLeft;
5347     }
5348 
5349     /**
5350      * Defines a function to be called when an rightward swipe gesture
5351      * happens in this scene.
5352      * @since JavaFX 2.2
5353      */
5354     private ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeRight;
5355 
5356     public final void setOnSwipeRight(EventHandler<? super SwipeEvent> value) {
5357         onSwipeRightProperty().set(value);
5358     }
5359 
5360     public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
5361         return onSwipeRight == null ? null : onSwipeRight.get();
5362     }
5363 
5364     public final ObjectProperty<EventHandler<? super SwipeEvent>> onSwipeRightProperty() {
5365         if (onSwipeRight == null) {
5366             onSwipeRight = new ObjectPropertyBase<EventHandler<? super SwipeEvent>>() {
5367 
5368                 @Override
5369                 protected void invalidated() {
5370                     setEventHandler(SwipeEvent.SWIPE_RIGHT, get());
5371                 }
5372 
5373                 @Override
5374                 public Object getBean() {
5375                     return Scene.this;
5376                 }
5377 
5378                 @Override
5379                 public String getName() {
5380                     return "onSwipeRight";
5381                 }
5382             };
5383         }
5384         return onSwipeRight;
5385     }
5386 
5387     /***************************************************************************
5388      *                                                                         *
5389      *                            Touch Handling                               *
5390      *                                                                         *
5391      **************************************************************************/
5392 
5393     /**
5394      * Defines a function to be called when a new touch point is pressed.
5395      * @since JavaFX 2.2
5396      */
5397     private ObjectProperty<EventHandler<? super TouchEvent>> onTouchPressed;
5398 
5399     public final void setOnTouchPressed(EventHandler<? super TouchEvent> value) {
5400         onTouchPressedProperty().set(value);
5401     }
5402 
5403     public final EventHandler<? super TouchEvent> getOnTouchPressed() {
5404         return onTouchPressed == null ? null : onTouchPressed.get();
5405     }
5406 
5407     public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchPressedProperty() {
5408         if (onTouchPressed == null) {
5409             onTouchPressed = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
5410 
5411                 @Override
5412                 protected void invalidated() {
5413                     setEventHandler(TouchEvent.TOUCH_PRESSED, get());
5414                 }
5415 
5416                 @Override
5417                 public Object getBean() {
5418                     return Scene.this;
5419                 }
5420 
5421                 @Override
5422                 public String getName() {
5423                     return "onTouchPressed";
5424                 }
5425             };
5426         }
5427         return onTouchPressed;
5428     }
5429 
5430     /**
5431      * Defines a function to be called when a touch point is moved.
5432      * @since JavaFX 2.2
5433      */
5434     private ObjectProperty<EventHandler<? super TouchEvent>> onTouchMoved;
5435 
5436     public final void setOnTouchMoved(EventHandler<? super TouchEvent> value) {
5437         onTouchMovedProperty().set(value);
5438     }
5439 
5440     public final EventHandler<? super TouchEvent> getOnTouchMoved() {
5441         return onTouchMoved == null ? null : onTouchMoved.get();
5442     }
5443 
5444     public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchMovedProperty() {
5445         if (onTouchMoved == null) {
5446             onTouchMoved = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
5447 
5448                 @Override
5449                 protected void invalidated() {
5450                     setEventHandler(TouchEvent.TOUCH_MOVED, get());
5451                 }
5452 
5453                 @Override
5454                 public Object getBean() {
5455                     return Scene.this;
5456                 }
5457 
5458                 @Override
5459                 public String getName() {
5460                     return "onTouchMoved";
5461                 }
5462             };
5463         }
5464         return onTouchMoved;
5465     }
5466 
5467     /**
5468      * Defines a function to be called when a new touch point is pressed.
5469      * @since JavaFX 2.2
5470      */
5471     private ObjectProperty<EventHandler<? super TouchEvent>> onTouchReleased;
5472 
5473     public final void setOnTouchReleased(EventHandler<? super TouchEvent> value) {
5474         onTouchReleasedProperty().set(value);
5475     }
5476 
5477     public final EventHandler<? super TouchEvent> getOnTouchReleased() {
5478         return onTouchReleased == null ? null : onTouchReleased.get();
5479     }
5480 
5481     public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchReleasedProperty() {
5482         if (onTouchReleased == null) {
5483             onTouchReleased = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
5484 
5485                 @Override
5486                 protected void invalidated() {
5487                     setEventHandler(TouchEvent.TOUCH_RELEASED, get());
5488                 }
5489 
5490                 @Override
5491                 public Object getBean() {
5492                     return Scene.this;
5493                 }
5494 
5495                 @Override
5496                 public String getName() {
5497                     return "onTouchReleased";
5498                 }
5499             };
5500         }
5501         return onTouchReleased;
5502     }
5503 
5504     /**
5505      * Defines a function to be called when a touch point stays pressed and
5506      * still.
5507      * @since JavaFX 2.2
5508      */
5509     private ObjectProperty<EventHandler<? super TouchEvent>> onTouchStationary;
5510 
5511     public final void setOnTouchStationary(EventHandler<? super TouchEvent> value) {
5512         onTouchStationaryProperty().set(value);
5513     }
5514 
5515     public final EventHandler<? super TouchEvent> getOnTouchStationary() {
5516         return onTouchStationary == null ? null : onTouchStationary.get();
5517     }
5518 
5519     public final ObjectProperty<EventHandler<? super TouchEvent>> onTouchStationaryProperty() {
5520         if (onTouchStationary == null) {
5521             onTouchStationary = new ObjectPropertyBase<EventHandler<? super TouchEvent>>() {
5522 
5523                 @Override
5524                 protected void invalidated() {
5525                     setEventHandler(TouchEvent.TOUCH_STATIONARY, get());
5526                 }
5527 
5528                 @Override
5529                 public Object getBean() {
5530                     return Scene.this;
5531                 }
5532 
5533                 @Override
5534                 public String getName() {
5535                     return "onTouchStationary";
5536                 }
5537             };
5538         }
5539         return onTouchStationary;
5540     }
5541 
5542     /*
5543      * This class provides reordering and ID mapping of particular touch points.
5544      * Platform may report arbitrary touch point IDs and they may be reused
5545      * during one gesture. This class keeps track of it and provides
5546      * sequentially sorted IDs, unique in scope of a gesture.
5547      *
5548      * Some platforms report always small numbers, these take fast paths through
5549      * the algorithm, directly indexing an array. Bigger numbers take a slow
5550      * path using a hash map.
5551      *
5552      * The algorithm performance was measured and it doesn't impose
5553      * any significant slowdown on the event delivery.
5554      */
5555     private static class TouchMap {
5556         private static final int FAST_THRESHOLD = 10;
5557         int[] fastMap = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
5558         Map<Long, Integer> slowMap = new HashMap<Long, Integer>();
5559         List<Integer> order = new LinkedList<Integer>();
5560         List<Long> removed = new ArrayList<Long>(10);
5561         int counter = 0;
5562         int active = 0;
5563 
5564         public int add(long id) {
5565             counter++;
5566             active++;
5567             if (id < FAST_THRESHOLD) {
5568                 fastMap[(int) id] = counter;
5569             } else {
5570                 slowMap.put(id, counter);
5571             }
5572             order.add(counter);
5573             return counter;
5574         }
5575 
5576         public void remove(long id) {
5577             // book the removal - it needs to be done after all touch points
5578             // of an event are processed - see cleanup()
5579             removed.add(id);
5580         }
5581 
5582         public int get(long id) {
5583             if (id < FAST_THRESHOLD) {
5584                 int result = fastMap[(int) id];
5585                 if (result == 0) {
5586                     throw new RuntimeException("Platform reported wrong "
5587                             + "touch point ID");
5588                 }
5589                 return result;
5590             } else {
5591                 try {
5592                     return slowMap.get(id);
5593                 } catch (NullPointerException e) {
5594                     throw new RuntimeException("Platform reported wrong "
5595                             + "touch point ID");
5596                 }
5597             }
5598         }
5599 
5600         public int getOrder(int id) {
5601             return order.indexOf(id);
5602         }
5603 
5604         // returns true if gesture finished (no finger is touched)
5605         public boolean cleanup() {
5606             for (long id : removed) {
5607                 active--;
5608                 order.remove(Integer.valueOf(get(id)));
5609                 if (id < FAST_THRESHOLD) {
5610                     fastMap[(int) id] = 0;
5611                 } else {
5612                     slowMap.remove(id);
5613                 }
5614                 if (active == 0) {
5615                     // gesture finished
5616                     counter = 0;
5617                 }
5618             }
5619             removed.clear();
5620             return active == 0;
5621         }
5622     }
5623 
5624 
5625     /***************************************************************************
5626      *                                                                         *
5627      *                         Drag and Drop Handling                          *
5628      *                                                                         *
5629      **************************************************************************/
5630 
5631     private ObjectProperty<EventHandler<? super DragEvent>> onDragEntered;
5632 
5633     public final void setOnDragEntered(EventHandler<? super DragEvent> value) {
5634         onDragEnteredProperty().set(value);
5635     }
5636 
5637     public final EventHandler<? super DragEvent> getOnDragEntered() {
5638         return onDragEntered == null ? null : onDragEntered.get();
5639     }
5640 
5641     /**
5642      * Defines a function to be called when drag gesture
5643      * enters this {@code Scene}.
5644      */
5645     public final ObjectProperty<EventHandler<? super DragEvent>> onDragEnteredProperty() {
5646         if (onDragEntered == null) {
5647             onDragEntered = new ObjectPropertyBase<EventHandler<? super DragEvent>>() {
5648 
5649                 @Override
5650                 protected void invalidated() {
5651                     setEventHandler(DragEvent.DRAG_ENTERED, get());
5652                 }
5653 
5654                 @Override
5655                 public Object getBean() {
5656                     return Scene.this;
5657                 }
5658 
5659                 @Override
5660                 public String getName() {
5661                     return "onDragEntered";
5662                 }
5663             };
5664         }
5665         return onDragEntered;
5666     }
5667 
5668     private ObjectProperty<EventHandler<? super DragEvent>> onDragExited;
5669 
5670     public final void setOnDragExited(EventHandler<? super DragEvent> value) {
5671         onDragExitedProperty().set(value);
5672     }
5673 
5674     public final EventHandler<? super DragEvent> getOnDragExited() {
5675         return onDragExited == null ? null : onDragExited.get();
5676     }
5677 
5678     /**
5679      * Defines a function to be called when drag gesture
5680      * exits this {@code Scene}.
5681      */
5682     public final ObjectProperty<EventHandler<? super DragEvent>> onDragExitedProperty() {
5683         if (onDragExited == null) {
5684             onDragExited = new ObjectPropertyBase<EventHandler<? super DragEvent>>() {
5685 
5686                 @Override
5687                 protected void invalidated() {
5688                     setEventHandler(DragEvent.DRAG_EXITED, get());
5689                 }
5690 
5691                 @Override
5692                 public Object getBean() {
5693                     return Scene.this;
5694                 }
5695 
5696                 @Override
5697                 public String getName() {
5698                     return "onDragExited";
5699                 }
5700             };
5701         }
5702         return onDragExited;
5703     }
5704 
5705     private ObjectProperty<EventHandler<? super DragEvent>> onDragOver;
5706 
5707     public final void setOnDragOver(EventHandler<? super DragEvent> value) {
5708         onDragOverProperty().set(value);
5709     }
5710 
5711     public final EventHandler<? super DragEvent> getOnDragOver() {
5712         return onDragOver == null ? null : onDragOver.get();
5713     }
5714 
5715     /**
5716      * Defines a function to be called when drag gesture progresses
5717      * within this {@code Scene}.
5718      */
5719     public final ObjectProperty<EventHandler<? super DragEvent>> onDragOverProperty() {
5720         if (onDragOver == null) {
5721             onDragOver = new ObjectPropertyBase<EventHandler<? super DragEvent>>() {
5722 
5723                 @Override
5724                 protected void invalidated() {
5725                     setEventHandler(DragEvent.DRAG_OVER, get());
5726                 }
5727 
5728                 @Override
5729                 public Object getBean() {
5730                     return Scene.this;
5731                 }
5732 
5733                 @Override
5734                 public String getName() {
5735                     return "onDragOver";
5736                 }
5737             };
5738         }
5739         return onDragOver;
5740     }
5741 
5742     // Do we want DRAG_TRANSFER_MODE_CHANGED event?
5743 //    private ObjectProperty<EventHandler<? super DragEvent>> onDragTransferModeChanged;
5744 //
5745 //    public final void setOnDragTransferModeChanged(EventHandler<? super DragEvent> value) {
5746 //        onDragTransferModeChangedProperty().set(value);
5747 //    }
5748 //
5749 //    public final EventHandler<? super DragEvent> getOnDragTransferModeChanged() {
5750 //        return onDragTransferModeChanged == null ? null : onDragTransferModeChanged.get();
5751 //    }
5752 //
5753 //    /**
5754 //     * Defines a function to be called this {@code Scene} if it is a potential
5755 //     * drag-and-drop target when the user takes action to change the intended
5756 //     * {@code TransferMode}.
5757 //     * The user can change the intended {@link TransferMode} by holding down
5758 //     * or releasing key modifiers.
5759 //     */
5760 //    public ObjectProperty<EventHandler<? super DragEvent>> onDragTransferModeChangedProperty() {
5761 //        if (onDragTransferModeChanged == null) {
5762 //            onDragTransferModeChanged = new SimpleObjectProperty<EventHandler<? super DragEvent>>() {
5763 //
5764 //                @Override
5765 //                protected void invalidated() {
5766 //                    setEventHandler(DragEvent.DRAG_TRANSFER_MODE_CHANGED, get());
5767 //                }
5768 //            };
5769 //        }
5770 //        return onDragTransferModeChanged;
5771 //    }
5772 
5773     private ObjectProperty<EventHandler<? super DragEvent>> onDragDropped;
5774 
5775     public final void setOnDragDropped(EventHandler<? super DragEvent> value) {
5776         onDragDroppedProperty().set(value);
5777     }
5778 
5779     public final EventHandler<? super DragEvent> getOnDragDropped() {
5780         return onDragDropped == null ? null : onDragDropped.get();
5781     }
5782 
5783     /**
5784      * Defines a function to be called when the mouse button is released
5785      * on this {@code Scene} during drag and drop gesture. Transfer of data from
5786      * the {@link DragEvent}'s {@link DragEvent#dragboard dragboard} should
5787      * happen in this function.
5788      */
5789     public final ObjectProperty<EventHandler<? super DragEvent>> onDragDroppedProperty() {
5790         if (onDragDropped == null) {
5791             onDragDropped = new ObjectPropertyBase<EventHandler<? super DragEvent>>() {
5792 
5793                 @Override
5794                 protected void invalidated() {
5795                     setEventHandler(DragEvent.DRAG_DROPPED, get());
5796                 }
5797 
5798                 @Override
5799                 public Object getBean() {
5800                     return Scene.this;
5801                 }
5802 
5803                 @Override
5804                 public String getName() {
5805                     return "onDragDropped";
5806                 }
5807             };
5808         }
5809         return onDragDropped;
5810     }
5811 
5812     private ObjectProperty<EventHandler<? super DragEvent>> onDragDone;
5813 
5814     public final void setOnDragDone(EventHandler<? super DragEvent> value) {
5815         onDragDoneProperty().set(value);
5816     }
5817 
5818     public final EventHandler<? super DragEvent> getOnDragDone() {
5819         return onDragDone == null ? null : onDragDone.get();
5820     }
5821 
5822     /**
5823      * Defines a function to be called when this @{code Scene} is a
5824      * drag and drop gesture source after its data has
5825      * been dropped on a drop target. The {@code transferMode} of the
5826      * event shows what just happened at the drop target.
5827      * If {@code transferMode} has the value {@code MOVE}, then the source can
5828      * clear out its data. Clearing the source's data gives the appropriate
5829      * appearance to a user that the data has been moved by the drag and drop
5830      * gesture. A {@code transferMode} that has the value {@code NONE}
5831      * indicates that no data was transferred during the drag and drop gesture.
5832      * Positional data for the {@code DragEvent} is invalid.  Valid positional
5833      * data for the {@code DragEvent} is presented in the {@link onDragDropped}
5834      * event handler.
5835      */
5836     public final ObjectProperty<EventHandler<? super DragEvent>> onDragDoneProperty() {
5837         if (onDragDone == null) {
5838             onDragDone = new ObjectPropertyBase<EventHandler<? super DragEvent>>() {
5839 
5840                 @Override
5841                 protected void invalidated() {
5842                     setEventHandler(DragEvent.DRAG_DONE, get());
5843                 }
5844 
5845                 @Override
5846                 public Object getBean() {
5847                     return Scene.this;
5848                 }
5849 
5850                 @Override
5851                 public String getName() {
5852                     return "onDragDone";
5853                 }
5854             };
5855         }
5856         return onDragDone;
5857     }
5858 
5859     /**
5860      * Confirms a potential drag and drop gesture that is recognized over this
5861      * {@code Scene}.
5862      * Can be called only from a DRAG_DETECTED event handler. The returned
5863      * {@link Dragboard} is used to transfer data during
5864      * the drag and drop gesture. Placing this {@code Scene}'s data on the
5865      * {@link Dragboard} also identifies this {@code Scene} as the source of
5866      * the drag and drop gesture.
5867      * More detail about drag and drop gestures is described in the overivew
5868      * of {@link DragEvent}.
5869      *
5870      * @see DragEvent
5871      * @param transferModes The supported {@code TransferMode}(s) of this {@code Node}
5872      * @return A {@code Dragboard} to place this {@code Scene}'s data on
5873      * @throws IllegalStateException if drag and drop cannot be started at this
5874      * moment (it's called outside of {@code DRAG_DETECTED} event handling).
5875      */
5876     public Dragboard startDragAndDrop(TransferMode... transferModes) {
5877         return startDragAndDrop(this, transferModes);
5878     }
5879 
5880     /**
5881      * Starts a full press-drag-release gesture with this scene as gesture
5882      * source. This method can be called only from a {@code DRAG_DETECTED} mouse
5883      * event handler. More detail about dragging gestures can be found
5884      * in the overview of {@link MouseEvent} and {@link MouseDragEvent}.
5885      *
5886      * @see MouseEvent
5887      * @see MouseDragEvent
5888      * @throws IllegalStateException if the full press-drag-release gesture
5889      * cannot be started at this moment (it's called outside of
5890      * {@code DRAG_DETECTED} event handling).
5891      * @since JavaFX 2.1
5892      */
5893     public void startFullDrag() {
5894         startFullDrag(this);
5895     }
5896 
5897 
5898     Dragboard startDragAndDrop(EventTarget source, TransferMode... transferModes) {
5899         Toolkit.getToolkit().checkFxUserThread();
5900         if (dndGesture == null ||
5901             (dndGesture.dragDetected != DragDetectedState.PROCESSING))
5902         {
5903             throw new IllegalStateException("Cannot start drag and drop " +
5904                     "outside of DRAG_DETECTED event handler");
5905         }
5906 
5907         Set<TransferMode> set = EnumSet.noneOf(TransferMode.class);
5908         for (TransferMode tm : InputEventUtils.safeTransferModes(transferModes)) {
5909             set.add(tm);
5910         }
5911         return dndGesture.startDrag(source, set);
5912     }
5913 
5914     void startFullDrag(EventTarget source) {
5915         Toolkit.getToolkit().checkFxUserThread();
5916         if (dndGesture.dragDetected != DragDetectedState.PROCESSING) {
5917             throw new IllegalStateException("Cannot start full drag " +
5918                     "outside of DRAG_DETECTED event handler");
5919         }
5920 
5921         if (dndGesture != null) {
5922             dndGesture.startFullPDR(source);
5923             return;
5924         }
5925 
5926         throw new IllegalStateException("Cannot start full drag when "
5927                 + "mouse button is not pressed");
5928     }
5929 
5930     /***************************************************************************
5931      *                                                                         *
5932      *                           Keyboard Handling                             *
5933      *                                                                         *
5934      **************************************************************************/
5935 
5936     /**
5937      * Defines a function to be called when some {@code Node} of this
5938      * {@code Scene} has input focus and a key has been pressed. The function
5939      * is called only if the event hasn't been already consumed during its
5940      * capturing or bubbling phase.
5941      */
5942     private ObjectProperty<EventHandler<? super KeyEvent>> onKeyPressed;
5943 
5944     public final void setOnKeyPressed(EventHandler<? super KeyEvent> value) {
5945         onKeyPressedProperty().set(value);
5946     }
5947 
5948     public final EventHandler<? super KeyEvent> getOnKeyPressed() {
5949         return onKeyPressed == null ? null : onKeyPressed.get();
5950     }
5951 
5952     public final ObjectProperty<EventHandler<? super KeyEvent>> onKeyPressedProperty() {
5953         if (onKeyPressed == null) {
5954             onKeyPressed = new ObjectPropertyBase<EventHandler<? super KeyEvent>>() {
5955 
5956                 @Override
5957                 protected void invalidated() {
5958                     setEventHandler(KeyEvent.KEY_PRESSED, get());
5959                 }
5960 
5961                 @Override
5962                 public Object getBean() {
5963                     return Scene.this;
5964                 }
5965 
5966                 @Override
5967                 public String getName() {
5968                     return "onKeyPressed";
5969                 }
5970             };
5971         }
5972         return onKeyPressed;
5973     }
5974 
5975     /**
5976      * Defines a function to be called when some {@code Node} of this
5977      * {@code Scene} has input focus and a key has been released. The function
5978      * is called only if the event hasn't been already consumed during its
5979      * capturing or bubbling phase.
5980      */
5981     private ObjectProperty<EventHandler<? super KeyEvent>> onKeyReleased;
5982 
5983     public final void setOnKeyReleased(EventHandler<? super KeyEvent> value) {
5984         onKeyReleasedProperty().set(value);
5985     }
5986 
5987     public final EventHandler<? super KeyEvent> getOnKeyReleased() {
5988         return onKeyReleased == null ? null : onKeyReleased.get();
5989     }
5990 
5991     public final ObjectProperty<EventHandler<? super KeyEvent>> onKeyReleasedProperty() {
5992         if (onKeyReleased == null) {
5993             onKeyReleased = new ObjectPropertyBase<EventHandler<? super KeyEvent>>() {
5994 
5995                 @Override
5996                 protected void invalidated() {
5997                     setEventHandler(KeyEvent.KEY_RELEASED, get());
5998                 }
5999 
6000                 @Override
6001                 public Object getBean() {
6002                     return Scene.this;
6003                 }
6004 
6005                 @Override
6006                 public String getName() {
6007                     return "onKeyReleased";
6008                 }
6009             };
6010         }
6011         return onKeyReleased;
6012     }
6013 
6014     /**
6015      * Defines a function to be called when some {@code Node} of this
6016      * {@code Scene} has input focus and a key has been typed. The function
6017      * is called only if the event hasn't been already consumed during its
6018      * capturing or bubbling phase.
6019      */
6020     private ObjectProperty<EventHandler<? super KeyEvent>> onKeyTyped;
6021 
6022     public final void setOnKeyTyped(
6023             EventHandler<? super KeyEvent> value) {
6024         onKeyTypedProperty().set( value);
6025 
6026     }
6027 
6028     public final EventHandler<? super KeyEvent> getOnKeyTyped(
6029             ) {
6030         return onKeyTyped == null ? null : onKeyTyped.get();
6031     }
6032 
6033     public final ObjectProperty<EventHandler<? super KeyEvent>> onKeyTypedProperty(
6034     ) {
6035         if (onKeyTyped == null) {
6036             onKeyTyped = new ObjectPropertyBase<EventHandler<? super KeyEvent>>() {
6037 
6038                 @Override
6039                 protected void invalidated() {
6040                     setEventHandler(KeyEvent.KEY_TYPED, get());
6041                 }
6042 
6043                 @Override
6044                 public Object getBean() {
6045                     return Scene.this;
6046                 }
6047 
6048                 @Override
6049                 public String getName() {
6050                     return "onKeyTyped";
6051                 }
6052             };
6053         }
6054         return onKeyTyped;
6055     }
6056 
6057     /***************************************************************************
6058      *                                                                         *
6059      *                           Input Method Handling                         *
6060      *                                                                         *
6061      **************************************************************************/
6062 
6063     /**
6064      * Defines a function to be called when this {@code Node}
6065      * has input focus and the input method text has changed.  If this
6066      * function is not defined in this {@code Node}, then it
6067      * receives the result string of the input method composition as a
6068      * series of {@code onKeyTyped} function calls.
6069      * </p>
6070      * When the {@code Node} loses the input focus, the JavaFX runtime
6071      * automatically commits the existing composed text if any.
6072      */
6073     private ObjectProperty<EventHandler<? super InputMethodEvent>> onInputMethodTextChanged;
6074 
6075     public final void setOnInputMethodTextChanged(
6076             EventHandler<? super InputMethodEvent> value) {
6077         onInputMethodTextChangedProperty().set( value);
6078     }
6079 
6080     public final EventHandler<? super InputMethodEvent> getOnInputMethodTextChanged() {
6081         return onInputMethodTextChanged == null ? null : onInputMethodTextChanged.get();
6082     }
6083 
6084     public final ObjectProperty<EventHandler<? super InputMethodEvent>> onInputMethodTextChangedProperty() {
6085         if (onInputMethodTextChanged == null) {
6086             onInputMethodTextChanged = new ObjectPropertyBase<EventHandler<? super InputMethodEvent>>() {
6087 
6088                 @Override
6089                 protected void invalidated() {
6090                     setEventHandler(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED, get());
6091                 }
6092 
6093                 @Override
6094                 public Object getBean() {
6095                     return Scene.this;
6096                 }
6097 
6098                 @Override
6099                 public String getName() {
6100                     return "onInputMethodTextChanged";
6101                 }
6102             };
6103         }
6104         return onInputMethodTextChanged;
6105     }
6106 
6107     /*
6108      * This class represents a picked target - either node, or scne, or null.
6109      * It provides functionality needed for the targets and covers the fact
6110      * that they are different kinds of animals.
6111      */
6112     private static class TargetWrapper {
6113         private Scene scene;
6114         private Node node;
6115         private PickResult result;
6116 
6117         /**
6118          * Fills the list with the target and all its parents (including scene)
6119          */
6120         public void fillHierarchy(final List<EventTarget> list) {
6121             list.clear();
6122             Node n = node;
6123             while(n != null) {
6124                 list.add(n);
6125                 final Parent p = n.getParent();
6126                 n = p != null ? p : n.getSubScene();
6127             }
6128 
6129             if (scene != null) {
6130                 list.add(scene);
6131             }
6132         }
6133 
6134         public EventTarget getEventTarget() {
6135             return node != null ? node : scene;
6136         }
6137 
6138         public Cursor getCursor() {
6139             Cursor cursor = null;
6140             if (node != null) {
6141                 cursor = node.getCursor();
6142                 Node n = node.getParent();
6143                 while (cursor == null && n != null) {
6144                     cursor = n.getCursor();
6145 
6146                     final Parent p = n.getParent();
6147                     n = p != null ? p : n.getSubScene();
6148                 }
6149             }
6150             return cursor;
6151         }
6152 
6153         public void clear() {
6154             set(null, null);
6155             result = null;
6156         }
6157 
6158         public void setNodeResult(PickResult result) {
6159             if (result != null) {
6160                 this.result = result;
6161                 final Node n = result.getIntersectedNode();
6162                 set(n, n.getScene());
6163             }
6164         }
6165 
6166         // Pass null scene if the mouse is outside of the window content
6167         public void setSceneResult(PickResult result, Scene scene) {
6168             if (result != null) {
6169                 this.result = result;
6170                 set(null, scene);
6171             }
6172         }
6173 
6174         public PickResult getResult() {
6175             return result;
6176         }
6177 
6178         public void copy(TargetWrapper tw) {
6179             node = tw.node;
6180             scene = tw.scene;
6181             result = tw.result;
6182         }
6183 
6184         private void set(Node n, Scene s) {
6185             node = n;
6186             scene = s;
6187         }
6188     }
6189 
6190     /*************************************************************************
6191     *                                                                        *
6192     *                                                                        *
6193     *                                                                        *
6194     *************************************************************************/
6195 
6196     private static final Object USER_DATA_KEY = new Object();
6197     // A map containing a set of properties for this scene
6198     private ObservableMap<Object, Object> properties;
6199 
6200     /**
6201       * Returns an observable map of properties on this node for use primarily
6202       * by application developers.
6203       *
6204       * @return an observable map of properties on this node for use primarily
6205       * by application developers
6206       *
6207       * @since JavaFX 8u40
6208       */
6209      public final ObservableMap<Object, Object> getProperties() {
6210         if (properties == null) {
6211             properties = FXCollections.observableMap(new HashMap<Object, Object>());
6212         }
6213         return properties;
6214     }
6215 
6216     /**
6217      * Tests if Scene has properties.
6218      * @return true if node has properties.
6219      *
6220      * @since JavaFX 8u40
6221      */
6222      public boolean hasProperties() {
6223         return properties != null && !properties.isEmpty();
6224     }
6225 
6226     /**
6227      * Convenience method for setting a single Object property that can be
6228      * retrieved at a later date. This is functionally equivalent to calling
6229      * the getProperties().put(Object key, Object value) method. This can later
6230      * be retrieved by calling {@link Scene#getUserData()}.
6231      *
6232      * @param value The value to be stored - this can later be retrieved by calling
6233      *          {@link Scene#getUserData()}.
6234      *
6235      * @since JavaFX 8u40
6236      */
6237     public void setUserData(Object value) {
6238         getProperties().put(USER_DATA_KEY, value);
6239     }
6240 
6241     /**
6242      * Returns a previously set Object property, or null if no such property
6243      * has been set using the {@link Scene#setUserData(java.lang.Object)} method.
6244      *
6245      * @return The Object that was previously set, or null if no property
6246      *          has been set or if null was set.
6247      *
6248      * @since JavaFX 8u40
6249      */
6250     public Object getUserData() {
6251         return getProperties().get(USER_DATA_KEY);
6252     }
6253 
6254     /***************************************************************************
6255      *                                                                         *
6256      *                       Component Orientation Properties                  *
6257      *                                                                         *
6258      **************************************************************************/
6259 
6260     private static final NodeOrientation defaultNodeOrientation =
6261         AccessController.doPrivileged(
6262                 (PrivilegedAction<Boolean>) () -> Boolean.getBoolean("javafx.scene.nodeOrientation.RTL")) ? NodeOrientation.RIGHT_TO_LEFT : NodeOrientation.INHERIT;
6263 
6264 
6265 
6266     private ObjectProperty<NodeOrientation> nodeOrientation;
6267     private EffectiveOrientationProperty effectiveNodeOrientationProperty;
6268 
6269     private NodeOrientation effectiveNodeOrientation;
6270 
6271     public final void setNodeOrientation(NodeOrientation orientation) {
6272         nodeOrientationProperty().set(orientation);
6273     }
6274 
6275     public final NodeOrientation getNodeOrientation() {
6276         return nodeOrientation == null ? defaultNodeOrientation : nodeOrientation.get();
6277     }
6278 
6279     /**
6280      * Property holding NodeOrientation.
6281      * <p>
6282      * Node orientation describes the flow of visual data within a node.
6283      * In the English speaking world, visual data normally flows from
6284      * left-to-right. In an Arabic or Hebrew world, visual data flows
6285      * from right-to-left.  This is consistent with the reading order
6286      * of text in both worlds.  The default value is left-to-right.
6287      * </p>
6288      *
6289      * @return NodeOrientation
6290      * @since JavaFX 8.0
6291      */
6292     public final ObjectProperty<NodeOrientation> nodeOrientationProperty() {
6293         if (nodeOrientation == null) {
6294             nodeOrientation = new StyleableObjectProperty<NodeOrientation>(defaultNodeOrientation) {
6295                 @Override
6296                 protected void invalidated() {
6297                     sceneEffectiveOrientationInvalidated();
6298                     getRoot().applyCss();
6299                 }
6300 
6301                 @Override
6302                 public Object getBean() {
6303                     return Scene.this;
6304                 }
6305 
6306                 @Override
6307                 public String getName() {
6308                     return "nodeOrientation";
6309                 }
6310 
6311                 @Override
6312                 public CssMetaData getCssMetaData() {
6313                     //TODO - not yet supported
6314                     throw new UnsupportedOperationException("Not supported yet.");
6315                 }
6316             };
6317         }
6318         return nodeOrientation;
6319     }
6320 
6321     public final NodeOrientation getEffectiveNodeOrientation() {
6322         if (effectiveNodeOrientation == null) {
6323             effectiveNodeOrientation = calcEffectiveNodeOrientation();
6324         }
6325 
6326         return effectiveNodeOrientation;
6327     }
6328 
6329     /**
6330      * The effective node orientation of a scene resolves the inheritance of
6331      * node orientation, returning either left-to-right or right-to-left.
6332      * @since JavaFX 8.0
6333      */
6334     public final ReadOnlyObjectProperty<NodeOrientation>
6335             effectiveNodeOrientationProperty() {
6336         if (effectiveNodeOrientationProperty == null) {
6337             effectiveNodeOrientationProperty =
6338                     new EffectiveOrientationProperty();
6339         }
6340 
6341         return effectiveNodeOrientationProperty;
6342     }
6343 
6344     private void parentEffectiveOrientationInvalidated() {
6345         if (getNodeOrientation() == NodeOrientation.INHERIT) {
6346             sceneEffectiveOrientationInvalidated();
6347         }
6348     }
6349 
6350     private void sceneEffectiveOrientationInvalidated() {
6351         effectiveNodeOrientation = null;
6352 
6353         if (effectiveNodeOrientationProperty != null) {
6354             effectiveNodeOrientationProperty.invalidate();
6355         }
6356 
6357         getRoot().parentResolvedOrientationInvalidated();
6358     }
6359 
6360     private NodeOrientation calcEffectiveNodeOrientation() {
6361         NodeOrientation orientation = getNodeOrientation();
6362         if (orientation == NodeOrientation.INHERIT) {
6363             Window window = getWindow();
6364             if (window != null) {
6365                 Window parent = null;
6366                 if (window instanceof Stage) {
6367                     parent = ((Stage)window).getOwner();
6368                 } else {
6369                     if (window instanceof PopupWindow) {
6370                         parent = ((PopupWindow)window).getOwnerWindow();
6371                     }
6372                 }
6373                 if (parent != null) {
6374                     Scene scene = parent.getScene();
6375                     if (scene != null) return scene.getEffectiveNodeOrientation();
6376                 }
6377             }
6378             return NodeOrientation.LEFT_TO_RIGHT;
6379         }
6380         return orientation;
6381     }
6382 
6383     private final class EffectiveOrientationProperty
6384             extends ReadOnlyObjectPropertyBase<NodeOrientation> {
6385         @Override
6386         public NodeOrientation get() {
6387             return getEffectiveNodeOrientation();
6388         }
6389 
6390         @Override
6391         public Object getBean() {
6392             return Scene.this;
6393         }
6394 
6395         @Override
6396         public String getName() {
6397             return "effectiveNodeOrientation";
6398         }
6399 
6400         public void invalidate() {
6401             fireValueChangedEvent();
6402         }
6403     }
6404 
6405     private Map<Node, Accessible> accMap;
6406     Accessible removeAccessible(Node node) {
6407         if (accMap == null) return null;
6408         return accMap.remove(node);
6409     }
6410 
6411     void addAccessible(Node node, Accessible acc) {
6412         if (accMap == null) {
6413             accMap = new HashMap<Node, Accessible>();
6414         }
6415         accMap.put(node, acc);
6416     }
6417 
6418     private void disposeAccessibles() {
6419         if (accMap != null) {
6420             for (Map.Entry<Node, Accessible> entry : accMap.entrySet()) {
6421                 Node node = entry.getKey();
6422                 Accessible acc = entry.getValue();
6423                 if (node.accessible != null) {
6424                     /* This node has already been initialized to another scene.
6425                      * Note an accessible can be returned to the node before the
6426                      * pulse if getAccessible() is called. In which case it must
6427                      * already being removed from accMap.
6428                      */
6429                     if (node.accessible == acc) {
6430                         System.err.println("[A11y] 'node.accessible == acc' should never happen.");
6431                     }
6432                     if (node.getScene() == this) {
6433                         System.err.println("[A11y] 'node.getScene() == this' should never happen.");
6434                     }
6435                     acc.dispose();
6436                 } else {
6437                     if (node.getScene() == this) {
6438                         node.accessible = acc;
6439                     } else {
6440                         acc.dispose();
6441                     }
6442                 }
6443             }
6444             accMap.clear();
6445         }
6446     }
6447 
6448     private Accessible accessible;
6449     Accessible getAccessible() {
6450         /*
6451          * The accessible for the Scene should never be
6452          * requested when the peer is not set.
6453          * This can only happen in a error case where a
6454          * descender of this Scene was not disposed and
6455          * it still being used by the AT client and trying
6456          * to reach to the top level window.
6457          */
6458         if (peer == null) return null;
6459         if (accessible == null) {
6460             accessible = Application.GetApplication().createAccessible();
6461             accessible.setEventHandler(new Accessible.EventHandler() {
6462                 @Override public AccessControlContext getAccessControlContext() {
6463                     return impl_getPeer().getAccessControlContext();
6464                 }
6465 
6466                 @Override public Object getAttribute(AccessibleAttribute attribute,
6467                                                      Object... parameters) {
6468                     switch (attribute) {
6469                         case CHILDREN: {
6470                             Parent root = getRoot();
6471                             if (root != null) {
6472                                 return FXCollections.observableArrayList(root);
6473                             }
6474                             break;
6475                         }
6476                         case TEXT: {
6477                             Window w = getWindow();
6478                             if (w instanceof Stage) {
6479                                 return ((Stage)w).getTitle();
6480                             }
6481                             break;
6482                         }
6483                         case NODE_AT_POINT: {
6484                             Window window = getWindow();
6485                             /* is this screen to scene translation correct ? not considering camera ? */
6486                             Point2D pt = (Point2D)parameters[0];
6487                             PickResult res = pick(pt.getX() - getX() - window.getX(), pt.getY() - getY() - window.getY());
6488                             if (res != null) {
6489                                 Node node = res.getIntersectedNode();
6490                                 if (node != null) return node;
6491                             }
6492                             return getRoot();//not sure
6493                         }
6494                         case ROLE: return AccessibleRole.PARENT;
6495                         case SCENE: return Scene.this;
6496                         case FOCUS_NODE: {
6497                             if (transientFocusContainer != null) {
6498                                 return transientFocusContainer.queryAccessibleAttribute(AccessibleAttribute.FOCUS_NODE);
6499                             }
6500                             return getFocusOwner();
6501                         }
6502                         default:
6503                     }
6504                     return super.getAttribute(attribute, parameters);
6505                 }
6506             });
6507             PlatformImpl.accessibilityActiveProperty().set(true);
6508         }
6509         return accessible;
6510     }
6511 }