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