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