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