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