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.scene.input.ExtendedInputMethodRequests;
  29 import com.sun.javafx.tk.TKClipboard;
  30 import javafx.animation.KeyFrame;
  31 import javafx.animation.Timeline;
  32 import javafx.application.ConditionalFeature;
  33 import javafx.application.Platform;
  34 import javafx.beans.DefaultProperty;
  35 import javafx.beans.InvalidationListener;
  36 import javafx.beans.NamedArg;
  37 import javafx.beans.Observable;
  38 import javafx.beans.property.ObjectProperty;
  39 import javafx.beans.property.ObjectPropertyBase;
  40 import javafx.beans.property.ReadOnlyBooleanProperty;
  41 import javafx.beans.property.ReadOnlyDoubleProperty;
  42 import javafx.beans.property.ReadOnlyDoubleWrapper;
  43 import javafx.beans.property.ReadOnlyObjectProperty;
  44 import javafx.beans.property.ReadOnlyObjectPropertyBase;
  45 import javafx.beans.property.ReadOnlyObjectWrapper;
  46 import javafx.beans.property.SimpleObjectProperty;
  47 import javafx.beans.value.WritableValue;
  48 import javafx.collections.ListChangeListener.Change;
  49 import javafx.collections.ObservableList;
  50 import javafx.collections.ObservableMap;
  51 import javafx.css.CssMetaData;
  52 import javafx.css.StyleableObjectProperty;
  53 import javafx.event.ActionEvent;
  54 import javafx.event.Event;
  55 import javafx.event.EventDispatchChain;
  56 import javafx.event.EventDispatcher;
  57 import javafx.event.EventHandler;
  58 import javafx.event.EventTarget;
  59 import javafx.event.EventType;
  60 import javafx.geometry.Bounds;
  61 import javafx.geometry.NodeOrientation;
  62 import javafx.geometry.Orientation;
  63 import javafx.geometry.Point2D;
  64 import javafx.geometry.Point3D;
  65 import javafx.scene.image.WritableImage;
  66 import javafx.scene.input.ContextMenuEvent;
  67 import javafx.scene.input.DragEvent;
  68 import javafx.scene.input.Dragboard;
  69 import javafx.scene.input.GestureEvent;
  70 import javafx.scene.input.InputMethodEvent;
  71 import javafx.scene.input.InputMethodRequests;
  72 import javafx.scene.input.InputMethodTextRun;
  73 import javafx.scene.input.KeyCode;
  74 import javafx.scene.input.KeyCombination;
  75 import javafx.scene.input.KeyEvent;
  76 import javafx.scene.input.Mnemonic;
  77 import javafx.scene.input.MouseButton;
  78 import javafx.scene.input.MouseDragEvent;
  79 import javafx.scene.input.MouseEvent;
  80 import javafx.scene.input.PickResult;
  81 import javafx.scene.input.RotateEvent;
  82 import javafx.scene.input.ScrollEvent;
  83 import javafx.scene.input.SwipeEvent;
  84 import javafx.scene.input.TouchEvent;
  85 import javafx.scene.input.TouchPoint;
  86 import javafx.scene.input.TransferMode;
  87 import javafx.scene.input.ZoomEvent;
  88 import javafx.scene.paint.Color;
  89 import javafx.scene.paint.Paint;
  90 import javafx.stage.PopupWindow;
  91 import javafx.stage.Stage;
  92 import javafx.stage.StageStyle;
  93 import javafx.stage.Window;
  94 import javafx.util.Callback;
  95 import javafx.util.Duration;
  96 
  97 import java.security.AccessControlContext;
  98 import java.security.AccessController;
  99 import java.security.PrivilegedAction;
 100 import java.util.ArrayList;
 101 import java.util.Arrays;
 102 import java.util.EnumMap;
 103 import java.util.EnumSet;
 104 import java.util.HashMap;
 105 import java.util.LinkedHashSet;
 106 import java.util.LinkedList;
 107 import java.util.List;
 108 import java.util.Map;
 109 import java.util.Set;
 110 
 111 import com.sun.javafx.Logging;
 112 import com.sun.javafx.Utils;
 113 import com.sun.javafx.collections.TrackableObservableList;
 114 import com.sun.javafx.css.StyleManager;
 115 import com.sun.javafx.cursor.CursorFrame;
 116 import com.sun.javafx.event.EventQueue;
 117 import com.sun.javafx.geom.PickRay;
 118 import com.sun.javafx.geom.Vec3d;
 119 import com.sun.javafx.geom.transform.BaseTransform;
 120 import com.sun.javafx.perf.PerformanceTracker;
 121 import com.sun.javafx.robot.impl.FXRobotHelper;
 122 import com.sun.javafx.runtime.SystemProperties;
 123 import com.sun.javafx.scene.CssFlags;
 124 import com.sun.javafx.scene.SceneEventDispatcher;
 125 import com.sun.javafx.scene.SceneHelper;
 126 import com.sun.javafx.scene.input.InputEventUtils;
 127 import com.sun.javafx.scene.input.KeyCodeMap;
 128 import com.sun.javafx.scene.input.PickResultChooser;
 129 import com.sun.javafx.scene.traversal.Direction;
 130 import com.sun.javafx.scene.traversal.TraversalEngine;
 131 import com.sun.javafx.sg.prism.NGCamera;
 132 import com.sun.javafx.sg.prism.NGLightBase;
 133 import com.sun.javafx.tk.TKDragGestureListener;
 134 import com.sun.javafx.tk.TKDragSourceListener;
 135 import com.sun.javafx.tk.TKDropTargetListener;
 136 import com.sun.javafx.tk.TKPulseListener;
 137 import com.sun.javafx.tk.TKScene;
 138 import com.sun.javafx.tk.TKSceneListener;
 139 import com.sun.javafx.tk.TKScenePaintListener;
 140 import com.sun.javafx.tk.TKStage;
 141 import com.sun.javafx.tk.Toolkit;
 142 import com.sun.javafx.scene.LayoutFlags;
 143 import com.sun.prism.impl.PrismSettings;
 144 
 145 import sun.util.logging.PlatformLogger;
 146 import sun.util.logging.PlatformLogger.Level;
 147 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGER;
 148 import static com.sun.javafx.logging.PulseLogger.PULSE_LOGGING_ENABLED;
 149 
 150 /**
 151  * The JavaFX {@code Scene} class is the container for all content in a scene graph.
 152  * The background of the scene is filled as specified by the {@code fill} property.
 153  * <p>
 154  * The application must specify the root {@code Node} for the scene graph by setting
 155  * the {@code root} property.   If a {@code Group} is used as the root, the
 156  * contents of the scene graph will be clipped by the scene's width and height and
 157  * changes to the scene's size (if user resizes the stage) will not alter the
 158  * layout of the scene graph.    If a resizable node (layout {@code Region} or
 159  * {@code Control} is set as the root, then the root's size will track the
 160  * scene's size, causing the contents to be relayed out as necessary.
 161  * <p>
 162  * The scene's size may be initialized by the application during construction.
 163  * If no size is specified, the scene will automatically compute its initial
 164  * size based on the preferred size of its content. If only one dimension is specified,
 165  * the other dimension is computed using the specified dimension, respecting content bias
 166  * of a root.
 167  * 
 168  * <p>
 169  * A default headlight will be added to a scene that contains {@code Shape3D}
 170  * but without light source. This light source is a {@code Color.WHITE}
 171  * {@code PointLight} placed at the camera position.
 172  *
 173  * <p>
 174  * Scene objects must be constructed and modified on the
 175  * JavaFX Application Thread.
 176  * </p>
 177  *
 178  * <p>Example:</p>
 179  *
 180  * <p>
 181  * <pre>
 182 import javafx.scene.*;
 183 import javafx.scene.paint.*;
 184 import javafx.scene.shape.*;
 185 
 186 Group root = new Group();
 187 Scene s = new Scene(root, 300, 300, Color.BLACK);
 188 
 189 Rectangle r = new Rectangle(25,25,250,250);
 190 r.setFill(Color.BLUE);
 191 
 192 root.getChildren().add(r);
 193  * </pre>
 194  * </p>
 195  * @since JavaFX 2.0
 196  */
 197 @DefaultProperty("root")
 198 public class Scene implements EventTarget {
 199 
 200     private double widthSetByUser = -1.0;
 201     private double heightSetByUser = -1.0;
 202     private boolean sizeInitialized = false;
 203     private final boolean depthBuffer;
 204     private final SceneAntialiasing antiAliasing;
 205 
 206     private int dirtyBits;
 207 
 208     private final AccessControlContext acc = AccessController.getContext();
 209 
 210     private Camera defaultCamera;
 211 
 212     //Neither width nor height are initialized and will be calculated according to content when this Scene
 213     //is shown for the first time.
 214 //    public Scene() {
 215 //        //this(-1, -1, (Parent) new Group());
 216 //        this(-1, -1, (Parent)null);
 217 //    }
 218 
 219     /**
 220      * Creates a Scene for a specific root Node.
 221      *
 222      * @param root The root node of the scene graph
 223      *
 224      * @throws IllegalStateException if this constructor is called on a thread
 225      * other than the JavaFX Application Thread.
 226      * @throws NullPointerException if root is null
 227      */
 228     public Scene(@NamedArg("root") Parent root) {
 229         this(root, -1, -1, Color.WHITE, false, SceneAntialiasing.DISABLED);
 230     }
 231 
 232 //Public constructor initializing public-init properties
 233 //When width < 0, and or height < 0 is passed, then width and/or height are understood as unitialized
 234 //Unitialized dimension is calculated when Scene is shown for the first time.
 235 //    public Scene(
 236 //            @Default("-1") double width,
 237 //            @Default("-1") double height) {
 238 //        //this(width, height, (Parent)new Group());
 239 //        this(width, height, (Parent)null);
 240 //    }
 241 //
 242 //    public Scene(double width, double height, Paint fill) {
 243 //        //this(width, height, (Parent) new Group());
 244 //        this(width, height, (Parent)null);
 245 //        setFill(fill);
 246 //    }
 247 
 248     /**
 249      * Creates a Scene for a specific root Node with a specific size.
 250      *
 251      * @param root The root node of the scene graph
 252      * @param width The width of the scene
 253      * @param height The height of the scene
 254      *
 255      * @throws IllegalStateException if this constructor is called on a thread
 256      * other than the JavaFX Application Thread.
 257      * @throws NullPointerException if root is null
 258      */
 259     public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height) {
 260         this(root, width, height, Color.WHITE, false, SceneAntialiasing.DISABLED);
 261     }
 262 
 263     /**
 264      * Creates a Scene for a specific root Node with a fill.
 265      *
 266      * @param root The parent
 267      * @param fill The fill
 268      *
 269      * @throws IllegalStateException if this constructor is called on a thread
 270      * other than the JavaFX Application Thread.
 271      * @throws NullPointerException if root is null
 272      */
 273     public Scene(@NamedArg("root") Parent root, @NamedArg(value="fill", defaultValue="WHITE") Paint fill) {
 274         this(root, -1, -1, fill, false, SceneAntialiasing.DISABLED);
 275     }
 276 
 277     /**
 278      * Creates a Scene for a specific root Node with a specific size and fill.
 279      *
 280      * @param root The root node of the scene graph
 281      * @param width The width of the scene
 282      * @param height The height of the scene
 283      * @param fill The fill
 284      *
 285      * @throws IllegalStateException if this constructor is called on a thread
 286      * other than the JavaFX Application Thread.
 287      * @throws NullPointerException if root is null
 288      */
 289     public Scene(@NamedArg("root") Parent root, @NamedArg("width") double width, @NamedArg("height") double height,
 290             @NamedArg(value="fill", defaultValue="WHITE") Paint fill) {
 291         this(root, width, height, fill, false, SceneAntialiasing.DISABLED);
 292     }
 293 
 294     /**
 295      * Constructs a scene consisting of a root, with a dimension of width and
 296      * height, and specifies whether a depth buffer is created for this scene.
 297      *
 298      * @param root The root node of the scene graph
 299      * @param width The width of the scene
 300      * @param height The height of the scene
 301      * @param depthBuffer The depth buffer flag
 302      * <p>
 303      * The depthBuffer flag is a conditional feature and its default value is
 304      * false. See
 305      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 306      * for more information.
 307      *
 308      * @throws IllegalStateException if this constructor is called on a thread
 309      * other than the JavaFX Application Thread.
 310      * @throws NullPointerException if root is null
 311      *
 312      * @see javafx.scene.Node#setDepthTest(DepthTest)
 313      */
 314     public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height, @NamedArg("depthBuffer") boolean depthBuffer) {
 315         this(root, width, height, Color.WHITE, depthBuffer, SceneAntialiasing.DISABLED);
 316     }
 317 
 318     /**
 319      * Constructs a scene consisting of a root, with a dimension of width and
 320      * height, specifies whether a depth buffer is created for this scene and
 321      * specifies whether scene anti-aliasing is requested.
 322      *
 323      * @param root The root node of the scene graph
 324      * @param width The width of the scene
 325      * @param height The height of the scene
 326      * @param depthBuffer The depth buffer flag
 327      * @param antiAliasing The scene anti-aliasing attribute. A value of
 328      * {@code null} is treated as DISABLED.
 329      * <p>
 330      * The depthBuffer and antiAliasing are conditional features. With the
 331      * respective default values of: false and {@code SceneAntialiasing.DISABLED}. See
 332      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 333      * for more information.
 334      *
 335      * @throws IllegalStateException if this constructor is called on a thread
 336      * other than the JavaFX Application Thread.
 337      * @throws NullPointerException if root is null
 338      *
 339      * @see javafx.scene.Node#setDepthTest(DepthTest)
 340      * @since JavaFX 8.0
 341      */
 342     public Scene(@NamedArg("root") Parent root, @NamedArg(value="width", defaultValue="-1") double width, @NamedArg(value="height", defaultValue="-1") double height,
 343             @NamedArg("depthBuffer") boolean depthBuffer,
 344             @NamedArg(value="antiAliasing", defaultValue="DISABLED") SceneAntialiasing antiAliasing) {
 345         this(root, width, height, Color.WHITE, depthBuffer, antiAliasing);
 346 
 347         if (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED &&
 348                 !Toolkit.getToolkit().isAntiAliasingSupported())
 349         {
 350             String logname = Scene.class.getName();
 351             PlatformLogger.getLogger(logname).warning("System can't support "
 352                 + "antiAliasing");
 353         }
 354     }
 355 
 356     private Scene(Parent root, double width, double height, Paint fill,
 357             boolean depthBuffer, SceneAntialiasing antiAliasing) {
 358         this.depthBuffer = depthBuffer;
 359         this.antiAliasing = antiAliasing;
 360         if (root == null) {
 361             throw new NullPointerException("Root cannot be null");
 362         }
 363 
 364         if ((depthBuffer || (antiAliasing != null && antiAliasing != SceneAntialiasing.DISABLED))
 365                 && !Platform.isSupported(ConditionalFeature.SCENE3D)) {
 366             String logname = Scene.class.getName();
 367             PlatformLogger.getLogger(logname).warning("System can't support "
 368                     + "ConditionalFeature.SCENE3D");
 369         }
 370 
 371         Toolkit.getToolkit().checkFxUserThread();
 372         init();
 373         setRoot(root);
 374         init(width, height);
 375         setFill(fill);
 376     }
 377 
 378     static {
 379             PerformanceTracker.setSceneAccessor(new PerformanceTracker.SceneAccessor() {
 380                 public void setPerfTracker(Scene scene, PerformanceTracker tracker) {
 381                     synchronized (trackerMonitor) {
 382                         scene.tracker = tracker;
 383                     }
 384                 }
 385                 public PerformanceTracker getPerfTracker(Scene scene) {
 386                     synchronized (trackerMonitor) {
 387                         return scene.tracker;
 388                     }
 389                 }
 390             });
 391             FXRobotHelper.setSceneAccessor(new FXRobotHelper.FXRobotSceneAccessor() {
 392                 public void processKeyEvent(Scene scene, KeyEvent keyEvent) {
 393                     scene.impl_processKeyEvent(keyEvent);
 394                 }
 395                 public void processMouseEvent(Scene scene, MouseEvent mouseEvent) {
 396                     scene.impl_processMouseEvent(mouseEvent);
 397                 }
 398                 public void processScrollEvent(Scene scene, ScrollEvent scrollEvent) {
 399                     scene.processGestureEvent(scrollEvent, scene.scrollGesture);
 400                 }
 401                 public ObservableList<Node> getChildren(Parent parent) {
 402                     return parent.getChildren(); //was impl_getChildren
 403                 }
 404                 public Object renderToImage(Scene scene, Object platformImage) {
 405                     return scene.snapshot(null).impl_getPlatformImage();
 406                 }
 407             });
 408             SceneHelper.setSceneAccessor(
 409                     new SceneHelper.SceneAccessor() {
 410                         @Override
 411                         public void setPaused(boolean paused) {
 412                             Scene.paused = paused;
 413                         }
 414 
 415                         @Override
 416                         public void parentEffectiveOrientationInvalidated(
 417                                 final Scene scene) {
 418                             scene.parentEffectiveOrientationInvalidated();
 419                         }
 420 
 421                         @Override
 422                         public Camera getEffectiveCamera(Scene scene) {
 423                             return scene.getEffectiveCamera();
 424                         }
 425 
 426                         @Override
 427                         public Scene createPopupScene(Parent root) {
 428                             return new Scene(root) {
 429                                        @Override
 430                                        void doLayoutPass() {
 431                                            resizeRootToPreferredSize(getRoot());
 432                                            super.doLayoutPass();
 433                                        }
 434 
 435                                        @Override
 436                                        void resizeRootOnSceneSizeChange(
 437                                                double newWidth,
 438                                                double newHeight) {
 439                                            // don't resize
 440                                        }
 441                                    };
 442                         }
 443                     });
 444         }
 445 
 446         // Reserve space for 30 nodes in the dirtyNodes set.
 447         private static final int MIN_DIRTY_CAPACITY = 30;
 448 
 449         // For debugging
 450         private static boolean inSynchronizer = false;
 451         private static boolean inMousePick = false;
 452         private static boolean allowPGAccess = false;
 453         private static int pgAccessCount = 0;
 454 
 455         // Flag set by the Toolkit when we are paused for JMX debugging
 456         private static boolean paused = false;
 457 
 458         /**
 459          * Used for debugging purposes. Returns true if we are in either the
 460          * mouse event code (picking) or the synchronizer, or if the scene is
 461          * not yet initialized,
 462          *
 463          */
 464         static boolean isPGAccessAllowed() {
 465             return inSynchronizer || inMousePick || allowPGAccess;
 466         }
 467 
 468         /**
 469          * @treatAsPrivate implementation detail
 470          * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 471          */
 472         @Deprecated
 473         public static void impl_setAllowPGAccess(boolean flag) {
 474             if (Utils.assertionEnabled()) {
 475                 if (flag) {
 476                     pgAccessCount++;
 477                     allowPGAccess = true;
 478                 }
 479                 else {
 480                     if (pgAccessCount <= 0) {
 481                         throw new java.lang.AssertionError("*** pgAccessCount underflow");
 482                     }
 483                     if (--pgAccessCount == 0) {
 484                         allowPGAccess = false;
 485                     }
 486                 }
 487             }
 488         }
 489 
 490         /**
 491          * If true, use the platform's drag gesture detection
 492          * else use Scene-level detection as per DnDGesture.process(MouseEvent, List)
 493          */
 494         private static final boolean PLATFORM_DRAG_GESTURE_INITIATION = false;
 495 
 496     /**
 497      * Set of dirty nodes; processed once per frame by the synchronizer.
 498      * When a node's state changes such that it becomes "dirty" with respect
 499      * to the graphics stack and requires synchronization, then that node
 500      * is added to this list. Note that if state on the Node changes, but it
 501      * was already dirty, then the Node doesn't add itself again.
 502      * <p>
 503      * Because at initialization time every node in the scene graph is dirty,
 504      * we have a special state and special code path during initialization
 505      * that does not involve adding each node to the dirtyNodes list. When
 506      * dirtyNodes is null, that means this Scene has not yet been synchronized.
 507      * A good default size is then created for the dirtyNodes list.
 508      * <p>
 509      * We double-buffer the set so that we can add new nodes to the
 510      * set while processing the existing set. This avoids our having to
 511      * take a snapshot of the set (e.g., with toArray()) and reduces garbage.
 512      */
 513     private Node[] dirtyNodes;
 514     private int dirtyNodesSize;
 515 
 516     /**
 517      * Add the specified node to this scene's dirty list. Called by the
 518      * markDirty method in Node or when the Node's scene changes.
 519      */
 520     void addToDirtyList(Node n) {
 521         Toolkit.getToolkit().checkFxUserThread();
 522 
 523         if (dirtyNodes == null || dirtyNodesSize == 0) {
 524             if (impl_peer != null) {
 525                 Toolkit.getToolkit().requestNextPulse();
 526             }
 527         }
 528 
 529         if (dirtyNodes != null) {
 530             if (dirtyNodesSize == dirtyNodes.length) {
 531                 Node[] tmp = new Node[dirtyNodesSize + (dirtyNodesSize >> 1)];
 532                 System.arraycopy(dirtyNodes, 0, tmp, 0, dirtyNodesSize);
 533                 dirtyNodes = tmp;
 534             }
 535             dirtyNodes[dirtyNodesSize++] = n;
 536         }
 537     }
 538 
 539     private void doCSSPass() {
 540         final Parent sceneRoot = getRoot();
 541         //
 542         // RT-17547: when the tree is synchronized, the dirty bits are
 543         // are cleared but the cssFlag might still be something other than
 544         // clean.
 545         //
 546         // Before RT-17547, the code checked the dirty bit. But this is
 547         // superfluous since the dirty bit will be set if the flag is not clean,
 548         // but the flag will never be anything other than clean if the dirty
 549         // bit is not set. The dirty bit is still needed, however, since setting
 550         // it ensures a pulse if no other dirty bits have been set.
 551         //
 552         // For the purpose of showing the change, the dirty bit
 553         // check code was commented out and not removed.
 554         //
 555 //        if (sceneRoot.impl_isDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS)) {
 556         if (sceneRoot.cssFlag != CssFlags.CLEAN) {
 557             // The dirty bit isn't checked but we must ensure it is cleared.
 558             // The cssFlag is set to clean in either Node.processCSS or
 559             // Node.impl_processCSS(boolean)
 560             sceneRoot.impl_clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
 561             sceneRoot.processCSS(null);
 562         }
 563     }
 564 
 565     void doLayoutPass() {
 566         final Parent r = getRoot();
 567         if (r != null) {
 568             r.layout();
 569         }
 570     }
 571 
 572     /**
 573      * The peer of this scene
 574      *
 575      * @treatAsPrivate implementation detail
 576      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 577      */
 578     @Deprecated
 579     private TKScene impl_peer;
 580 
 581     /**
 582      * Get Scene's peer
 583      *
 584      * @treatAsPrivate implementation detail
 585      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 586      */
 587     @Deprecated
 588     public TKScene impl_getPeer() {
 589         return impl_peer;
 590     }
 591 
 592     /**
 593      * The scene pulse listener that gets called on toolkit pulses
 594      */
 595     ScenePulseListener scenePulseListener = new ScenePulseListener();
 596 
 597     /**
 598      * @treatAsPrivate implementation detail
 599      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 600      */
 601     @Deprecated
 602     public TKPulseListener impl_getScenePulseListener() {
 603         if (SystemProperties.isDebug()) {
 604             return scenePulseListener;
 605         }
 606         return null;
 607     }
 608 
 609     /**
 610      * Return the defined {@code SceneAntialiasing} for this {@code Scene}.
 611      * <p>
 612      * Note: this is a conditional feature. See
 613      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 614      * and {@link javafx.scene.SceneAntialiasing SceneAntialiasing}
 615      * for more information.
 616      * @since JavaFX 8.0
 617      */
 618     public final SceneAntialiasing getAntiAliasing() {
 619         return antiAliasing;
 620     }
 621 
 622     private boolean getAntiAliasingInternal() {
 623         return (antiAliasing != null &&
 624                 Toolkit.getToolkit().isAntiAliasingSupported() &&
 625                 Platform.isSupported(ConditionalFeature.SCENE3D)) ?
 626                 antiAliasing != SceneAntialiasing.DISABLED : false;
 627     }
 628 
 629     /**
 630      * The {@code Window} for this {@code Scene}
 631      */
 632     private ReadOnlyObjectWrapper<Window> window;
 633 
 634     private void setWindow(Window value) {
 635         windowPropertyImpl().set(value);
 636     }
 637 
 638     public final Window getWindow() {
 639         return window == null ? null : window.get();
 640     }
 641 
 642     public final ReadOnlyObjectProperty<Window> windowProperty() {
 643         return windowPropertyImpl().getReadOnlyProperty();
 644     }
 645 
 646     private ReadOnlyObjectWrapper<Window> windowPropertyImpl() {
 647         if (window == null) {
 648             window = new ReadOnlyObjectWrapper<Window>() {
 649                 private Window oldWindow;
 650 
 651                 @Override protected void invalidated() {
 652                     final Window newWindow = get();
 653                     getKeyHandler().windowForSceneChanged(oldWindow, newWindow);
 654                     if (oldWindow != null) {
 655                         impl_disposePeer();
 656                     }
 657                     if (newWindow != null) {
 658                         impl_initPeer();
 659                     }
 660                     parentEffectiveOrientationInvalidated();
 661 
 662                     oldWindow = newWindow;
 663                 }
 664 
 665                 @Override
 666                 public Object getBean() {
 667                     return Scene.this;
 668                 }
 669 
 670                 @Override
 671                 public String getName() {
 672                     return "window";
 673                 }
 674             };
 675         }
 676         return window;
 677     }
 678 
 679     /**
 680      * @treatAsPrivate implementation detail
 681      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 682      */
 683     @Deprecated
 684     public void impl_setWindow(Window value) {
 685         setWindow(value);
 686     }
 687 
 688     /**
 689      * @treatAsPrivate implementation detail
 690      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 691      */
 692     @Deprecated
 693     public void impl_initPeer() {
 694         assert impl_peer == null;
 695 
 696         Window window = getWindow();
 697         // impl_initPeer() is only called from Window, either when the window
 698         // is being shown, or the window scene is being changed. In any case
 699         // this scene's window cannot be null.
 700         assert window != null;
 701 
 702         TKStage windowPeer = window.impl_getPeer();
 703         if (windowPeer == null) {
 704             // This is fine, the window is not visible. impl_initPeer() will
 705             // be called again later, when the window is being shown.
 706             return;
 707         }
 708 
 709         PerformanceTracker.logEvent("Scene.initPeer started");
 710 
 711         impl_setAllowPGAccess(true);
 712 
 713         Toolkit tk = Toolkit.getToolkit();
 714         impl_peer = windowPeer.createTKScene(isDepthBufferInternal(), getAntiAliasingInternal(), acc);
 715         PerformanceTracker.logEvent("Scene.initPeer TKScene created");
 716         impl_peer.setTKSceneListener(new ScenePeerListener());
 717         impl_peer.setTKScenePaintListener(new ScenePeerPaintListener());
 718         PerformanceTracker.logEvent("Scene.initPeer TKScene set");
 719         impl_peer.setRoot(getRoot().impl_getPeer());
 720         impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill()));
 721         getEffectiveCamera().impl_updatePeer();
 722         impl_peer.setCamera((NGCamera) getEffectiveCamera().impl_getPeer());
 723         impl_peer.markDirty();
 724         PerformanceTracker.logEvent("Scene.initPeer TKScene initialized");
 725 
 726         impl_setAllowPGAccess(false);
 727 
 728         tk.addSceneTkPulseListener(scenePulseListener);
 729         // listen to dnd gestures coming from the platform
 730         if (PLATFORM_DRAG_GESTURE_INITIATION) {
 731             if (dragGestureListener == null) {
 732                 dragGestureListener = new DragGestureListener();
 733             }
 734             tk.registerDragGestureListener(impl_peer, EnumSet.allOf(TransferMode.class), dragGestureListener);
 735         }
 736         tk.enableDrop(impl_peer, new DropTargetListener());
 737         tk.installInputMethodRequests(impl_peer, new InputMethodRequestsDelegate());
 738 
 739         PerformanceTracker.logEvent("Scene.initPeer finished");
 740     }
 741 
 742     /**
 743      * @treatAsPrivate implementation detail
 744      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 745      */
 746     @Deprecated
 747     public void impl_disposePeer() {
 748         if (impl_peer == null) {
 749             // This is fine, the window is either not shown yet and there is no
 750             // need in disposing scene peer, or is hidden and impl_disposePeer()
 751             // has already been called.
 752             return;
 753         }
 754 
 755         PerformanceTracker.logEvent("Scene.disposePeer started");
 756 
 757         Toolkit tk = Toolkit.getToolkit();
 758         tk.removeSceneTkPulseListener(scenePulseListener);
 759         impl_peer.dispose();
 760         impl_peer = null;
 761 
 762         PerformanceTracker.logEvent("Scene.disposePeer finished");
 763     }
 764 
 765     DnDGesture dndGesture = null;
 766     DragGestureListener dragGestureListener;
 767     /**
 768      * The horizontal location of this {@code Scene} on the {@code Window}.
 769      */
 770     private ReadOnlyDoubleWrapper x;
 771 
 772     private final void setX(double value) {
 773         xPropertyImpl().set(value);
 774     }
 775 
 776     public final double getX() {
 777         return x == null ? 0.0 : x.get();
 778     }
 779 
 780     public final ReadOnlyDoubleProperty xProperty() {
 781         return xPropertyImpl().getReadOnlyProperty();
 782     }
 783 
 784     private ReadOnlyDoubleWrapper xPropertyImpl() {
 785         if (x == null) {
 786             x = new ReadOnlyDoubleWrapper(this, "x");
 787         }
 788         return x;
 789     }
 790 
 791     /**
 792      * The vertical location of this {@code Scene} on the {@code Window}.
 793      */
 794     private ReadOnlyDoubleWrapper y;
 795 
 796     private final void setY(double value) {
 797         yPropertyImpl().set(value);
 798     }
 799 
 800     public final double getY() {
 801         return y == null ? 0.0 : y.get();
 802     }
 803 
 804     public final ReadOnlyDoubleProperty yProperty() {
 805         return yPropertyImpl().getReadOnlyProperty();
 806     }
 807 
 808     private ReadOnlyDoubleWrapper yPropertyImpl() {
 809         if (y == null) {
 810             y = new ReadOnlyDoubleWrapper(this, "y");
 811         }
 812         return y;
 813     }
 814 
 815     /**
 816      * The width of this {@code Scene}
 817      */
 818     private ReadOnlyDoubleWrapper width;
 819 
 820     private final void setWidth(double value) {
 821         widthPropertyImpl().set(value);
 822     }
 823 
 824     public final double getWidth() {
 825         return width == null ? 0.0 : width.get();
 826     }
 827 
 828     public final ReadOnlyDoubleProperty widthProperty() {
 829         return widthPropertyImpl().getReadOnlyProperty();
 830     }
 831 
 832     private ReadOnlyDoubleWrapper widthPropertyImpl() {
 833         if (width == null) {
 834             width = new ReadOnlyDoubleWrapper() {
 835 
 836                 @Override
 837                 protected void invalidated() {
 838                     final Parent _root = getRoot();
 839                     //TODO - use a better method to update mirroring
 840                     if (_root.getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT) {
 841                         _root.impl_transformsChanged();
 842                     }
 843                     if (_root.isResizable()) {
 844                         resizeRootOnSceneSizeChange(get() - _root.getLayoutX() - _root.getTranslateX(), _root.getLayoutBounds().getHeight());
 845                     }
 846 
 847                     getEffectiveCamera().setViewWidth(get());
 848                 }
 849 
 850                 @Override
 851                 public Object getBean() {
 852                     return Scene.this;
 853                 }
 854 
 855                 @Override
 856                 public String getName() {
 857                     return "width";
 858                 }
 859             };
 860         }
 861         return width;
 862     }
 863 
 864     /**
 865      * The height of this {@code Scene}
 866      */
 867     private ReadOnlyDoubleWrapper height;
 868 
 869     private final void setHeight(double value) {
 870         heightPropertyImpl().set(value);
 871     }
 872 
 873     public final double getHeight() {
 874         return height == null ? 0.0 : height.get();
 875     }
 876 
 877     public final ReadOnlyDoubleProperty heightProperty() {
 878         return heightPropertyImpl().getReadOnlyProperty();
 879     }
 880 
 881     private ReadOnlyDoubleWrapper heightPropertyImpl() {
 882         if (height == null) {
 883             height = new ReadOnlyDoubleWrapper() {
 884 
 885                 @Override
 886                 protected void invalidated() {
 887                     final Parent _root = getRoot();
 888                     if (_root.isResizable()) {
 889                         resizeRootOnSceneSizeChange(_root.getLayoutBounds().getWidth(), get() - _root.getLayoutY() - _root.getTranslateY());
 890                     }
 891 
 892                     getEffectiveCamera().setViewHeight(get());
 893                 }
 894 
 895                 @Override
 896                 public Object getBean() {
 897                     return Scene.this;
 898                 }
 899 
 900                 @Override
 901                 public String getName() {
 902                     return "height";
 903                 }
 904             };
 905         }
 906         return height;
 907     }
 908 
 909     void resizeRootOnSceneSizeChange(double newWidth, double newHeight) {
 910         getRoot().resize(newWidth, newHeight);
 911     }
 912 
 913     // Reusable target wrapper (to avoid creating new one for each picking)
 914     private TargetWrapper tmpTargetWrapper = new TargetWrapper();
 915 
 916     /**
 917      * Specifies the type of camera use for rendering this {@code Scene}.
 918      * If {@code camera} is null, a parallel camera is used for rendering.
 919      * It is illegal to set a camera that belongs to other {@code Scene}
 920      * or {@code SubScene}.
 921      * <p>
 922      * Note: this is a conditional feature. See
 923      * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
 924      * for more information.
 925      *
 926      * @defaultValue null
 927      */
 928     private ObjectProperty<Camera> camera;
 929 
 930     public final void setCamera(Camera value) {
 931         cameraProperty().set(value);
 932     }
 933 
 934     public final Camera getCamera() {
 935         return camera == null ? null : camera.get();
 936     }
 937 
 938     public final ObjectProperty<Camera> cameraProperty() {
 939         if (camera == null) {
 940             camera = new ObjectPropertyBase<Camera>() {
 941                 Camera oldCamera = null;
 942 
 943                 @Override
 944                 protected void invalidated() {
 945                     Camera _value = get();
 946                     if (_value != null) {
 947                         if (_value instanceof PerspectiveCamera
 948                                 && !Platform.isSupported(ConditionalFeature.SCENE3D)) {
 949                             String logname = Scene.class.getName();
 950                             PlatformLogger.getLogger(logname).warning("System can't support "
 951                                     + "ConditionalFeature.SCENE3D");
 952                         }
 953                         // Illegal value if it belongs to other scene or any subscene
 954                         if ((_value.getScene() != null && _value.getScene() != Scene.this)
 955                                 || _value.getSubScene() != null) {
 956                             throw new IllegalArgumentException(_value
 957                                     + "is already part of other scene or subscene");
 958                         }
 959                         // throws exception if the camera already has a different owner
 960                         _value.setOwnerScene(Scene.this);
 961                         _value.setViewWidth(getWidth());
 962                         _value.setViewHeight(getHeight());
 963                     }
 964                     if (oldCamera != null && oldCamera != _value) {
 965                         oldCamera.setOwnerScene(null);
 966                     }
 967                     oldCamera = _value;
 968                 }
 969 
 970                 @Override
 971                 public Object getBean() {
 972                     return Scene.this;
 973                 }
 974 
 975                 @Override
 976                 public String getName() {
 977                     return "camera";
 978                 }
 979             };
 980         }
 981         return camera;
 982     }
 983 
 984     Camera getEffectiveCamera() {
 985         final Camera cam = getCamera();
 986         if (cam == null
 987                 || (cam instanceof PerspectiveCamera
 988                 && !Platform.isSupported(ConditionalFeature.SCENE3D))) {
 989             if (defaultCamera == null) {
 990                 defaultCamera = new ParallelCamera();
 991                 defaultCamera.setOwnerScene(this);
 992                 defaultCamera.setViewWidth(getWidth());
 993                 defaultCamera.setViewHeight(getHeight());
 994             }
 995             return defaultCamera;
 996         }
 997 
 998         return cam;
 999     }
1000 
1001     // Used by the camera
1002     void markCameraDirty() {
1003         markDirty(DirtyBits.CAMERA_DIRTY);
1004         setNeedsRepaint();
1005     }
1006 
1007     void markCursorDirty() {
1008         markDirty(DirtyBits.CURSOR_DIRTY);
1009     }
1010 
1011     /**
1012      * Defines the background fill of this {@code Scene}. Both a {@code null}
1013      * value meaning paint no background and a {@link javafx.scene.paint.Paint}
1014      * with transparency are supported, but what is painted behind it will
1015      * depend on the platform.  The default value is the color white.
1016      *
1017      * @defaultValue WHITE
1018      */
1019     private ObjectProperty<Paint> fill;
1020 
1021     public final void setFill(Paint value) {
1022         fillProperty().set(value);
1023     }
1024 
1025     public final Paint getFill() {
1026         return fill == null ? Color.WHITE : fill.get();
1027     }
1028 
1029     public final ObjectProperty<Paint> fillProperty() {
1030         if (fill == null) {
1031             fill = new ObjectPropertyBase<Paint>(Color.WHITE) {
1032 
1033                 @Override
1034                 protected void invalidated() {
1035                     markDirty(DirtyBits.FILL_DIRTY);
1036                 }
1037 
1038                 @Override
1039                 public Object getBean() {
1040                     return Scene.this;
1041                 }
1042 
1043                 @Override
1044                 public String getName() {
1045                     return "fill";
1046                 }
1047             };
1048         }
1049         return fill;
1050     }
1051 
1052     /**
1053      * Defines the root {@code Node} of the scene graph.
1054      * If a {@code Group} is used as the root, the
1055      * contents of the scene graph will be clipped by the scene's width and height and
1056      * changes to the scene's size (if user resizes the stage) will not alter the
1057      * layout of the scene graph.    If a resizable node (layout {@code Region} or
1058      * {@code Control}) is set as the root, then the root's size will track the
1059      * scene's size, causing the contents to be relayed out as necessary.
1060      *
1061      * Scene doesn't accept null root.
1062      *
1063      */
1064     private ObjectProperty<Parent> root;
1065 
1066     public final void setRoot(Parent value) {
1067         rootProperty().set(value);
1068     }
1069 
1070     public final Parent getRoot() {
1071         return root == null ? null : root.get();
1072     }
1073 
1074     Parent oldRoot;
1075     public final ObjectProperty<Parent> rootProperty() {
1076         if (root == null) {
1077             root = new ObjectPropertyBase<Parent>() {
1078 
1079                 private void forceUnbind() {
1080                     System.err.println("Unbinding illegal root.");
1081                     unbind();
1082                 }
1083 
1084                 @Override
1085                 protected void invalidated() {
1086                     Parent _value = get();
1087 
1088                     if (_value == null) {
1089                         if (isBound()) forceUnbind();
1090                         throw new NullPointerException("Scene's root cannot be null");
1091                     }
1092 
1093                     if (_value.getParent() != null) {
1094                         if (isBound()) forceUnbind();
1095                         throw new IllegalArgumentException(_value +
1096                                 "is already inside a scene-graph and cannot be set as root");
1097                     }
1098                     if (_value.getClipParent() != null) {
1099                         if (isBound()) forceUnbind();
1100                         throw new IllegalArgumentException(_value +
1101                                 "is set as a clip on another node, so cannot be set as root");
1102                     }
1103                     if (_value.getScene() != null && _value.getScene().getRoot() == _value && _value.getScene() != Scene.this) {
1104                         if (isBound()) forceUnbind();
1105                         throw new IllegalArgumentException(_value +
1106                                 "is already set as root of another scene");
1107                     }
1108 
1109                     if (oldRoot != null) {
1110                         oldRoot.setScenes(null, null);
1111                         oldRoot.setImpl_traversalEngine(null);
1112                     }
1113                     oldRoot = _value;
1114                     if (_value.getImpl_traversalEngine() == null) {
1115                         _value.setImpl_traversalEngine(new TraversalEngine(_value, true));
1116                     }
1117                     _value.getStyleClass().add(0, "root");
1118                     _value.setScenes(Scene.this, null);
1119                     markDirty(DirtyBits.ROOT_DIRTY);
1120                     _value.resize(getWidth(), getHeight()); // maybe no-op if root is not resizable
1121                     _value.requestLayout();
1122                 }
1123 
1124                 @Override
1125                 public Object getBean() {
1126                     return Scene.this;
1127                 }
1128 
1129                 @Override
1130                 public String getName() {
1131                     return "root";
1132                 }
1133             };
1134         }
1135         return root;
1136     }
1137 
1138     void setNeedsRepaint() {
1139         if (this.impl_peer != null) {
1140             impl_peer.entireSceneNeedsRepaint();
1141         }
1142     }
1143 
1144     // Process CSS and layout and sync the scene prior to the snapshot
1145     // operation of the given node for this scene (currently the node
1146     // is unused but could possibly be used in the future to optimize this)
1147     void doCSSLayoutSyncForSnapshot(Node node) {
1148         if (!sizeInitialized) {
1149             preferredSize();
1150         } else {
1151             doCSSPass();
1152         }
1153 
1154         // we do not need pulse in the snapshot code
1155         // because this scene can be stage-less
1156         doLayoutPass();
1157 
1158         if (!paused) {
1159             getRoot().updateBounds();
1160             if (impl_peer != null) {
1161                 impl_peer.waitForRenderingToComplete();
1162                 impl_peer.waitForSynchronization();
1163                 try {
1164                     // Run the synchronizer while holding the render lock
1165                     scenePulseListener.synchronizeSceneNodes();
1166                 } finally {
1167                     impl_peer.releaseSynchronization(false);
1168                 }
1169             } else {
1170                 scenePulseListener.synchronizeSceneNodes();
1171             }
1172         }
1173 
1174     }
1175 
1176     // Shared method for Scene.snapshot and Node.snapshot. It is static because
1177     // we might be doing a Node snapshot with a null scene
1178     static WritableImage doSnapshot(Scene scene,
1179             double x, double y, double w, double h,
1180             Node root, BaseTransform transform, boolean depthBuffer,
1181             Paint fill, Camera camera, WritableImage wimg) {
1182 
1183         Toolkit tk = Toolkit.getToolkit();
1184         Toolkit.ImageRenderingContext context = new Toolkit.ImageRenderingContext();
1185 
1186         int xMin = (int)Math.floor(x);
1187         int yMin = (int)Math.floor(y);
1188         int xMax = (int)Math.ceil(x + w);
1189         int yMax = (int)Math.ceil(y + h);
1190         int width = Math.max(xMax - xMin, 1);
1191         int height = Math.max(yMax - yMin, 1);
1192         if (wimg == null) {
1193             wimg = new WritableImage(width, height);
1194         } else {
1195             width = (int)wimg.getWidth();
1196             height = (int)wimg.getHeight();
1197         }
1198 
1199         impl_setAllowPGAccess(true);
1200         context.x = xMin;
1201         context.y = yMin;
1202         context.width = width;
1203         context.height = height;
1204         context.transform = transform;
1205         context.depthBuffer = depthBuffer;
1206         context.root = root.impl_getPeer();
1207         context.platformPaint = fill == null ? null : tk.getPaint(fill);
1208         double cameraViewWidth = 1.0;
1209         double cameraViewHeight = 1.0;
1210         if (camera != null) {
1211             // temporarily adjust camera viewport to the snapshot size
1212             cameraViewWidth = camera.getViewWidth();
1213             cameraViewHeight = camera.getViewHeight();
1214             camera.setViewWidth(width);
1215             camera.setViewHeight(height);
1216             camera.impl_updatePeer();
1217             context.camera = camera.impl_getPeer();
1218         } else {
1219             context.camera = null;
1220         }
1221 
1222         // Grab the lights from the scene
1223         context.lights = null;
1224         if (scene != null && !scene.lights.isEmpty()) {
1225             context.lights = new NGLightBase[scene.lights.size()];
1226             for (int i = 0; i < scene.lights.size(); i++) {
1227                 context.lights[i] = scene.lights.get(i).impl_getPeer();
1228             }
1229         }
1230 
1231         Toolkit.WritableImageAccessor accessor = Toolkit.getWritableImageAccessor();
1232         context.platformImage = accessor.getTkImageLoader(wimg);
1233         impl_setAllowPGAccess(false);
1234         Object tkImage = tk.renderToImage(context);
1235         accessor.loadTkImage(wimg, tkImage);
1236 
1237         if (camera != null) {
1238             impl_setAllowPGAccess(true);
1239             camera.setViewWidth(cameraViewWidth);
1240             camera.setViewHeight(cameraViewHeight);
1241             camera.impl_updatePeer();
1242             impl_setAllowPGAccess(false);
1243         }
1244 
1245         // if this scene belongs to some stage
1246         // we need to mark the entire scene as dirty
1247         // because dirty logic is buggy
1248         if (scene != null && scene.impl_peer != null) {
1249             scene.setNeedsRepaint();
1250         }
1251 
1252         return wimg;
1253     }
1254 
1255     /**
1256      * Implementation method for snapshot
1257      */
1258     private WritableImage doSnapshot(WritableImage img) {
1259         // TODO: no need to do CSS, layout or sync in the deferred case,
1260         // if this scene is attached to a visible stage
1261         doCSSLayoutSyncForSnapshot(getRoot());
1262 
1263         double w = getWidth();
1264         double h = getHeight();
1265         BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
1266 
1267         return doSnapshot(this, 0, 0, w, h,
1268                 getRoot(), transform, isDepthBufferInternal(),
1269                 getFill(), getEffectiveCamera(), img);
1270     }
1271 
1272     // Pulse listener used to run all deferred (async) snapshot requests
1273     private static TKPulseListener snapshotPulseListener = null;
1274 
1275     private static List<Runnable> snapshotRunnableListA;
1276     private static List<Runnable> snapshotRunnableListB;
1277     private static List<Runnable> snapshotRunnableList;
1278 
1279     static void addSnapshotRunnable(Runnable r) {
1280         Toolkit.getToolkit().checkFxUserThread();
1281 
1282         if (snapshotPulseListener == null) {
1283             snapshotRunnableListA = new ArrayList<Runnable>();
1284             snapshotRunnableListB = new ArrayList<Runnable>();
1285             snapshotRunnableList = snapshotRunnableListA;
1286 
1287             snapshotPulseListener = new TKPulseListener() {
1288                 @Override public void pulse() {
1289                     if (snapshotRunnableList.size() > 0) {
1290                         List<Runnable> runnables = snapshotRunnableList;
1291                         if (snapshotRunnableList == snapshotRunnableListA) {
1292                             snapshotRunnableList = snapshotRunnableListB;
1293                         } else {
1294                             snapshotRunnableList = snapshotRunnableListA;
1295                         }
1296                         for (Runnable r : runnables) {
1297                             try {
1298                                 r.run();
1299                             } catch (Throwable th) {
1300                                 System.err.println("Exception in snapshot runnable");
1301                                 th.printStackTrace(System.err);
1302                             }
1303                         }
1304                         runnables.clear();
1305                     }
1306                 }
1307             };
1308 
1309             // Add listener that will be called after all of the scenes have
1310             // had layout and CSS processing, and have been synced
1311             Toolkit.getToolkit().addPostSceneTkPulseListener(snapshotPulseListener);
1312         }
1313         snapshotRunnableList.add(r);
1314         Toolkit.getToolkit().requestNextPulse();
1315     }
1316 
1317     /**
1318      * Takes a snapshot of this scene and returns the rendered image when
1319      * it is ready.
1320      * CSS and layout processing will be done for the scene prior to
1321      * rendering it.
1322      * The entire destination image is cleared using the fill {@code Paint}
1323      * of this scene. The nodes in the scene are then rendered to the image.
1324      * The point (0,0) in scene coordinates is mapped to (0,0) in the image.
1325      * If the image is smaller than the size of the scene, then the rendering
1326      * will be clipped by the image.
1327      *
1328      * <p>
1329      * When taking a snapshot of a scene that is being animated, either
1330      * explicitly by the application or implicitly (such as chart animation),
1331      * the snapshot will be rendered based on the state of the scene graph at
1332      * the moment the snapshot is taken and will not reflect any subsequent
1333      * animation changes.
1334      * </p>
1335      *
1336      * @param image the writable image that will be used to hold the rendered scene.
1337      * It may be null in which case a new WritableImage will be constructed.
1338      * If the image is non-null, the scene will be rendered into the
1339      * existing image.
1340      * In this case, the width and height of the image determine the area
1341      * that is rendered instead of the width and height of the scene.
1342      *
1343      * @throws IllegalStateException if this method is called on a thread
1344      *     other than the JavaFX Application Thread.
1345      *
1346      * @return the rendered image
1347      * @since JavaFX 2.2
1348      */
1349     public WritableImage snapshot(WritableImage image) {
1350         if (!paused) {
1351             Toolkit.getToolkit().checkFxUserThread();
1352         }
1353 
1354         return doSnapshot(image);
1355     }
1356 
1357     /**
1358      * Takes a snapshot of this scene at the next frame and calls the
1359      * specified callback method when the image is ready.
1360      * CSS and layout processing will be done for the scene prior to
1361      * rendering it.
1362      * The entire destination image is cleared using the fill {@code Paint}
1363      * of this scene. The nodes in the scene are then rendered to the image.
1364      * The point (0,0) in scene coordinates is mapped to (0,0) in the image.
1365      * If the image is smaller than the size of the scene, then the rendering
1366      * will be clipped by the image.
1367      *
1368      * <p>
1369      * This is an asynchronous call, which means that other
1370      * events or animation might be processed before the scene is rendered.
1371      * If any such events modify a node in the scene that modification will
1372      * be reflected in the rendered image (as it will also be reflected in
1373      * the frame rendered to the Stage).
1374      * </p>
1375      *
1376      * <p>
1377      * When taking a snapshot of a scene that is being animated, either
1378      * explicitly by the application or implicitly (such as chart animation),
1379      * the snapshot will be rendered based on the state of the scene graph at
1380      * the moment the snapshot is taken and will not reflect any subsequent
1381      * animation changes.
1382      * </p>
1383      *
1384      * @param callback a class whose call method will be called when the image
1385      * is ready. The SnapshotResult that is passed into the call method of
1386      * the callback will contain the rendered image and the source scene
1387      * that was rendered. The callback parameter must not be null.
1388      *
1389      * @param image the writable image that will be used to hold the rendered scene.
1390      * It may be null in which case a new WritableImage will be constructed.
1391      * If the image is non-null, the scene will be rendered into the
1392      * existing image.
1393      * In this case, the width and height of the image determine the area
1394      * that is rendered instead of the width and height of the scene.
1395      *
1396      * @throws IllegalStateException if this method is called on a thread
1397      *     other than the JavaFX Application Thread.
1398      *
1399      * @throws NullPointerException if the callback parameter is null.
1400      * @since JavaFX 2.2
1401      */
1402     public void snapshot(Callback<SnapshotResult, Void> callback, WritableImage image) {
1403         Toolkit.getToolkit().checkFxUserThread();
1404         if (callback == null) {
1405             throw new NullPointerException("The callback must not be null");
1406         }
1407 
1408         final Callback<SnapshotResult, Void> theCallback = callback;
1409         final WritableImage theImage = image;
1410 
1411         // Create a deferred runnable that will be run from a pulse listener
1412         // that is called after all of the scenes have been synced but before
1413         // any of them have been rendered.
1414         final Runnable snapshotRunnable = new Runnable() {
1415             @Override public void run() {
1416                 WritableImage img = doSnapshot(theImage);
1417 //                System.err.println("Calling snapshot callback");
1418                 SnapshotResult result = new SnapshotResult(img, Scene.this, null);
1419                 try {
1420                     Void v = theCallback.call(result);
1421                 } catch (Throwable th) {
1422                     System.err.println("Exception in snapshot callback");
1423                     th.printStackTrace(System.err);
1424                 }
1425             }
1426         };
1427 //        System.err.println("Schedule a snapshot in the future");
1428         addSnapshotRunnable(snapshotRunnable);
1429     }
1430 
1431     /**
1432      * Defines the mouse cursor for this {@code Scene}.
1433      */
1434     private ObjectProperty<Cursor> cursor;
1435 
1436     public final void setCursor(Cursor value) {
1437         cursorProperty().set(value);
1438     }
1439 
1440     public final Cursor getCursor() {
1441         return cursor == null ? null : cursor.get();
1442     }
1443 
1444     public final ObjectProperty<Cursor> cursorProperty() {
1445         if (cursor == null) {
1446             cursor = new ObjectPropertyBase<Cursor>() {
1447                          @Override
1448                          protected void invalidated() {
1449                              markCursorDirty();
1450                          }
1451 
1452                          @Override
1453                          public Object getBean() {
1454                              return Scene.this;
1455                          }
1456 
1457                          @Override
1458                          public String getName() {
1459                              return "cursor";
1460                          }
1461                      };
1462         }
1463         return cursor;
1464     }
1465 
1466     /**
1467      * Looks for any node within the scene graph based on the specified CSS selector.
1468      * If more than one node matches the specified selector, this function
1469      * returns the first of them.
1470      * If no nodes are found with this id, then null is returned.
1471      *
1472      * @param selector The css selector to look up
1473      * @return the {@code Node} in the scene which matches the CSS {@code selector},
1474      * or {@code null} if none is found.
1475      */
1476      public Node lookup(String selector) {
1477          return getRoot().lookup(selector);
1478      }
1479     /**
1480      * A ObservableList of string URLs linking to the stylesheets to use with this scene's
1481      * contents. For additional information about using CSS with the
1482      * scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1483      * Guide</a>.
1484      */
1485     private final ObservableList<String> stylesheets  = new TrackableObservableList<String>() {
1486         @Override
1487         protected void onChanged(Change<String> c) {
1488             StyleManager.getInstance().stylesheetsChanged(Scene.this, c);
1489             // RT-9784 - if stylesheet is removed, reset styled properties to
1490             // their initial value.
1491             c.reset();
1492             while(c.next()) {
1493                 if (c.wasRemoved() == false) {
1494                     continue;
1495                 }
1496                 break; // no point in resetting more than once...
1497             }
1498             getRoot().impl_reapplyCSS();
1499         }
1500     };
1501 
1502     /**
1503      * Gets an observable list of string URLs linking to the stylesheets to use
1504      * with this scene's contents. For additional information about using CSS
1505      * with the scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1506      * Guide</a>.
1507      *
1508      * @return the list of stylesheets to use with this scene
1509      */
1510     public final ObservableList<String> getStylesheets() { return stylesheets; }
1511 
1512     /**
1513      * Retrieves the depth buffer attribute for this scene.
1514      * @return the depth buffer attribute.
1515      */
1516     public final boolean isDepthBuffer() {
1517         return depthBuffer;
1518     }
1519 
1520     boolean isDepthBufferInternal() {
1521         if (!Platform.isSupported(ConditionalFeature.SCENE3D)) {
1522             return false;
1523         }
1524         return depthBuffer;
1525     }
1526 
1527     private void init(double width, double height) {
1528         if (width >= 0) {
1529             widthSetByUser = width;
1530             setWidth((float)width);
1531         }
1532         if (height >= 0) {
1533             heightSetByUser = height;
1534             setHeight((float)height);
1535         }
1536         sizeInitialized = (widthSetByUser >= 0 && heightSetByUser >= 0);
1537     }
1538 
1539     private void init() {
1540         if (PerformanceTracker.isLoggingEnabled()) {
1541             PerformanceTracker.logEvent("Scene.init for [" + this + "]");
1542         }
1543         mouseHandler = new MouseHandler();
1544         clickGenerator = new ClickGenerator();
1545 
1546         if (PerformanceTracker.isLoggingEnabled()) {
1547             PerformanceTracker.logEvent("Scene.init for [" + this + "] - finished");
1548         }
1549     }
1550 
1551     private void preferredSize() {
1552         final Parent root = getRoot();
1553 
1554         // one or the other isn't initialized, need to perform layout in
1555         // order to ensure we can properly measure the preferred size of the
1556         // scene
1557         doCSSPass();
1558 
1559         resizeRootToPreferredSize(root);
1560         doLayoutPass();
1561 
1562         if (widthSetByUser < 0) {
1563             setWidth(root.isResizable()? root.getLayoutX() + root.getTranslateX() + root.getLayoutBounds().getWidth() :
1564                             root.getBoundsInParent().getMaxX());
1565         } else {
1566             setWidth(widthSetByUser);
1567         }
1568 
1569         if (heightSetByUser < 0) {
1570             setHeight(root.isResizable()? root.getLayoutY() + root.getTranslateY() + root.getLayoutBounds().getHeight() :
1571                             root.getBoundsInParent().getMaxY());
1572         } else {
1573             setHeight(heightSetByUser);
1574         }
1575 
1576         sizeInitialized = (getWidth() > 0) && (getHeight() > 0);
1577 
1578         PerformanceTracker.logEvent("Scene preferred bounds computation complete");
1579     }
1580 
1581     final void resizeRootToPreferredSize(Parent root) {
1582         final double preferredWidth;
1583         final double preferredHeight;
1584 
1585         final Orientation contentBias = root.getContentBias();
1586         if (contentBias == null) {
1587             preferredWidth = getPreferredWidth(root, widthSetByUser, -1);
1588             preferredHeight = getPreferredHeight(root, heightSetByUser, -1);
1589         } else if (contentBias == Orientation.HORIZONTAL) {
1590             // height depends on width
1591             preferredWidth = getPreferredWidth(root, widthSetByUser, -1);
1592             preferredHeight = getPreferredHeight(root, heightSetByUser,
1593                                                        preferredWidth);
1594         } else /* if (contentBias == Orientation.VERTICAL) */ {
1595             // width depends on height
1596             preferredHeight = getPreferredHeight(root, heightSetByUser, -1);
1597             preferredWidth = getPreferredWidth(root, widthSetByUser,
1598                                                      preferredHeight);
1599         }
1600 
1601         root.resize(preferredWidth, preferredHeight);
1602     }
1603 
1604     private static double getPreferredWidth(Parent root,
1605                                             double forcedWidth,
1606                                             double height) {
1607         if (forcedWidth >= 0) {
1608             return forcedWidth;
1609         }
1610         final double normalizedHeight = (height >= 0) ? height : -1;
1611         return root.boundedSize(root.prefWidth(normalizedHeight),
1612                                 root.minWidth(normalizedHeight),
1613                                 root.maxWidth(normalizedHeight));
1614     }
1615 
1616     private static double getPreferredHeight(Parent root,
1617                                              double forcedHeight,
1618                                              double width) {
1619         if (forcedHeight >= 0) {
1620             return forcedHeight;
1621         }
1622         final double normalizedWidth = (width >= 0) ? width : -1;
1623         return root.boundedSize(root.prefHeight(normalizedWidth),
1624                                 root.minHeight(normalizedWidth),
1625                                 root.maxHeight(normalizedWidth));
1626     }
1627 
1628     /**
1629      * @treatAsPrivate implementation detail
1630      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1631      */
1632     @Deprecated
1633     public void impl_preferredSize() {
1634         preferredSize();
1635     }
1636 
1637     private PerformanceTracker tracker;
1638     private static final Object trackerMonitor = new Object();
1639 
1640     // mouse events handling
1641     private MouseHandler mouseHandler;
1642     private ClickGenerator clickGenerator;
1643 
1644     // gesture events handling
1645     private Point2D cursorScreenPos;
1646     private Point2D cursorScenePos;
1647 
1648     private static class TouchGesture {
1649         EventTarget target;
1650         Point2D sceneCoords;
1651         Point2D screenCoords;
1652         boolean finished;
1653     }
1654 
1655     private final TouchGesture scrollGesture = new TouchGesture();
1656     private final TouchGesture zoomGesture = new TouchGesture();
1657     private final TouchGesture rotateGesture = new TouchGesture();
1658     private final TouchGesture swipeGesture = new TouchGesture();
1659 
1660     // touch events handling
1661     private TouchMap touchMap = new TouchMap();
1662     private TouchEvent nextTouchEvent = null;
1663     private TouchPoint[] touchPoints = null;
1664     private int touchEventSetId = 0;
1665     private int touchPointIndex = 0;
1666     private Map<Integer, EventTarget> touchTargets =
1667             new HashMap<Integer, EventTarget>();
1668 
1669     /**
1670      * @treatAsPrivate implementation detail
1671      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1672      */
1673     // SB-dependency: RT-22747 has been filed to track this
1674     @Deprecated
1675     public void impl_processMouseEvent(MouseEvent e) {
1676         mouseHandler.process(e, false);
1677     }
1678 
1679     private void processMenuEvent(double x2, double y2, double xAbs, double yAbs, boolean isKeyboardTrigger) {
1680         EventTarget eventTarget = null;
1681         Scene.inMousePick = true;
1682         if (isKeyboardTrigger) {
1683             Node sceneFocusOwner = getFocusOwner();
1684 
1685             // for keyboard triggers set coordinates inside focus owner
1686             final double xOffset = xAbs - x2;
1687             final double yOffset = yAbs - y2;
1688             if (sceneFocusOwner != null) {
1689                 final Bounds bounds = sceneFocusOwner.localToScene(
1690                         sceneFocusOwner.getBoundsInLocal());
1691                 x2 = bounds.getMinX() + bounds.getWidth() / 4;
1692                 y2 = bounds.getMinY() + bounds.getHeight() / 2;
1693                 eventTarget = sceneFocusOwner;
1694             } else {
1695                 x2 = Scene.this.getWidth() / 4;
1696                 y2 = Scene.this.getWidth() / 2;
1697                 eventTarget = Scene.this;
1698             }
1699 
1700             xAbs = x2 + xOffset;
1701             yAbs = y2 + yOffset;
1702         }
1703 
1704         final PickResult res = pick(x2, y2);
1705 
1706         if (!isKeyboardTrigger) {
1707             eventTarget = res.getIntersectedNode();
1708             if (eventTarget == null) {
1709                 eventTarget = this;
1710             }
1711         }
1712 
1713         if (eventTarget != null) {
1714             ContextMenuEvent context = new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED,
1715                     x2, y2, xAbs, yAbs, isKeyboardTrigger, res);
1716             Event.fireEvent(eventTarget, context);
1717         }
1718         Scene.inMousePick = false;
1719     }
1720 
1721     private void processGestureEvent(GestureEvent e, TouchGesture gesture) {
1722         EventTarget pickedTarget = null;
1723 
1724         if (e.getEventType() == ZoomEvent.ZOOM_STARTED ||
1725                 e.getEventType() == RotateEvent.ROTATION_STARTED ||
1726                 e.getEventType() == ScrollEvent.SCROLL_STARTED) {
1727             gesture.target = null;
1728             gesture.finished = false;
1729         }
1730 
1731         if (gesture.target != null && (!gesture.finished || e.isInertia())) {
1732             pickedTarget = gesture.target;
1733         } else {
1734             pickedTarget = e.getPickResult().getIntersectedNode();
1735             if (pickedTarget == null) {
1736                 pickedTarget = this;
1737             }
1738         }
1739 
1740         if (e.getEventType() == ZoomEvent.ZOOM_STARTED ||
1741                 e.getEventType() == RotateEvent.ROTATION_STARTED ||
1742                 e.getEventType() == ScrollEvent.SCROLL_STARTED) {
1743             gesture.target = pickedTarget;
1744         }
1745         if (e.getEventType() != ZoomEvent.ZOOM_FINISHED &&
1746                 e.getEventType() != RotateEvent.ROTATION_FINISHED &&
1747                 e.getEventType() != ScrollEvent.SCROLL_FINISHED &&
1748                 !e.isInertia()) {
1749             gesture.sceneCoords = new Point2D(e.getSceneX(), e.getSceneY());
1750             gesture.screenCoords = new Point2D(e.getScreenX(), e.getScreenY());
1751         }
1752 
1753         if (pickedTarget != null) {
1754             Event.fireEvent(pickedTarget, e);
1755         }
1756 
1757         if (e.getEventType() == ZoomEvent.ZOOM_FINISHED ||
1758                 e.getEventType() == RotateEvent.ROTATION_FINISHED ||
1759                 e.getEventType() == ScrollEvent.SCROLL_FINISHED) {
1760             gesture.finished = true;
1761         }
1762     }
1763 
1764     private void processTouchEvent(TouchEvent e, TouchPoint[] touchPoints) {
1765         inMousePick = true;
1766         touchEventSetId++;
1767 
1768         List<TouchPoint> touchList = Arrays.asList(touchPoints);
1769 
1770         // fire all the events
1771         for (TouchPoint tp : touchPoints) {
1772             if (tp.getTarget() != null) {
1773                 EventType<TouchEvent> type = null;
1774                 switch (tp.getState()) {
1775                     case MOVED:
1776                         type = TouchEvent.TOUCH_MOVED;
1777                         break;
1778                     case PRESSED:
1779                         type = TouchEvent.TOUCH_PRESSED;
1780                         break;
1781                     case RELEASED:
1782                         type = TouchEvent.TOUCH_RELEASED;
1783                         break;
1784                     case STATIONARY:
1785                         type = TouchEvent.TOUCH_STATIONARY;
1786                         break;
1787                 }
1788 
1789                 for (TouchPoint t : touchPoints) {
1790                     t.impl_reset();
1791                 }
1792 
1793                 TouchEvent te = new TouchEvent(type, tp, touchList,
1794                         touchEventSetId, e.isShiftDown(), e.isControlDown(),
1795                         e.isAltDown(), e.isMetaDown());
1796 
1797                 Event.fireEvent(tp.getTarget(), te);
1798             }
1799         }
1800 
1801         // process grabbing
1802         for (TouchPoint tp : touchPoints) {
1803             EventTarget grabbed = tp.getGrabbed();
1804             if (grabbed != null) {
1805                 touchTargets.put(tp.getId(), grabbed);
1806             };
1807 
1808             if (grabbed == null || tp.getState() == TouchPoint.State.RELEASED) {
1809                 touchTargets.remove(tp.getId());
1810             }
1811         }
1812 
1813         inMousePick = false;
1814     }
1815 
1816     /**
1817      * Note: The only user of this method is in unit test: PickAndContainTest.
1818      */
1819     Node test_pick(double x, double y) {
1820         inMousePick = true;
1821         PickResult result = mouseHandler.pickNode(new PickRay(x, y, 1,
1822                 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY));
1823         inMousePick = false;
1824         if (result != null) {
1825             return result.getIntersectedNode();
1826         }
1827         return null;
1828     }
1829 
1830     private PickResult pick(final double x, final double y) {
1831         pick(tmpTargetWrapper, x, y);
1832         return tmpTargetWrapper.getResult();
1833     }
1834 
1835     private boolean isInScene(double x, double y) {
1836         if (x < 0 || y < 0 || x > getWidth() || y > getHeight())  {
1837             return false;
1838         }
1839 
1840         Window w = getWindow();
1841         if (w instanceof Stage
1842                 && ((Stage) w).getStyle() == StageStyle.TRANSPARENT
1843                 && getFill() == null) {
1844             return false;
1845         }
1846 
1847         return true;
1848     }
1849 
1850     private void pick(TargetWrapper target, final double x, final double y) {
1851         final PickRay pickRay = getEffectiveCamera().computePickRay(
1852                 x, y, null);
1853 
1854         final double mag = pickRay.getDirectionNoClone().length();
1855         pickRay.getDirectionNoClone().normalize();
1856         final PickResult res = mouseHandler.pickNode(pickRay);
1857         if (res != null) {
1858             target.setNodeResult(res);
1859         } else {
1860             //TODO: is this the intersection with projection plane?
1861             Vec3d o = pickRay.getOriginNoClone();
1862             Vec3d d = pickRay.getDirectionNoClone();
1863             target.setSceneResult(new PickResult(
1864                     null, new Point3D(
1865                     o.x + mag * d.x,
1866                     o.y + mag * d.y,
1867                     o.z + mag * d.z),
1868                     mag),
1869                     isInScene(x, y) ? this : null);
1870         }
1871     }
1872 
1873     /***************************************************************************
1874      *                                                                         *
1875      * Key Events and Focus Traversal                                          *
1876      *                                                                         *
1877      **************************************************************************/
1878 
1879     /*
1880      * We cannot initialize keyHandler in init because some of the triggers
1881      * access it before the init block.
1882      * No clue why def keyHandler = bind lazy {KeyHandler{scene:this};}
1883      * does not compile.
1884      */
1885     private KeyHandler keyHandler = null;
1886     private KeyHandler getKeyHandler() {
1887         if (keyHandler == null) {
1888             keyHandler = new KeyHandler();
1889         }
1890         return keyHandler;
1891     }
1892     /**
1893      * Set to true if something has happened to the focused node that makes
1894      * it no longer eligible to have the focus.
1895      *
1896      */
1897     private boolean focusDirty = true;
1898 
1899     final void setFocusDirty(boolean value) {
1900         if (!focusDirty) {
1901             Toolkit.getToolkit().requestNextPulse();
1902         }
1903         focusDirty = value;
1904     }
1905 
1906     final boolean isFocusDirty() {
1907         return focusDirty;
1908     }
1909 
1910     /**
1911      * This is a map from focusTraversable nodes within this scene
1912      * to instances of a traversal engine. The traversal engine is
1913      * either the instance for the scene itself, or for a Parent
1914      * nested somewhere within this scene.
1915      *
1916      * This has package access for testing purposes.
1917      */
1918     Map traversalRegistry; // Map<Node,TraversalEngine>
1919 
1920     /**
1921      * Searches up the scene graph for a Parent with a traversal engine.
1922      */
1923     private TraversalEngine lookupTraversalEngine(Node node) {
1924         Parent p = node.getParent();
1925 
1926         while (p != null) {
1927             if (p.getImpl_traversalEngine() != null) {
1928                 return p.getImpl_traversalEngine();
1929             }
1930             p = p.getParent();
1931         }
1932 
1933         // This shouldn't ever occur, since walking up the tree
1934         // should always find the Scene's root, which always has
1935         // a traversal engine. But if for some reason we get here,
1936         // just return the root's traversal engine.
1937 
1938         return getRoot().getImpl_traversalEngine();
1939     }
1940 
1941     /**
1942      * Registers a traversable node with a traversal engine
1943      * on this scene.
1944      */
1945     void registerTraversable(Node n) {
1946         initializeInternalEventDispatcher();
1947 
1948         final TraversalEngine te = lookupTraversalEngine(n);
1949         if (te != null) {
1950             if (traversalRegistry == null) {
1951                 traversalRegistry = new HashMap();
1952             }
1953             traversalRegistry.put(n, te);
1954             te.reg(n);
1955         }
1956     }
1957 
1958     /**
1959      * Unregisters a traversable node from this scene.
1960      */
1961     void unregisterTraversable(Node n) {
1962         final TraversalEngine te = (TraversalEngine) traversalRegistry.remove(n);
1963         if (te != null) {
1964             te.unreg(n);
1965         }
1966     }
1967 
1968     /**
1969      * Traverses focus from the given node in the given direction.
1970      */
1971     void traverse(Node node, Direction dir) {
1972         /*
1973         ** if the registry is null then there are no
1974         ** registered traversable nodes in this scene
1975         */
1976         if (traversalRegistry != null) {
1977             TraversalEngine te = (TraversalEngine) traversalRegistry.get(node);
1978             if (te == null) {
1979                 te = lookupTraversalEngine(node);
1980             }
1981             te.trav(node, dir);
1982         }
1983     }
1984 
1985     /**
1986      * Moves the focus to a reasonable initial location. Called when a scene's
1987      * focus is dirty and there's no current owner, or if the owner has been
1988      * removed from the scene.
1989      */
1990     private void focusInitial() {
1991         getRoot().getImpl_traversalEngine().getTopLeftFocusableNode();
1992     }
1993 
1994     /**
1995      * Moves the focus to a reasonble location "near" the given node.
1996      * Called when the focused node is no longer eligible to have
1997      * the focus because it has become invisible or disabled. This
1998      * function assumes that it is still a member of the same scene.
1999      */
2000     private void focusIneligible(Node node) {
2001         traverse(node, Direction.NEXT);
2002     }
2003 
2004     /**
2005      * @treatAsPrivate implementation detail
2006      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2007      */
2008     // SB-dependency: RT-24668 has been filed to track this
2009     @Deprecated
2010     public void impl_processKeyEvent(KeyEvent e) {
2011         if (dndGesture != null) {
2012             if (!dndGesture.processKey(e)) {
2013                 dndGesture = null;
2014             }
2015         }
2016 
2017         getKeyHandler().process(e);
2018     }
2019 
2020     void requestFocus(Node node) {
2021         getKeyHandler().requestFocus(node);
2022     }
2023 
2024     private Node oldFocusOwner;
2025 
2026     /**
2027       * The scene's current focus owner node. This node's "focused"
2028       * variable might be false if this scene has no window, or if the
2029       * window is inactive (window.focused == false).
2030       * @since JavaFX 2.2
2031       */
2032     private ReadOnlyObjectWrapper<Node> focusOwner = new ReadOnlyObjectWrapper<Node>(this, "focusOwner") {
2033 
2034         @Override
2035         protected void invalidated() {
2036             if (oldFocusOwner != null) {
2037                 ((Node.FocusedProperty) oldFocusOwner.focusedProperty()).store(false);
2038             }
2039             Node value = get();
2040             if (value != null) {
2041                 ((Node.FocusedProperty) value.focusedProperty()).store(keyHandler.windowFocused);
2042                 if (value != oldFocusOwner) {
2043                     value.getScene().impl_enableInputMethodEvents(
2044                             value.getInputMethodRequests() != null
2045                             && value.getOnInputMethodTextChanged() != null);
2046                 }
2047             }
2048             // for the rest of the method we need to update the oldFocusOwner
2049             // and use a local copy of it because the user handlers can cause
2050             // recurrent calls of requestFocus
2051             Node localOldOwner = oldFocusOwner;
2052             oldFocusOwner = value;
2053             if (localOldOwner != null) {
2054                 ((Node.FocusedProperty) localOldOwner.focusedProperty()).notifyListeners();
2055             }
2056             if (value != null) {
2057                 ((Node.FocusedProperty) value.focusedProperty()).notifyListeners();
2058             }
2059             PlatformLogger logger = Logging.getFocusLogger();
2060             if (logger.isLoggable(Level.FINE)) {
2061                 if (value == get()) {
2062                     logger.fine("Changed focus from "
2063                             + localOldOwner + " to " + value);
2064                 } else {
2065                     logger.fine("Changing focus from "
2066                             + localOldOwner + " to " + value
2067                             + " canceled by nested requestFocus");
2068                 }
2069             }
2070         }
2071     };
2072 
2073     public final Node getFocusOwner() {
2074         return focusOwner.get();
2075     }
2076 
2077     public final ReadOnlyObjectProperty<Node> focusOwnerProperty() {
2078         return focusOwner.getReadOnlyProperty();
2079     }
2080 
2081     // For testing.
2082     void focusCleanup() {
2083         scenePulseListener.focusCleanup();
2084     }
2085 
2086     private void processInputMethodEvent(InputMethodEvent e) {
2087         Node node = getFocusOwner();
2088         if (node != null) {
2089             node.fireEvent(e);
2090         }
2091     }
2092 
2093     /**
2094      * @treatAsPrivate implementation detail
2095      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2096      */
2097     @Deprecated
2098     public void impl_enableInputMethodEvents(boolean enable) {
2099        if (impl_peer != null) {
2100            impl_peer.enableInputMethodEvents(enable);
2101        }
2102     }
2103 
2104     /**
2105      * Returns true if this scene is quiescent, i.e. it has no activity
2106      * pending on it such as CSS processing or layout requests.
2107      *
2108      * Intended to be used for tests only
2109      *
2110      * @return boolean indicating whether the scene is quiescent
2111      */
2112     boolean isQuiescent() {
2113         final Parent r = getRoot();
2114         return !isFocusDirty()
2115                && (r == null || (r.cssFlag == CssFlags.CLEAN &&
2116                 r.layoutFlag == LayoutFlags.CLEAN));
2117     }
2118 
2119     /**
2120      * A listener for pulses, used for testing. If non-null, this is called at
2121      * the very end of ScenePulseListener.pulse().
2122      *
2123      * Intended to be used for tests only
2124      */
2125     Runnable testPulseListener = null;
2126 
2127     /**
2128      * Set the specified dirty bit and mark the peer as dirty
2129      */
2130     private void markDirty(DirtyBits dirtyBit) {
2131         setDirty(dirtyBit);
2132         if (impl_peer != null) {
2133             Toolkit.getToolkit().requestNextPulse();
2134         }
2135     }
2136 
2137     /**
2138      * Set the specified dirty bit
2139      */
2140     private void setDirty(DirtyBits dirtyBit) {
2141         dirtyBits |= dirtyBit.getMask();
2142     }
2143 
2144     /**
2145      * Test the specified dirty bit
2146      */
2147     private boolean isDirty(DirtyBits dirtyBit) {
2148         return ((dirtyBits & dirtyBit.getMask()) != 0);
2149     }
2150 
2151     /**
2152      * Test whether the dirty bits are empty
2153      */
2154     private boolean isDirtyEmpty() {
2155         return dirtyBits == 0;
2156     }
2157 
2158     /**
2159      * Clear all dirty bits
2160      */
2161     private void clearDirty() {
2162         dirtyBits = 0;
2163     }
2164 
2165     private enum DirtyBits {
2166         FILL_DIRTY,
2167         ROOT_DIRTY,
2168         CAMERA_DIRTY,
2169         LIGHTS_DIRTY,
2170         CURSOR_DIRTY;
2171 
2172         private int mask;
2173 
2174         private DirtyBits() {
2175             mask = 1 << ordinal();
2176         }
2177 
2178         public final int getMask() { return mask; }
2179     }
2180 
2181     private List<LightBase> lights = new ArrayList<>();
2182 
2183     // @param light must not be null
2184     final void addLight(LightBase light) {
2185         if (!lights.contains(light)) {
2186             lights.add(light);
2187             markDirty(DirtyBits.LIGHTS_DIRTY);
2188         }
2189     }
2190 
2191     final void removeLight(LightBase light) {
2192         if (lights.remove(light)) {
2193             markDirty(DirtyBits.LIGHTS_DIRTY);
2194         }
2195     }
2196 
2197     /**
2198      * PG Light synchronizer.
2199      */
2200     private void syncLights() {
2201         if (!isDirty(DirtyBits.LIGHTS_DIRTY)) {
2202             return;
2203         }
2204         inSynchronizer = true;
2205         NGLightBase peerLights[] = impl_peer.getLights();
2206         if (!lights.isEmpty() || (peerLights != null)) {
2207             if (lights.isEmpty()) {
2208                 impl_peer.setLights(null);
2209             } else {
2210                 if (peerLights == null || peerLights.length < lights.size()) {
2211                     peerLights = new NGLightBase[lights.size()];
2212                 }
2213                 int i = 0;
2214                 for (; i < lights.size(); i++) {
2215                     peerLights[i] = lights.get(i).impl_getPeer();
2216                 }
2217                 // Clear the rest of the list
2218                 while (i < peerLights.length && peerLights[i] != null) {
2219                     peerLights[i++] = null;
2220                 }
2221                 impl_peer.setLights(peerLights);
2222             }
2223         }
2224         inSynchronizer = false;
2225     }
2226 
2227     //INNER CLASSES
2228 
2229     /*******************************************************************************
2230      *                                                                             *
2231      * Scene Pulse Listener                                                        *
2232      *                                                                             *
2233      ******************************************************************************/
2234 
2235     class ScenePulseListener implements TKPulseListener {
2236 
2237         private boolean firstPulse = true;
2238 
2239         /**
2240          * PG synchronizer. Called once per frame from the pulse listener.
2241          * This function calls the synchronizePGNode method on each node in
2242          * the dirty list.
2243          */
2244         private void synchronizeSceneNodes() {
2245             Toolkit.getToolkit().checkFxUserThread();
2246 
2247             Scene.inSynchronizer = true;
2248 
2249             // if dirtyNodes is null then that means this Scene has not yet been
2250             // synchronized, and so we will simply synchronize every node in the
2251             // scene and then create the dirty nodes array list
2252             if (Scene.this.dirtyNodes == null) {
2253                 // must do this recursively
2254                 syncAll(getRoot());
2255                 dirtyNodes = new Node[MIN_DIRTY_CAPACITY];
2256 
2257             } else {
2258                 // This is not the first time this scene has been synchronized,
2259                 // so we will only synchronize those nodes that need it
2260                 for (int i = 0 ; i < dirtyNodesSize; ++i) {
2261                     Node node = dirtyNodes[i];
2262                     dirtyNodes[i] = null;
2263                     if (node.getScene() == Scene.this) {
2264                             node.impl_syncPeer();
2265                         }
2266                     }
2267                 dirtyNodesSize = 0;
2268             }
2269 
2270             Scene.inSynchronizer = false;
2271         }
2272 
2273         /**
2274          * Recursive function for synchronizing every node in the scenegraph.
2275          * The return value is the number of nodes in the graph.
2276          */
2277         private int syncAll(Node node) {
2278             node.impl_syncPeer();
2279             int size = 1;
2280             if (node instanceof Parent) {
2281                 Parent p = (Parent) node;
2282                 final int childrenCount = p.getChildren().size();
2283 
2284                 for (int i = 0; i < childrenCount; i++) {
2285                     Node n = p.getChildren().get(i);
2286                     if (n != null) {
2287                         size += syncAll(n);
2288                     }
2289                 }
2290             } else if (node instanceof SubScene) {
2291                 SubScene subScene = (SubScene)node;
2292                 size += syncAll(subScene.getRoot());
2293             }
2294             if (node.getClip() != null) {
2295                 size += syncAll(node.getClip());
2296             }
2297 
2298             return size;
2299         }
2300 
2301         private void synchronizeSceneProperties() {
2302             inSynchronizer = true;
2303             if (isDirty(DirtyBits.ROOT_DIRTY)) {
2304                 impl_peer.setRoot(getRoot().impl_getPeer());
2305             }
2306 
2307             if (isDirty(DirtyBits.FILL_DIRTY)) {
2308                 Toolkit tk = Toolkit.getToolkit();
2309                 impl_peer.setFillPaint(getFill() == null ? null : tk.getPaint(getFill()));
2310             }
2311 
2312             // new camera was set on the scene or old camera changed
2313             final Camera cam = getEffectiveCamera();
2314             if (isDirty(DirtyBits.CAMERA_DIRTY)) {
2315                 cam.impl_updatePeer();
2316                 impl_peer.setCamera((NGCamera) cam.impl_getPeer());
2317             }
2318 
2319             clearDirty();
2320             inSynchronizer = false;
2321         }
2322 
2323         /**
2324          * The focus is considered dirty if something happened to
2325          * the scene graph that may require the focus to be moved.
2326          * This must handle cases where (a) the focus owner may have
2327          * become ineligible to have the focus, and (b) where the focus
2328          * owner is null and a node may have become traversable and eligible.
2329          */
2330         private void focusCleanup() {
2331             if (Scene.this.isFocusDirty()) {
2332                 final Node oldOwner = Scene.this.getFocusOwner();
2333                 if (oldOwner == null) {
2334                     Scene.this.focusInitial();
2335                 } else if (oldOwner.getScene() != Scene.this) {
2336                     Scene.this.requestFocus(null);
2337                     Scene.this.focusInitial();
2338                 } else if (!oldOwner.isCanReceiveFocus()) {
2339                     Scene.this.requestFocus(null);
2340                     Scene.this.focusIneligible(oldOwner);
2341                 }
2342                 Scene.this.setFocusDirty(false);
2343             }
2344         }
2345 
2346         @Override
2347         public void pulse() {
2348             if (Scene.this.tracker != null) {
2349                 Scene.this.tracker.pulse();
2350             }
2351             if (firstPulse) {
2352                 PerformanceTracker.logEvent("Scene - first repaint");
2353             }
2354 
2355             focusCleanup();
2356 
2357             if (PULSE_LOGGING_ENABLED) {
2358                 long start = System.currentTimeMillis();
2359                 Scene.this.doCSSPass();
2360                 PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "CSS Pass");
2361 
2362                 start = System.currentTimeMillis();
2363                 Scene.this.doLayoutPass();
2364                 PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Layout Pass");
2365             } else {
2366                 Scene.this.doCSSPass();
2367                 Scene.this.doLayoutPass();
2368             }
2369 
2370             boolean dirty = dirtyNodes == null || dirtyNodesSize != 0 || !isDirtyEmpty();
2371             if (dirty) {
2372                 getRoot().updateBounds();
2373                 if (impl_peer != null) {
2374                     try {
2375                         long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
2376                         impl_peer.waitForRenderingToComplete();
2377                         impl_peer.waitForSynchronization();
2378                         if (PULSE_LOGGING_ENABLED) {
2379                             PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Waiting for previous rendering");
2380                         }
2381                         start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
2382                         // synchronize scene properties
2383                         syncLights();
2384                         synchronizeSceneProperties();
2385                         // Run the synchronizer
2386                         synchronizeSceneNodes();
2387                         Scene.this.mouseHandler.pulse();
2388                         // Tell the scene peer that it needs to repaint
2389                         impl_peer.markDirty();
2390                         if (PULSE_LOGGING_ENABLED) {
2391                             PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Copy state to render graph");
2392                         }
2393                     } finally {
2394                         impl_peer.releaseSynchronization(true);
2395                     }
2396                 } else {
2397                     long start = PULSE_LOGGING_ENABLED ? System.currentTimeMillis() : 0;
2398                     synchronizeSceneProperties();
2399                     synchronizeSceneNodes();
2400                     Scene.this.mouseHandler.pulse();
2401                     if (PULSE_LOGGING_ENABLED) {
2402                         PULSE_LOGGER.fxMessage(start, System.currentTimeMillis(), "Synchronize with null peer");
2403                     }
2404 
2405                 }
2406 
2407                 if (Scene.this.getRoot().cssFlag != CssFlags.CLEAN) {
2408                     Scene.this.getRoot().impl_markDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
2409                 }
2410             }
2411 
2412             // required for image cursor created from animated image
2413             Scene.this.mouseHandler.updateCursorFrame();
2414 
2415             if (firstPulse) {
2416                 if (PerformanceTracker.isLoggingEnabled()) {
2417                     PerformanceTracker.logEvent("Scene - first repaint - layout complete");
2418                     if (PrismSettings.perfLogFirstPaintFlush) {
2419                         PerformanceTracker.outputLog();
2420                     }
2421                     if (PrismSettings.perfLogFirstPaintExit) {
2422                         System.exit(0);
2423                     }
2424                 }
2425                 firstPulse = false;
2426             }
2427 
2428             if (testPulseListener != null) {
2429                 testPulseListener.run();
2430             }
2431         }
2432     }
2433 
2434     /*******************************************************************************
2435      *                                                                             *
2436      * Scene Peer Listener                                                         *
2437      *                                                                             *
2438      ******************************************************************************/
2439 
2440     class ScenePeerListener implements TKSceneListener {
2441         @Override
2442         public void changedLocation(float x, float y) {
2443             if (x != Scene.this.getX()) {
2444                 Scene.this.setX(x);
2445             }
2446             if (y != Scene.this.getY()) {
2447                 Scene.this.setY(y);
2448             }
2449         }
2450 
2451         @Override
2452         public void changedSize(float w, float h) {
2453             if (w != Scene.this.getWidth()) Scene.this.setWidth(w);
2454             if (h != Scene.this.getHeight()) Scene.this.setHeight(h);
2455         }
2456 
2457         @Override
2458         public void mouseEvent(EventType<MouseEvent> type, double x, double y, double screenX, double screenY,
2459                                MouseButton button, boolean popupTrigger, boolean synthesized,
2460                                boolean shiftDown, boolean controlDown, boolean altDown, boolean metaDown,
2461                                boolean primaryDown, boolean middleDown, boolean secondaryDown)
2462         {
2463             MouseEvent mouseEvent = new MouseEvent(type, x, y, screenX, screenY, button,
2464                     0, // click count will be adjusted by clickGenerator later anyway
2465                     shiftDown, controlDown, altDown, metaDown,
2466                     primaryDown, middleDown, secondaryDown, synthesized, popupTrigger, false, null);
2467             impl_processMouseEvent(mouseEvent);
2468         }
2469 
2470 
2471         @Override
2472         public void keyEvent(KeyEvent keyEvent)
2473         {
2474             impl_processKeyEvent(keyEvent);
2475         }
2476 
2477         @Override
2478         public void inputMethodEvent(EventType<InputMethodEvent> type,
2479                                      ObservableList<InputMethodTextRun> composed, String committed,
2480                                      int caretPosition)
2481         {
2482             InputMethodEvent inputMethodEvent = new InputMethodEvent(
2483                type, composed, committed, caretPosition);
2484             processInputMethodEvent(inputMethodEvent);
2485         }
2486 
2487         public void menuEvent(double x, double y, double xAbs, double yAbs,
2488                 boolean isKeyboardTrigger) {
2489             Scene.this.processMenuEvent(x, y, xAbs,yAbs, isKeyboardTrigger);
2490         }
2491 
2492         @Override
2493         public void scrollEvent(
2494                 EventType<ScrollEvent> eventType,
2495                 double scrollX, double scrollY,
2496                 double totalScrollX, double totalScrollY,
2497                 double xMultiplier, double yMultiplier,
2498                 int touchCount,
2499                 int scrollTextX, int scrollTextY,
2500                 int defaultTextX, int defaultTextY,
2501                 double x, double y, double screenX, double screenY,
2502                 boolean _shiftDown, boolean _controlDown,
2503                 boolean _altDown, boolean _metaDown,
2504                 boolean _direct, boolean _inertia) {
2505 
2506             ScrollEvent.HorizontalTextScrollUnits xUnits = scrollTextX > 0 ?
2507                     ScrollEvent.HorizontalTextScrollUnits.CHARACTERS :
2508                     ScrollEvent.HorizontalTextScrollUnits.NONE;
2509 
2510             double xText = scrollTextX < 0 ? 0 : scrollTextX * scrollX;
2511 
2512             ScrollEvent.VerticalTextScrollUnits yUnits = scrollTextY > 0 ?
2513                     ScrollEvent.VerticalTextScrollUnits.LINES :
2514                     (scrollTextY < 0 ?
2515                         ScrollEvent.VerticalTextScrollUnits.PAGES :
2516                         ScrollEvent.VerticalTextScrollUnits.NONE);
2517 
2518             double yText = scrollTextY < 0 ? scrollY : scrollTextY * scrollY;
2519 
2520             xMultiplier = defaultTextX > 0 && scrollTextX >= 0
2521                     ? Math.round(xMultiplier * scrollTextX / defaultTextX)
2522                     : xMultiplier;
2523 
2524             yMultiplier = defaultTextY > 0 && scrollTextY >= 0
2525                     ? Math.round(yMultiplier * scrollTextY / defaultTextY)
2526                     : yMultiplier;
2527 
2528             if (eventType == ScrollEvent.SCROLL_FINISHED) {
2529                 x = scrollGesture.sceneCoords.getX();
2530                 y = scrollGesture.sceneCoords.getY();
2531                 screenX = scrollGesture.screenCoords.getX();
2532                 screenY = scrollGesture.screenCoords.getY();
2533             } else if (Double.isNaN(x) || Double.isNaN(y) ||
2534                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2535                 if (cursorScenePos == null || cursorScreenPos == null) {
2536                     return;
2537                 }
2538                 x = cursorScenePos.getX();
2539                 y = cursorScenePos.getY();
2540                 screenX = cursorScreenPos.getX();
2541                 screenY = cursorScreenPos.getY();
2542             }
2543 
2544             inMousePick = true;
2545             Scene.this.processGestureEvent(new ScrollEvent(
2546                     eventType,
2547                     x, y, screenX, screenY,
2548                     _shiftDown, _controlDown, _altDown, _metaDown,
2549                     _direct, _inertia,
2550                     scrollX * xMultiplier, scrollY * yMultiplier,
2551                     totalScrollX * xMultiplier, totalScrollY * yMultiplier,
2552                     xMultiplier, yMultiplier,
2553                     xUnits, xText, yUnits, yText, touchCount, pick(x, y)),
2554                     scrollGesture);
2555             inMousePick = false;
2556         }
2557 
2558         @Override
2559         public void zoomEvent(
2560                 EventType<ZoomEvent> eventType,
2561                 double zoomFactor, double totalZoomFactor,
2562                 double x, double y, double screenX, double screenY,
2563                 boolean _shiftDown, boolean _controlDown,
2564                 boolean _altDown, boolean _metaDown,
2565                 boolean _direct, boolean _inertia) {
2566 
2567             if (eventType == ZoomEvent.ZOOM_FINISHED) {
2568                 x = zoomGesture.sceneCoords.getX();
2569                 y = zoomGesture.sceneCoords.getY();
2570                 screenX = zoomGesture.screenCoords.getX();
2571                 screenY = zoomGesture.screenCoords.getY();
2572             } else if (Double.isNaN(x) || Double.isNaN(y) ||
2573                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2574                 if (cursorScenePos == null || cursorScreenPos == null) {
2575                     return;
2576                 }
2577                 x = cursorScenePos.getX();
2578                 y = cursorScenePos.getY();
2579                 screenX = cursorScreenPos.getX();
2580                 screenY = cursorScreenPos.getY();
2581             }
2582 
2583             inMousePick = true;
2584             Scene.this.processGestureEvent(new ZoomEvent(eventType,
2585                     x, y, screenX, screenY,
2586                     _shiftDown, _controlDown, _altDown, _metaDown,
2587                     _direct, _inertia,
2588                     zoomFactor, totalZoomFactor, pick(x, y)),
2589                     zoomGesture);
2590             inMousePick = false;
2591         }
2592 
2593         @Override
2594         public void rotateEvent(
2595                 EventType<RotateEvent> eventType, double angle, double totalAngle,
2596                 double x, double y, double screenX, double screenY,
2597                 boolean _shiftDown, boolean _controlDown,
2598                 boolean _altDown, boolean _metaDown,
2599                 boolean _direct, boolean _inertia) {
2600 
2601             if (eventType == RotateEvent.ROTATION_FINISHED) {
2602                 x = rotateGesture.sceneCoords.getX();
2603                 y = rotateGesture.sceneCoords.getY();
2604                 screenX = rotateGesture.screenCoords.getX();
2605                 screenY = rotateGesture.screenCoords.getY();
2606             } else if (Double.isNaN(x) || Double.isNaN(y) ||
2607                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2608                 if (cursorScenePos == null || cursorScreenPos == null) {
2609                     return;
2610                 }
2611                 x = cursorScenePos.getX();
2612                 y = cursorScenePos.getY();
2613                 screenX = cursorScreenPos.getX();
2614                 screenY = cursorScreenPos.getY();
2615             }
2616 
2617             inMousePick = true;
2618             Scene.this.processGestureEvent(new RotateEvent(
2619                     eventType, x, y, screenX, screenY,
2620                     _shiftDown, _controlDown, _altDown, _metaDown,
2621                     _direct, _inertia, angle, totalAngle, pick(x, y)),
2622                     rotateGesture);
2623             inMousePick = false;
2624 
2625         }
2626 
2627         @Override
2628         public void swipeEvent(
2629                 EventType<SwipeEvent> eventType, int touchCount,
2630                 double x, double y, double screenX, double screenY,
2631                 boolean _shiftDown, boolean _controlDown,
2632                 boolean _altDown, boolean _metaDown, boolean _direct) {
2633 
2634             if (Double.isNaN(x) || Double.isNaN(y) ||
2635                     Double.isNaN(screenX) || Double.isNaN(screenY)) {
2636                 if (cursorScenePos == null || cursorScreenPos == null) {
2637                     return;
2638                 }
2639                 x = cursorScenePos.getX();
2640                 y = cursorScenePos.getY();
2641                 screenX = cursorScreenPos.getX();
2642                 screenY = cursorScreenPos.getY();
2643             }
2644 
2645             inMousePick = true;
2646             Scene.this.processGestureEvent(new SwipeEvent(
2647                     eventType, x, y, screenX, screenY,
2648                     _shiftDown, _controlDown, _altDown, _metaDown, _direct,
2649                     touchCount, pick(x, y)),
2650                     swipeGesture);
2651             inMousePick = false;
2652         }
2653 
2654         @Override
2655         public void touchEventBegin(
2656                 long time, int touchCount, boolean isDirect,
2657                 boolean _shiftDown, boolean _controlDown,
2658                 boolean _altDown, boolean _metaDown) {
2659 
2660             if (!isDirect) {
2661                 nextTouchEvent = null;
2662                 return;
2663             }
2664             nextTouchEvent = new TouchEvent(
2665                     TouchEvent.ANY, null, null, 0,
2666                     _shiftDown, _controlDown, _altDown, _metaDown);
2667             if (touchPoints == null || touchPoints.length != touchCount) {
2668                 touchPoints = new TouchPoint[touchCount];
2669             }
2670             touchPointIndex = 0;
2671         }
2672 
2673         @Override
2674         public void touchEventNext(
2675                 TouchPoint.State state, long touchId,
2676                 int x, int y, int xAbs, int yAbs) {
2677 
2678             inMousePick = true;
2679             if (nextTouchEvent == null) {
2680                 // ignore indirect touch events
2681                 return;
2682             }
2683             touchPointIndex++;
2684             int id = (state == TouchPoint.State.PRESSED
2685                     ? touchMap.add(touchId) :  touchMap.get(touchId));
2686             if (state == TouchPoint.State.RELEASED) {
2687                 touchMap.remove(touchId);
2688             }
2689             int order = touchMap.getOrder(id);
2690 
2691             if (order >= touchPoints.length) {
2692                 throw new RuntimeException("Too many touch points reported");
2693             }
2694 
2695             // pick target
2696             boolean isGrabbed = false;
2697             PickResult pickRes = pick(x, y);
2698             EventTarget pickedTarget = touchTargets.get(id);
2699             if (pickedTarget == null) {
2700                 pickedTarget = pickRes.getIntersectedNode();
2701                 if (pickedTarget == null) {
2702                     pickedTarget = Scene.this;
2703                 }
2704             } else {
2705                 isGrabbed = true;
2706             }
2707 
2708             TouchPoint tp = new TouchPoint(id, state,
2709                     x, y, xAbs, yAbs, pickedTarget, pickRes);
2710 
2711             touchPoints[order] = tp;
2712 
2713             if (isGrabbed) {
2714                 tp.grab(pickedTarget);
2715             }
2716             if (tp.getState() == TouchPoint.State.PRESSED) {
2717                 tp.grab(pickedTarget);
2718                 touchTargets.put(tp.getId(), pickedTarget);
2719             } else if (tp.getState() == TouchPoint.State.RELEASED) {
2720                 touchTargets.remove(tp.getId());
2721             }
2722             inMousePick = false;
2723         }
2724 
2725         @Override
2726         public void touchEventEnd() {
2727             if (nextTouchEvent == null) {
2728                 // ignore indirect touch events
2729                 return;
2730             }
2731 
2732             if (touchPointIndex != touchPoints.length) {
2733                 throw new RuntimeException("Wrong number of touch points reported");
2734             }
2735 
2736             Scene.this.processTouchEvent(nextTouchEvent, touchPoints);
2737 
2738             if (touchMap.cleanup()) {
2739                 // gesture finished
2740                 touchEventSetId = 0;
2741             }
2742         }
2743     }
2744 
2745     private class ScenePeerPaintListener implements TKScenePaintListener {
2746         @Override
2747         public void frameRendered() {
2748             // must use tracker with synchronization since this method is called on render thread
2749             synchronized (trackerMonitor) {
2750                 if (Scene.this.tracker != null) {
2751                     Scene.this.tracker.frameRendered();
2752                 }
2753             }
2754         }
2755     }
2756 
2757     /*******************************************************************************
2758      *                                                                             *
2759      * Drag and Drop                                                               *
2760      *                                                                             *
2761      ******************************************************************************/
2762 
2763     class DropTargetListener implements TKDropTargetListener {
2764 
2765         /*
2766          * This function is called when an drag operation enters a valid drop target.
2767          * This may be from either an internal or external dnd operation.
2768          */
2769         @Override
2770         public TransferMode dragEnter(double x, double y, double screenX, double screenY,
2771                                       TransferMode transferMode, TKClipboard dragboard)
2772         {
2773             if (dndGesture == null) {
2774                 dndGesture = new DnDGesture();
2775             }
2776             Dragboard db = Dragboard.impl_createDragboard(dragboard);
2777             dndGesture.dragboard = db;
2778             DragEvent dragEvent =
2779                     new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
2780                             transferMode, null, null, pick(x, y));
2781             return dndGesture.processTargetEnterOver(dragEvent);
2782         }
2783 
2784         @Override
2785         public TransferMode dragOver(double x, double y, double screenX, double screenY,
2786                                      TransferMode transferMode)
2787         {
2788             if (Scene.this.dndGesture == null) {
2789                 System.err.println("GOT A dragOver when dndGesture is null!");
2790                 return null;
2791             } else {
2792                 if (dndGesture.dragboard == null) {
2793                     throw new RuntimeException("dndGesture.dragboard is null in dragOver");
2794                 }
2795                 DragEvent dragEvent =
2796                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
2797                                 transferMode, null, null, pick(x, y));
2798                 return dndGesture.processTargetEnterOver(dragEvent);
2799             }
2800         }
2801 
2802         @Override
2803         public void dragExit(double x, double y, double screenX, double screenY) {
2804             if (dndGesture == null) {
2805                 System.err.println("GOT A dragExit when dndGesture is null!");
2806             } else {
2807                 if (dndGesture.dragboard == null) {
2808                     throw new RuntimeException("dndGesture.dragboard is null in dragExit");
2809                 }
2810                 DragEvent dragEvent =
2811                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
2812                                 null, null, null, pick(x, y));
2813                 dndGesture.processTargetExit(dragEvent);
2814                 if (dndGesture.source == null) {
2815                     dndGesture.dragboard = null;
2816                     dndGesture = null;
2817                 }
2818             }
2819         }
2820 
2821 
2822         @Override
2823         public TransferMode drop(double x, double y, double screenX, double screenY,
2824                                   TransferMode transferMode)
2825         {
2826             if (dndGesture == null) {
2827                 System.err.println("GOT A drop when dndGesture is null!");
2828                 return null;
2829             } else {
2830                 if (dndGesture.dragboard == null) {
2831                     throw new RuntimeException("dndGesture.dragboard is null in dragDrop");
2832                 }
2833                 DragEvent dragEvent =
2834                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
2835                                 transferMode, null, null, pick(x, y));
2836                 TransferMode tm = dndGesture.processTargetDrop(dragEvent);
2837                 if (dndGesture.source == null) {
2838                     dndGesture.dragboard = null;
2839                     dndGesture = null;
2840                 }
2841                 return tm;
2842             }
2843         }
2844     }
2845 
2846     class DragGestureListener implements TKDragGestureListener {
2847 
2848        @Override
2849        public void dragGestureRecognized(double x, double y, double screenX, double screenY,
2850                                          int button, TKClipboard dragboard)
2851        {
2852            Dragboard db = Dragboard.impl_createDragboard(dragboard);
2853            dndGesture = new DnDGesture();
2854            dndGesture.dragboard = db;
2855            // TODO: support mouse buttons in DragEvent
2856            DragEvent dragEvent = new DragEvent(DragEvent.ANY, db, x, y, screenX, screenY,
2857                    null, null, null, pick(x, y));
2858            dndGesture.processRecognized(dragEvent);
2859            dndGesture = null;
2860         }
2861     }
2862 
2863     /**
2864      * A Drag and Drop gesture has a lifespan that lasts from mouse
2865      * PRESSED event to mouse RELEASED event.
2866      */
2867     class DnDGesture {
2868         private final double hysteresisSizeX =
2869                 Toolkit.getToolkit().getMultiClickMaxX();
2870         private final double hysteresisSizeY =
2871                 Toolkit.getToolkit().getMultiClickMaxY();
2872 
2873         private EventTarget source = null;
2874         private Set<TransferMode> sourceTransferModes = null;
2875         private TransferMode acceptedTransferMode = null;
2876         private Dragboard dragboard = null;
2877         private EventTarget potentialTarget = null;
2878         private EventTarget target = null;
2879         private DragDetectedState dragDetected = DragDetectedState.NOT_YET;
2880         private double pressedX;
2881         private double pressedY;
2882         private List<EventTarget> currentTargets = new ArrayList<EventTarget>();
2883         private List<EventTarget> newTargets = new ArrayList<EventTarget>();
2884         private EventTarget fullPDRSource = null;
2885 
2886         /**
2887          * Fires event on a given target or on scene if the node is null
2888          */
2889         private void fireEvent(EventTarget target, Event e) {
2890             if (target != null) {
2891                 Event.fireEvent(target, e);
2892             }
2893         }
2894 
2895         /**
2896          * Called when DRAG_DETECTED event is going to be processed by
2897          * application
2898          */
2899         private void processingDragDetected() {
2900             dragDetected = DragDetectedState.PROCESSING;
2901         }
2902 
2903         /**
2904          * Called after DRAG_DETECTED event has been processed by application
2905          */
2906         private void dragDetectedProcessed() {
2907             dragDetected = DragDetectedState.DONE;
2908             final boolean hasContent = (dragboard != null) && (dragboard.impl_contentPut());
2909             if (hasContent) {
2910                 /* start DnD */
2911                 Toolkit.getToolkit().startDrag(Scene.this.impl_peer,
2912                                                 sourceTransferModes,
2913                                                 new DragSourceListener(),
2914                                                 dragboard);
2915             } else if (fullPDRSource != null) {
2916                 /* start PDR */
2917                 Scene.this.mouseHandler.enterFullPDR(fullPDRSource);
2918             }
2919 
2920             fullPDRSource = null;
2921         }
2922 
2923         /**
2924          * Sets the default dragDetect value
2925          */
2926         private void processDragDetection(MouseEvent mouseEvent) {
2927 
2928             if (dragDetected != DragDetectedState.NOT_YET) {
2929                 mouseEvent.setDragDetect(false);
2930                 return;
2931             }
2932 
2933             if (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED) {
2934                 pressedX = mouseEvent.getSceneX();
2935                 pressedY = mouseEvent.getSceneY();
2936 
2937                 mouseEvent.setDragDetect(false);
2938 
2939             } else if (mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) {
2940 
2941                 double deltaX = Math.abs(mouseEvent.getSceneX() - pressedX);
2942                 double deltaY = Math.abs(mouseEvent.getSceneY() - pressedY);
2943                 mouseEvent.setDragDetect(deltaX > hysteresisSizeX ||
2944                                          deltaY > hysteresisSizeY);
2945 
2946             }
2947         }
2948 
2949         /**
2950          * This function is useful for drag gesture recognition from
2951          * within this Scene (as opposed to in the TK implementation... by the platform)
2952          */
2953         private boolean process(MouseEvent mouseEvent, EventTarget target) {
2954             boolean continueProcessing = true;
2955             if (!PLATFORM_DRAG_GESTURE_INITIATION) {
2956 
2957                 if (dragDetected != DragDetectedState.DONE &&
2958                         (mouseEvent.getEventType() == MouseEvent.MOUSE_PRESSED ||
2959                         mouseEvent.getEventType() == MouseEvent.MOUSE_DRAGGED) &&
2960                         mouseEvent.isDragDetect()) {
2961 
2962                     processingDragDetected();
2963 
2964                     if (target != null) {
2965                         final MouseEvent detectedEvent = mouseEvent.copyFor(
2966                                 mouseEvent.getSource(), target,
2967                                 MouseEvent.DRAG_DETECTED);
2968 
2969                         fireEvent(target, detectedEvent);
2970                     }
2971 
2972                     dragDetectedProcessed();
2973                 }
2974 
2975                 if (mouseEvent.getEventType() == MouseEvent.MOUSE_RELEASED) {
2976                     continueProcessing = false;
2977                 }
2978             }
2979             return continueProcessing;
2980         }
2981 
2982         /*
2983          * Called when a drag source is recognized. This occurs at the very start of
2984          * the publicly visible drag and drop API, as it is responsible for calling
2985          * the Node.onDragSourceRecognized function.
2986          */
2987         private boolean processRecognized(DragEvent de) {
2988             MouseEvent me = new MouseEvent(
2989                     MouseEvent.DRAG_DETECTED, de.getX(), de.getY(),
2990                     de.getSceneX(), de.getScreenY(), MouseButton.PRIMARY, 1,
2991                     false, false, false, false, false, true, false, false, false,
2992                     false, de.getPickResult());
2993 
2994             processingDragDetected();
2995 
2996             final EventTarget target = de.getPickResult().getIntersectedNode();
2997             fireEvent(target != null ? target : Scene.this, me);
2998 
2999             dragDetectedProcessed();
3000 
3001             final boolean hasContent = dragboard != null
3002                     && !dragboard.getContentTypes().isEmpty();
3003             return hasContent;
3004         }
3005 
3006         private void processDropEnd(DragEvent de) {
3007             if (source == null) {
3008                 System.out.println("Scene.DnDGesture.processDropEnd() - UNEXPECTD - source is NULL");
3009                 return;
3010             }
3011 
3012             de = new DragEvent(de.getSource(), source, DragEvent.DRAG_DONE,
3013                     de.getDragboard(), de.getSceneX(), de.getSceneY(),
3014                     de.getScreenX(), de.getScreenY(),
3015                     de.getTransferMode(), source, target, de.getPickResult());
3016 
3017             Event.fireEvent(source, de);
3018 
3019             tmpTargetWrapper.clear();
3020             handleExitEnter(de, tmpTargetWrapper);
3021 
3022             // at this point the drag and drop operation is completely over, so we
3023             // can tell the toolkit that it can clean up if needs be.
3024             Toolkit.getToolkit().stopDrag(dragboard);
3025         }
3026 
3027         private TransferMode processTargetEnterOver(DragEvent de) {
3028             pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY());
3029             final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget();
3030 
3031             if (dragboard == null) {
3032                 dragboard = createDragboard(de, false);
3033             }
3034 
3035             de = new DragEvent(de.getSource(), pickedTarget, de.getEventType(),
3036                     dragboard, de.getSceneX(), de.getSceneY(),
3037                     de.getScreenX(), de.getScreenY(),
3038                     de.getTransferMode(), source, potentialTarget, de.getPickResult());
3039 
3040             handleExitEnter(de, tmpTargetWrapper);
3041 
3042             de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_OVER,
3043                     de.getDragboard(), de.getSceneX(), de.getSceneY(),
3044                     de.getScreenX(), de.getScreenY(),
3045                     de.getTransferMode(), source, potentialTarget, de.getPickResult());
3046 
3047             fireEvent(pickedTarget, de);
3048 
3049             Object acceptingObject = de.getAcceptingObject();
3050             potentialTarget = acceptingObject instanceof EventTarget
3051                     ? (EventTarget) acceptingObject : null;
3052             acceptedTransferMode = de.getAcceptedTransferMode();
3053             return acceptedTransferMode;
3054         }
3055 
3056         private void processTargetActionChanged(DragEvent de) {
3057             // Do we want DRAG_TRANSFER_MODE_CHANGED event?
3058 //            final Node pickedNode = Scene.this.mouseHandler.pickNode(de.getX(), de.getY());
3059 //            if (pickedNode != null && pickedNode.impl_isTreeVisible()) {
3060 //                de = DragEvent.impl_copy(de.getSource(), pickedNode, source,
3061 //                        pickedNode, de, DragEvent.DRAG_TRANSFER_MODE_CHANGED);
3062 //
3063 //                if (dragboard == null) {
3064 //                    dragboard = createDragboard(de);
3065 //                }
3066 //                dragboard = de.impl_getPlatformDragboard();
3067 //
3068 //                fireEvent(pickedNode, de);
3069 //            }
3070         }
3071 
3072         private void processTargetExit(DragEvent de) {
3073             if (dragboard == null) {
3074                 // dragboard should have been created in processTargetEnterOver()
3075                 throw new NullPointerException("dragboard is null in processTargetExit()");
3076             }
3077 
3078             if (currentTargets.size() > 0) {
3079                 potentialTarget = null;
3080                 tmpTargetWrapper.clear();
3081                 handleExitEnter(de, tmpTargetWrapper);
3082             }
3083         }
3084 
3085         private TransferMode processTargetDrop(DragEvent de) {
3086             pick(tmpTargetWrapper, de.getSceneX(), de.getSceneY());
3087             final EventTarget pickedTarget = tmpTargetWrapper.getEventTarget();
3088 
3089             de = new DragEvent(de.getSource(), pickedTarget, DragEvent.DRAG_DROPPED,
3090                     de.getDragboard(), de.getSceneX(), de.getSceneY(),
3091                     de.getScreenX(), de.getScreenY(),
3092                     acceptedTransferMode, source, potentialTarget, de.getPickResult());
3093 
3094             if (dragboard == null) {
3095                 // dragboard should have been created in processTargetEnterOver()
3096                 throw new NullPointerException("dragboard is null in processTargetDrop()");
3097             }
3098 
3099             handleExitEnter(de, tmpTargetWrapper);
3100 
3101             fireEvent(pickedTarget, de);
3102 
3103             Object acceptingObject = de.getAcceptingObject();
3104             potentialTarget = acceptingObject instanceof EventTarget
3105                     ? (EventTarget) acceptingObject : null;
3106             target = potentialTarget;
3107 
3108             TransferMode result = de.isDropCompleted() ?
3109                 de.getAcceptedTransferMode() : null;
3110 
3111             tmpTargetWrapper.clear();
3112             handleExitEnter(de, tmpTargetWrapper);
3113 
3114             return result;
3115         }
3116 
3117         private void handleExitEnter(DragEvent e, TargetWrapper target) {
3118             EventTarget currentTarget =
3119                     currentTargets.size() > 0 ? currentTargets.get(0) : null;
3120 
3121             if (target.getEventTarget() != currentTarget) {
3122 
3123                 target.fillHierarchy(newTargets);
3124 
3125                 int i = currentTargets.size() - 1;
3126                 int j = newTargets.size() - 1;
3127 
3128                 while (i >= 0 && j >= 0 && currentTargets.get(i) == newTargets.get(j)) {
3129                     i--;
3130                     j--;
3131                 }
3132 
3133                 for (; i >= 0; i--) {
3134                     EventTarget t = currentTargets.get(i);
3135                     if (potentialTarget == t) {
3136                         potentialTarget = null;
3137                     }
3138                     e = e.copyFor(e.getSource(), t, source,
3139                             potentialTarget, DragEvent.DRAG_EXITED_TARGET);
3140                     Event.fireEvent(t, e);
3141                 }
3142 
3143                 potentialTarget = null;
3144                 for (; j >= 0; j--) {
3145                     EventTarget t = newTargets.get(j);
3146                     e = e.copyFor(e.getSource(), t, source,
3147                             potentialTarget, DragEvent.DRAG_ENTERED_TARGET);
3148                     Object acceptingObject = e.getAcceptingObject();
3149                     if (acceptingObject instanceof EventTarget) {
3150                         potentialTarget = (EventTarget) acceptingObject;
3151                     }
3152                     Event.fireEvent(t, e);
3153                 }
3154 
3155                 currentTargets.clear();
3156                 currentTargets.addAll(newTargets);
3157             }
3158         }
3159 
3160 //        function getIntendedTransferMode(e:MouseEvent):TransferMode {
3161 //            return if (e.altDown) TransferMode.COPY else TransferMode.MOVE;
3162 //        }
3163 
3164         /*
3165          * Function that hooks into the key processing code in Scene to handle the
3166          * situation where a drag and drop event is taking place and the user presses
3167          * the escape key to cancel the drag and drop operation.
3168          */
3169         private boolean processKey(KeyEvent e) {
3170             //note: this seems not to be called, the DnD cancelation is provided by platform
3171             if ((e.getEventType() == KeyEvent.KEY_PRESSED) && (e.getCode() == KeyCode.ESCAPE)) {
3172 
3173                 // cancel drag and drop
3174                 DragEvent de = new DragEvent(
3175                         source, source, DragEvent.DRAG_DONE, dragboard, 0, 0, 0, 0,
3176                         null, source, null, null);
3177                 if (source != null) {
3178                     Event.fireEvent(source, de);
3179                 }
3180 
3181                 tmpTargetWrapper.clear();
3182                 handleExitEnter(de, tmpTargetWrapper);
3183 
3184                 return false;
3185             }
3186             return true;
3187         }
3188 
3189         /*
3190          * This starts the drag gesture running, creating the dragboard used for
3191          * the remainder of this drag and drop operation.
3192          */
3193         private Dragboard startDrag(EventTarget source, Set<TransferMode> t) {
3194             if (dragDetected != DragDetectedState.PROCESSING) {
3195                 throw new IllegalStateException("Cannot start drag and drop "
3196                         + "outside of DRAG_DETECTED event handler");
3197             }
3198 
3199             if (t.isEmpty()) {
3200                 dragboard = null;
3201             } else if (dragboard == null) {
3202                 dragboard = createDragboard(null, true);
3203             }
3204             this.source = source;
3205             potentialTarget = source;
3206             sourceTransferModes = t;
3207             return dragboard;
3208         }
3209 
3210         /*
3211          * This starts the full PDR gesture.
3212          */
3213         private void startFullPDR(EventTarget source) {
3214             fullPDRSource = source;
3215         }
3216 
3217         private Dragboard createDragboard(final DragEvent de, boolean isDragSource) {
3218             Dragboard dragboard = null;
3219             if (de != null) {
3220                 dragboard = de.getDragboard();
3221                 if (dragboard != null) {
3222                     return dragboard;
3223                 }
3224             }
3225             TKClipboard dragboardPeer = impl_peer.createDragboard(isDragSource);
3226             return Dragboard.impl_createDragboard(dragboardPeer);
3227         }
3228     }
3229 
3230     /**
3231      * State of a drag gesture with regards to DRAG_DETECTED event.
3232      */
3233     private enum DragDetectedState {
3234         NOT_YET,
3235         PROCESSING,
3236         DONE
3237     }
3238 
3239     class DragSourceListener implements TKDragSourceListener {
3240 
3241         @Override
3242         public void dragDropEnd(double x, double y, double screenX, double screenY,
3243                                 TransferMode transferMode)
3244         {
3245             if (dndGesture != null) {
3246                 if (dndGesture.dragboard == null) {
3247                     throw new RuntimeException("dndGesture.dragboard is null in dragDropEnd");
3248                 }
3249                 DragEvent dragEvent =
3250                         new DragEvent(DragEvent.ANY, dndGesture.dragboard, x, y, screenX, screenY,
3251                         transferMode, null, null, null);
3252                 dndGesture.processDropEnd(dragEvent);
3253                 dndGesture = null;
3254             }
3255         }
3256     }
3257 
3258     /*******************************************************************************
3259      *                                                                             *
3260      * Mouse Event Handling                                                        *
3261      *                                                                             *
3262      ******************************************************************************/
3263 
3264     static class ClickCounter {
3265         Toolkit toolkit = Toolkit.getToolkit();
3266         private int count;
3267         private boolean out;
3268         private boolean still;
3269         private Timeline timeout;
3270         private double pressedX, pressedY;
3271 
3272         private void inc() { count++; }
3273         private int get() { return count; }
3274         private boolean isStill() { return still; }
3275 
3276         private void clear() {
3277             count = 0;
3278             stopTimeout();
3279         }
3280 
3281         private void out() {
3282             out = true;
3283             stopTimeout();
3284         }
3285 
3286         private void applyOut() {
3287             if (out) clear();
3288             out = false;
3289         }
3290 
3291         private void moved(double x, double y) {
3292             if (Math.abs(x - pressedX) > toolkit.getMultiClickMaxX() ||
3293                     Math.abs(y - pressedY) > toolkit.getMultiClickMaxY()) {
3294                 out();
3295                 still = false;
3296             }
3297         }
3298 
3299         private void start(double x, double y) {
3300             pressedX = x;
3301             pressedY = y;
3302             out = false;
3303 
3304             if (timeout != null) {
3305                 timeout.stop();
3306             }
3307             timeout = new Timeline();
3308             timeout.getKeyFrames().add(
3309                     new KeyFrame(new Duration(toolkit.getMultiClickTime()),
3310                     new EventHandler<ActionEvent>() {
3311                 @Override
3312                 public void handle(ActionEvent event) {
3313                     out = true;
3314                     timeout = null;
3315                 }
3316 
3317             }));
3318             timeout.play();
3319             still = true;
3320         }
3321 
3322         private void stopTimeout() {
3323             if (timeout != null) {
3324                 timeout.stop();
3325                 timeout = null;
3326             }
3327         }
3328     }
3329 
3330     static class ClickGenerator {
3331         private ClickCounter lastPress = null;
3332 
3333         private Map<MouseButton, ClickCounter> counters =
3334                 new EnumMap<MouseButton, ClickCounter>(MouseButton.class);
3335         private List<EventTarget> pressedTargets = new ArrayList<EventTarget>();
3336         private List<EventTarget> releasedTargets = new ArrayList<EventTarget>();
3337 
3338         public ClickGenerator() {
3339             for (MouseButton mb : MouseButton.values()) {
3340                 if (mb != MouseButton.NONE) {
3341                     counters.put(mb, new ClickCounter());
3342                 }
3343             }
3344         }
3345 
3346         private MouseEvent preProcess(MouseEvent e) {
3347             for (ClickCounter cc : counters.values()) {
3348                 cc.moved(e.getSceneX(), e.getSceneY());
3349             }
3350 
3351             ClickCounter cc = counters.get(e.getButton());
3352             boolean still = lastPress != null ? lastPress.isStill() : false;
3353 
3354             if (e.getEventType() == MouseEvent.MOUSE_PRESSED) {
3355 
3356                 if (! e.isPrimaryButtonDown()) { counters.get(MouseButton.PRIMARY).clear(); }
3357                 if (! e.isSecondaryButtonDown()) { counters.get(MouseButton.SECONDARY).clear(); }
3358                 if (! e.isMiddleButtonDown()) { counters.get(MouseButton.MIDDLE).clear(); }
3359 
3360                 cc.applyOut();
3361                 cc.inc();
3362                 cc.start(e.getSceneX(), e.getSceneY());
3363                 lastPress = cc;
3364             }
3365 
3366             return new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(),
3367                     e.getScreenX(), e.getScreenY(), e.getButton(),
3368                     cc != null && e.getEventType() != MouseEvent.MOUSE_MOVED ? cc.get() : 0,
3369                     e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(),
3370                     e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(),
3371                     e.isSynthesized(), e.isPopupTrigger(), still, e.getPickResult());
3372         }
3373 
3374         private void postProcess(MouseEvent e, TargetWrapper target, TargetWrapper pickedTarget) {
3375 
3376             if (e.getEventType() == MouseEvent.MOUSE_RELEASED) {
3377                 ClickCounter cc = counters.get(e.getButton());
3378 
3379                 target.fillHierarchy(pressedTargets);
3380                 pickedTarget.fillHierarchy(releasedTargets);
3381                 int i = pressedTargets.size() - 1;
3382                 int j = releasedTargets.size() - 1;
3383 
3384                 EventTarget clickedTarget = null;
3385                 while (i >= 0 && j >= 0 && pressedTargets.get(i) == releasedTargets.get(j)) {
3386                     clickedTarget = pressedTargets.get(i);
3387                     i--;
3388                     j--;
3389                 }
3390 
3391                 if (clickedTarget != null) {
3392                     MouseEvent click = new MouseEvent(null, clickedTarget,
3393                             MouseEvent.MOUSE_CLICKED, e.getSceneX(), e.getSceneY(),
3394                             e.getScreenX(), e.getScreenY(), e.getButton(),
3395                             cc.get(),
3396                             e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(),
3397                             e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(),
3398                             e.isSynthesized(), e.isPopupTrigger(), lastPress.isStill(), e.getPickResult());
3399                     Event.fireEvent(clickedTarget, click);
3400                 }
3401             }
3402         }
3403     }
3404 
3405     /**
3406      * Generates mouse exited event for a node which is going to be removed
3407      * and its children, where appropriate.
3408      * @param removing Node which is going to be removed
3409      */
3410     void generateMouseExited(Node removing) {
3411         mouseHandler.handleNodeRemoval(removing);
3412     }
3413 
3414     class MouseHandler {
3415         private TargetWrapper pdrEventTarget = new TargetWrapper(); // pdr - press-drag-release
3416         private boolean pdrInProgress = false;
3417         private boolean fullPDREntered = false;
3418 
3419         private EventTarget currentEventTarget = null;
3420         private MouseEvent lastEvent;
3421         private boolean hover = false;
3422 
3423         private boolean primaryButtonDown = false;
3424         private boolean secondaryButtonDown = false;
3425         private boolean middleButtonDown = false;
3426 
3427         private EventTarget fullPDRSource = null;
3428         private TargetWrapper fullPDRTmpTargetWrapper = new TargetWrapper();
3429 
3430         /* lists needed for enter/exit events generation */
3431         private final List<EventTarget> pdrEventTargets = new ArrayList<EventTarget>();
3432         private final List<EventTarget> currentEventTargets = new ArrayList<EventTarget>();
3433         private final List<EventTarget> newEventTargets = new ArrayList<EventTarget>();
3434 
3435         private final List<EventTarget> fullPDRCurrentEventTargets = new ArrayList<EventTarget>();
3436         private final List<EventTarget> fullPDRNewEventTargets = new ArrayList<EventTarget>();
3437         private EventTarget fullPDRCurrentTarget = null;
3438 
3439         private Cursor currCursor;
3440         private CursorFrame currCursorFrame;
3441         private EventQueue queue = new EventQueue();
3442 
3443         private Runnable pickProcess = new Runnable() {
3444 
3445             @Override
3446             public void run() {
3447                 // Make sure this is run only if the peer is still alive
3448                 // and there is an event to deliver
3449                 if (Scene.this.impl_peer != null && lastEvent != null) {
3450                     process(lastEvent, true);
3451                 }
3452             }
3453         };
3454 
3455         private void pulse() {
3456             if (hover && lastEvent != null) {
3457                 //Shouldn't run user code directly. User can call stage.showAndWait() and block the pulse.
3458                 Platform.runLater(pickProcess);
3459             }
3460         }
3461 
3462         private void clearPDREventTargets() {
3463             pdrInProgress = false;
3464             currentEventTarget = currentEventTargets.size() > 0
3465                     ? currentEventTargets.get(0) : null;
3466             pdrEventTarget.clear();
3467         }
3468 
3469         public void enterFullPDR(EventTarget gestureSource) {
3470             fullPDREntered = true;
3471             fullPDRSource = gestureSource;
3472             fullPDRCurrentTarget = null;
3473             fullPDRCurrentEventTargets.clear();
3474         }
3475 
3476         public void exitFullPDR(MouseEvent e) {
3477             if (!fullPDREntered) {
3478                 return;
3479             }
3480             fullPDREntered = false;
3481             for (int i = fullPDRCurrentEventTargets.size() - 1; i >= 0; i--) {
3482                 EventTarget entered = fullPDRCurrentEventTargets.get(i);
3483                 Event.fireEvent(entered, MouseEvent.copyForMouseDragEvent(e,
3484                         entered, entered,
3485                         MouseDragEvent.MOUSE_DRAG_EXITED_TARGET,
3486                         fullPDRSource, e.getPickResult()));
3487             }
3488             fullPDRSource = null;
3489             fullPDRCurrentEventTargets.clear();
3490             fullPDRCurrentTarget = null;
3491         }
3492 
3493         private void handleNodeRemoval(Node removing) {
3494             if (lastEvent == null) {
3495                 // this can happen only if everything has been exited anyway
3496                 return;
3497             }
3498 
3499 
3500             if (currentEventTargets.contains(removing)) {
3501                 int i = 0;
3502                 EventTarget trg = null;
3503                 while(trg != removing) {
3504                     trg = currentEventTargets.get(i++);
3505 
3506                     queue.postEvent(lastEvent.copyFor(trg, trg,
3507                             MouseEvent.MOUSE_EXITED_TARGET));
3508                 }
3509                 currentEventTargets.subList(0, i).clear();
3510             }
3511 
3512             if (fullPDREntered && fullPDRCurrentEventTargets.contains(removing)) {
3513                 int i = 0;
3514                 EventTarget trg = null;
3515                 while (trg != removing) {
3516                     trg = fullPDRCurrentEventTargets.get(i++);
3517 
3518                     queue.postEvent(
3519                             MouseEvent.copyForMouseDragEvent(lastEvent, trg, trg,
3520                             MouseDragEvent.MOUSE_DRAG_EXITED_TARGET,
3521                             fullPDRSource, lastEvent.getPickResult()));
3522                 }
3523 
3524                 fullPDRCurrentEventTargets.subList(0, i).clear();
3525             }
3526 
3527             queue.fire();
3528 
3529             if (pdrInProgress && pdrEventTargets.contains(removing)) {
3530                 int i = 0;
3531                 EventTarget trg = null;
3532                 while (trg != removing) {
3533                     trg = pdrEventTargets.get(i++);
3534 
3535                     // trg.setHover(false) - already taken care of
3536                     // by the code above which sent a mouse exited event
3537                     ((Node) trg).setPressed(false);
3538                 }
3539                 pdrEventTargets.subList(0, i).clear();
3540 
3541                 trg = pdrEventTargets.get(0);
3542                 final PickResult res = pdrEventTarget.getResult();
3543                 if (trg instanceof Node) {
3544                     pdrEventTarget.setNodeResult(new PickResult((Node) trg,
3545                             res.getIntersectedPoint(), res.getIntersectedDistance()));
3546                 } else {
3547                     pdrEventTarget.setSceneResult(new PickResult(null,
3548                             res.getIntersectedPoint(), res.getIntersectedDistance()),
3549                             (Scene) trg);
3550                 }
3551             }
3552         }
3553 
3554         private void handleEnterExit(MouseEvent e, TargetWrapper pickedTarget) {
3555             if (pickedTarget.getEventTarget() != currentEventTarget ||
3556                     e.getEventType() == MouseEvent.MOUSE_EXITED) {
3557 
3558                 if (e.getEventType() == MouseEvent.MOUSE_EXITED) {
3559                     newEventTargets.clear();
3560                 } else {
3561                     pickedTarget.fillHierarchy(newEventTargets);
3562                 }
3563 
3564                 int newTargetsSize = newEventTargets.size();
3565                 int i = currentEventTargets.size() - 1;
3566                 int j = newTargetsSize - 1;
3567                 int k = pdrEventTargets.size() - 1;
3568 
3569                 while (i >= 0 && j >= 0 && currentEventTargets.get(i) == newEventTargets.get(j)) {
3570                     i--;
3571                     j--;
3572                     k--;
3573                 }
3574 
3575                 final int memk = k;
3576                 for (; i >= 0; i--, k--) {
3577                     final EventTarget exitedEventTarget = currentEventTargets.get(i);
3578                     if (pdrInProgress &&
3579                             (k < 0 || exitedEventTarget != pdrEventTargets.get(k))) {
3580                          break;
3581                     }
3582                     queue.postEvent(e.copyFor(
3583                             exitedEventTarget, exitedEventTarget,
3584                             MouseEvent.MOUSE_EXITED_TARGET));
3585                 }
3586 
3587                 k = memk;
3588                 for (; j >= 0; j--, k--) {
3589                     final EventTarget enteredEventTarget = newEventTargets.get(j);
3590                     if (pdrInProgress &&
3591                             (k < 0 || enteredEventTarget != pdrEventTargets.get(k))) {
3592                         break;
3593                     }
3594                     queue.postEvent(e.copyFor(
3595                             enteredEventTarget, enteredEventTarget,
3596                             MouseEvent.MOUSE_ENTERED_TARGET));
3597                 }
3598 
3599                 currentEventTarget = pickedTarget.getEventTarget();
3600                 currentEventTargets.clear();
3601                 for (j++; j < newTargetsSize; j++) {
3602                     currentEventTargets.add(newEventTargets.get(j));
3603                 }
3604             }
3605             queue.fire();
3606         }
3607 
3608         private void process(MouseEvent e, boolean onPulse) {
3609             Toolkit.getToolkit().checkFxUserThread();
3610             Scene.inMousePick = true;
3611 
3612             cursorScreenPos = new Point2D(e.getScreenX(), e.getScreenY());
3613             cursorScenePos = new Point2D(e.getSceneX(), e.getSceneY());
3614 
3615             boolean gestureStarted = false;
3616             if (!onPulse) {
3617                 if (e.getEventType() == MouseEvent.MOUSE_PRESSED) {
3618                     if (!(primaryButtonDown || secondaryButtonDown || middleButtonDown)) {
3619                         //old gesture ended and new one started
3620                         gestureStarted = true;
3621                         if (!PLATFORM_DRAG_GESTURE_INITIATION) {
3622                             Scene.this.dndGesture = new DnDGesture();
3623                         }
3624                         clearPDREventTargets();
3625                     }
3626                 } else if (e.getEventType() == MouseEvent.MOUSE_MOVED) {
3627                     // gesture ended
3628                     clearPDREventTargets();
3629                 } else if (e.getEventType() == MouseEvent.MOUSE_ENTERED) {
3630                     hover = true;
3631                 } else if (e.getEventType() == MouseEvent.MOUSE_EXITED) {
3632                     hover = false;
3633                 }
3634 
3635                 primaryButtonDown = e.isPrimaryButtonDown();
3636                 secondaryButtonDown = e.isSecondaryButtonDown();
3637                 middleButtonDown = e.isMiddleButtonDown();
3638             }
3639 
3640             pick(tmpTargetWrapper, e.getSceneX(), e.getSceneY());
3641             PickResult res = tmpTargetWrapper.getResult();
3642             if (res != null) {
3643                 e = new MouseEvent(e.getEventType(), e.getSceneX(), e.getSceneY(),
3644                     e.getScreenX(), e.getScreenY(), e.getButton(), e.getClickCount(),
3645                     e.isShiftDown(), e.isControlDown(), e.isAltDown(), e.isMetaDown(),
3646                     e.isPrimaryButtonDown(), e.isMiddleButtonDown(), e.isSecondaryButtonDown(),
3647                     e.isSynthesized(), e.isPopupTrigger(), e.isStillSincePress(), res);
3648             }
3649 
3650             if (e.getEventType() == MouseEvent.MOUSE_EXITED) {
3651                 tmpTargetWrapper.clear();
3652             }
3653 
3654             TargetWrapper target;
3655             if (pdrInProgress) {
3656                 target = pdrEventTarget;
3657             } else {
3658                 target = tmpTargetWrapper;
3659             }
3660 
3661             if (gestureStarted) {
3662                 pdrEventTarget.copy(target);
3663                 pdrEventTarget.fillHierarchy(pdrEventTargets);
3664             }
3665 
3666             if (!onPulse) {
3667                 e = clickGenerator.preProcess(e);
3668             }
3669 
3670             // enter/exit handling
3671             handleEnterExit(e, tmpTargetWrapper);
3672 
3673             Cursor cursor = target.getCursor();
3674 
3675             //deliver event to the target node
3676 
3677             if (Scene.this.dndGesture != null) {
3678                 Scene.this.dndGesture.processDragDetection(e);
3679             }
3680 
3681             if (fullPDREntered && e.getEventType() == MouseEvent.MOUSE_RELEASED) {
3682                 processFullPDR(e, onPulse);
3683             }
3684 
3685             if (target.getEventTarget() != null) {
3686                 if (e.getEventType() != MouseEvent.MOUSE_ENTERED
3687                         && e.getEventType() != MouseEvent.MOUSE_EXITED
3688                         && !onPulse) {
3689                     Event.fireEvent(target.getEventTarget(), e);
3690                 }
3691             }
3692 
3693             if (fullPDREntered && e.getEventType() != MouseEvent.MOUSE_RELEASED) {
3694                 processFullPDR(e, onPulse);
3695             }
3696 
3697             if (!onPulse) {
3698                 clickGenerator.postProcess(e, target, tmpTargetWrapper);
3699             }
3700 
3701             // handle drag and drop
3702 
3703             if (!PLATFORM_DRAG_GESTURE_INITIATION && !onPulse) {
3704                 if (Scene.this.dndGesture != null) {
3705                     if (!Scene.this.dndGesture.process(e, target.getEventTarget())) {
3706                         dndGesture = null;
3707                     }
3708                 }
3709             }
3710 
3711 
3712             if (cursor == null && hover) {
3713                 cursor = Scene.this.getCursor();
3714             }
3715 
3716             updateCursor(cursor);
3717             updateCursorFrame();
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 }