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