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