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