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