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