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