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
028
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Set;
035import javafx.beans.InvalidationListener;
036import javafx.beans.Observable;
037import javafx.beans.binding.BooleanExpression;
038import javafx.beans.property.BooleanProperty;
039import javafx.beans.property.BooleanPropertyBase;
040import javafx.beans.property.DoubleProperty;
041import javafx.beans.property.DoublePropertyBase;
042import javafx.beans.property.IntegerProperty;
043import javafx.beans.property.ObjectProperty;
044import javafx.beans.property.ObjectPropertyBase;
045import javafx.beans.property.ReadOnlyBooleanProperty;
046import javafx.beans.property.ReadOnlyBooleanPropertyBase;
047import javafx.beans.property.ReadOnlyBooleanWrapper;
048import javafx.beans.property.ReadOnlyObjectProperty;
049import javafx.beans.property.ReadOnlyObjectPropertyBase;
050import javafx.beans.property.ReadOnlyObjectWrapper;
051import javafx.beans.property.SimpleBooleanProperty;
052import javafx.beans.property.SimpleObjectProperty;
053import javafx.beans.property.StringProperty;
054import javafx.beans.property.StringPropertyBase;
055import javafx.beans.value.ChangeListener;
056import javafx.collections.FXCollections;
057import javafx.collections.ListChangeListener.Change;
058import javafx.collections.ObservableList;
059import javafx.collections.ObservableMap;
060import javafx.collections.ObservableSet;
061import javafx.event.Event;
062import javafx.event.EventDispatchChain;
063import javafx.event.EventDispatcher;
064import javafx.event.EventHandler;
065import javafx.event.EventTarget;
066import javafx.event.EventType;
067import javafx.geometry.BoundingBox;
068import javafx.geometry.Bounds;
069import javafx.geometry.Orientation;
070import javafx.geometry.Point2D;
071import javafx.geometry.Point3D;
072import javafx.geometry.Rectangle2D;
073import javafx.scene.effect.Blend;
074import javafx.scene.effect.BlendMode;
075import javafx.scene.effect.Effect;
076import javafx.scene.image.WritableImage;
077import javafx.scene.input.ContextMenuEvent;
078import javafx.scene.input.DragEvent;
079import javafx.scene.input.Dragboard;
080import javafx.scene.input.InputEvent;
081import javafx.scene.input.InputMethodEvent;
082import javafx.scene.input.InputMethodRequests;
083import javafx.scene.input.KeyEvent;
084import javafx.scene.input.MouseDragEvent;
085import javafx.scene.input.MouseEvent;
086import javafx.scene.input.RotateEvent;
087import javafx.scene.input.ScrollEvent;
088import javafx.scene.input.SwipeEvent;
089import javafx.scene.input.TouchEvent;
090import javafx.scene.input.TransferMode;
091import javafx.scene.input.ZoomEvent;
092import javafx.scene.text.Font;
093import javafx.scene.transform.Rotate;
094import javafx.scene.transform.Transform;
095import javafx.util.Callback;
096import com.sun.javafx.Logging;
097import com.sun.javafx.TempState;
098import com.sun.javafx.Utils;
099import com.sun.javafx.beans.IDProperty;
100import com.sun.javafx.beans.event.AbstractNotifyListener;
101import com.sun.javafx.binding.ExpressionHelper;
102import com.sun.javafx.collections.TrackableObservableList;
103import com.sun.javafx.collections.UnmodifiableListSet;
104import com.sun.javafx.css.PseudoClassState;
105import javafx.css.ParsedValue;
106import com.sun.javafx.css.Selector;
107import com.sun.javafx.css.Style;
108import javafx.css.StyleConverter;
109import javafx.css.Styleable;
110import javafx.css.StyleableBooleanProperty;
111import javafx.css.StyleableDoubleProperty;
112import javafx.css.StyleableObjectProperty;
113import javafx.css.CssMetaData;
114import javafx.css.PseudoClass;
115import com.sun.javafx.css.converters.BooleanConverter;
116import com.sun.javafx.css.converters.CursorConverter;
117import com.sun.javafx.css.converters.EffectConverter;
118import com.sun.javafx.css.converters.EnumConverter;
119import com.sun.javafx.css.converters.SizeConverter;
120import com.sun.javafx.effect.EffectDirtyBits;
121import com.sun.javafx.geom.BaseBounds;
122import com.sun.javafx.geom.BoxBounds;
123import com.sun.javafx.geom.PickRay;
124import com.sun.javafx.geom.RectBounds;
125import com.sun.javafx.geom.transform.Affine3D;
126import com.sun.javafx.geom.transform.BaseTransform;
127import com.sun.javafx.geom.transform.NoninvertibleTransformException;
128import com.sun.javafx.geom.Vec3d;
129import com.sun.javafx.geom.transform.GeneralTransform3D;
130import com.sun.javafx.jmx.MXNodeAlgorithm;
131import com.sun.javafx.jmx.MXNodeAlgorithmContext;
132import sun.util.logging.PlatformLogger;
133import com.sun.javafx.perf.PerformanceTracker;
134import com.sun.javafx.scene.BoundsAccessor;
135import com.sun.javafx.scene.CameraHelper;
136import com.sun.javafx.scene.CssFlags;
137import com.sun.javafx.scene.DirtyBits;
138import com.sun.javafx.scene.EventHandlerProperties;
139import com.sun.javafx.scene.NodeEventDispatcher;
140import com.sun.javafx.scene.NodeHelper;
141import com.sun.javafx.scene.SceneHelper;
142import com.sun.javafx.scene.SceneUtils;
143import com.sun.javafx.scene.input.PickResultChooser;
144import com.sun.javafx.scene.transform.TransformUtils;
145import com.sun.javafx.scene.traversal.Direction;
146import com.sun.javafx.sg.PGNode;
147import com.sun.javafx.tk.Toolkit;
148import javafx.css.StyleableProperty;
149import javafx.geometry.NodeOrientation;
150import javafx.stage.Window;
151
152/**
153 * Base class for scene graph nodes. A scene graph is a set of tree data structures
154 * where every item has zero or one parent, and each item is either
155 * a "leaf" with zero sub-items or a "branch" with zero or more sub-items.
156 * <p>
157 * Each item in the scene graph is called a {@code Node}. Branch nodes are
158 * of type {@link Parent}, whose concrete subclasses are {@link Group},
159 * {@link javafx.scene.layout.Region}, and {@link javafx.scene.control.Control},
160 * or subclasses thereof.
161 * <p>
162 * Leaf nodes are classes such as
163 * {@link javafx.scene.shape.Rectangle}, {@link javafx.scene.text.Text},
164 * {@link javafx.scene.image.ImageView}, {@link javafx.scene.media.MediaView},
165 * or other such leaf classes which cannot have children. Only a single node within
166 * each scene graph tree will have no parent, which is referred to as the "root" node.
167 * <p>
168 * There may be several trees in the scene graph. Some trees may be part of
169 * a {@link Scene}, in which case they are eligible to be displayed.
170 * Other trees might not be part of any {@link Scene}.
171 * <p>
172 * A node may occur at most once anywhere in the scene graph. Specifically,
173 * a node must appear no more than once in all of the following:
174 * as the root node of a {@link Scene},
175 * the children ObservableList of a {@link Parent},
176 * or as the clip of a {@link Node}.
177 * <p>
178 * The scene graph must not have cycles. A cycle would exist if a node is
179 * an ancestor of itself in the tree, considering the {@link Group} content
180 * ObservableList, {@link Parent} children ObservableList, and {@link Node} clip relationships
181 * mentioned above.
182 * <p>
183 * If a program adds a child node to a Parent (including Group, Region, etc)
184 * and that node is already a child of a different Parent or the root of a Scene,
185 * the node is automatically (and silently) removed from its former parent.
186 * If a program attempts to modify the scene graph in any other way that violates
187 * the above rules, an exception is thrown, the modification attempt is ignored
188 * and the scene graph is restored to its previous state.
189 * <p>
190 * It is possible to rearrange the structure of the scene graph, for
191 * example, to move a subtree from one location in the scene graph to
192 * another. In order to do this, one would normally remove the subtree from
193 * its old location before inserting it at the new location. However, the
194 * subtree will be automatically removed as described above if the application
195 * doesn't explicitly remove it.
196 * <p>
197 * Node objects may be constructed and modified on any thread as long they are
198 * not yet attached to a {@link Scene}. An application must attach nodes to a
199 * Scene, and modify nodes that are already attached to a Scene, on the JavaFX
200 * Application Thread.
201 *
202 * <h4>String ID</h4>
203 * <p>
204 * Each node in the scene graph can be given a unique {@link #idProperty id}. This id is
205 * much like the "id" attribute of an HTML tag in that it is up to the designer
206 * and developer to ensure that the {@code id} is unique within the scene graph.
207 * A convenience function called {@link #lookup(String)} can be used to find
208 * a node with a unique id within the scene graph, or within a subtree of the
209 * scene graph. The id can also be used identify nodes for applying styles; see
210 * the CSS section below.
211 *
212 * <h4>Coordinate System</h4>
213 * <p>
214 * The {@code Node} class defines a traditional computer graphics "local"
215 * coordinate system in which the {@code x} axis increases to the right and the
216 * {@code y} axis increases downwards.  The concrete node classes for shapes
217 * provide variables for defining the geometry and location of the shape
218 * within this local coordinate space.  For example,
219 * {@link javafx.scene.shape.Rectangle} provides {@code x}, {@code y},
220 * {@code width}, {@code height} variables while
221 * {@link javafx.scene.shape.Circle} provides {@code centerX}, {@code centerY},
222 * and {@code radius}.
223 * <p>
224 * At the device pixel level, integer coordinates map onto the corners and
225 * cracks between the pixels and the centers of the pixels appear at the
226 * midpoints between integer pixel locations.  Because all coordinate values
227 * are specified with floating point numbers, coordinates can precisely
228 * point to these corners (when the floating point values have exact integer
229 * values) or to any location on the pixel.  For example, a coordinate of
230 * {@code (0.5, 0.5)} would point to the center of the upper left pixel on the
231 * {@code Stage}.  Similarly, a rectangle at {@code (0, 0)} with dimensions
232 * of {@code 10} by {@code 10} would span from the upper left corner of the
233 * upper left pixel on the {@code Stage} to the lower right corner of the
234 * 10th pixel on the 10th scanline.  The pixel center of the last pixel
235 * inside that rectangle would be at the coordinates {@code (9.5, 9.5)}.
236 * <p>
237 * In practice, most nodes have transformations applied to their coordinate
238 * system as mentioned below.  As a result, the information above describing
239 * the alignment of device coordinates to the pixel grid is relative to
240 * the transformed coordinates, not the local coordinates of the nodes.
241 * The {@link javafx.scene.shape.Shape Shape} class describes some additional
242 * important context-specific information about coordinate mapping and how
243 * it can affect rendering.
244 *
245 * <h4>Transformations</h4>
246 * <p>
247 * Any {@code Node} can have transformations applied to it. These include
248 * translation, rotation, scaling, or shearing.
249 * <p>
250 * A <b>translation</b> transformation is one which shifts the origin of the
251 * node's coordinate space along either the x or y axis. For example, if you
252 * create a {@link javafx.scene.shape.Rectangle} which is drawn at the origin
253 * (x=0, y=0) and has a width of 100 and a height of 50, and then apply a
254 * {@link javafx.scene.transform.Translate} with a shift of 10 along the x axis
255 * (x=10), then the rectangle will appear drawn at (x=10, y=0) and remain
256 * 100 points wide and 50 tall. Note that the origin was shifted, not the
257 * {@code x} variable of the rectangle.
258 * <p>
259 * A common node transform is a translation by an integer distance, most often
260 * used to lay out nodes on the stage.  Such integer translations maintain the
261 * device pixel mapping so that local coordinates that are integers still
262 * map to the cracks between pixels.
263 * <p>
264 * A <b>rotation</b> transformation is one which rotates the coordinate space of
265 * the node about a specified "pivot" point, causing the node to appear rotated.
266 * For example, if you create a {@link javafx.scene.shape.Rectangle} which is
267 * drawn at the origin (x=0, y=0) and has a width of 100 and height of 30 and
268 * you apply a {@link javafx.scene.transform.Rotate} with a 90 degree rotation
269 * (angle=90) and a pivot at the origin (pivotX=0, pivotY=0), then
270 * the rectangle will be drawn as if its x and y were zero but its height was
271 * 100 and its width -30. That is, it is as if a pin is being stuck at the top
272 * left corner and the rectangle is rotating 90 degrees clockwise around that
273 * pin. If the pivot point is instead placed in the center of the rectangle
274 * (at point x=50, y=15) then the rectangle will instead appear to rotate about
275 * its center.
276 * <p>
277 * Note that as with all transformations, the x, y, width, and height variables
278 * of the rectangle (which remain relative to the local coordinate space) have
279 * not changed, but rather the transformation alters the entire coordinate space
280 * of the rectangle.
281 * <p>
282 * A <b>scaling</b> transformation causes a node to either appear larger or
283 * smaller depending on the scaling factor. Scaling alters the coordinate space
284 * of the node such that each unit of distance along the axis in local
285 * coordinates is multipled by the scale factor. As with rotation
286 * transformations, scaling transformations are applied about a "pivot" point.
287 * You can think of this as the point in the Node around which you "zoom".  For
288 * example, if you create a {@link javafx.scene.shape.Rectangle} with a
289 * {@code strokeWidth} of 5, and a width and height of 50, and you apply a
290 * {@link javafx.scene.transform.Scale} with scale factors (x=2.0, y=2.0) and
291 * a pivot at the origin (pivotX=0, pivotY=0), the entire rectangle
292 * (including the stroke) will double in size, growing to the right and
293 * downwards from the origin.
294 * <p>
295 * A <b>shearing</b> transformation, sometimes called a skew, effectively
296 * rotates one axis so that the x and y axes are no longer perpendicular.
297 * <p>
298 * Multiple transformations may be applied to a node by specifying an ordered
299 * chain of transforms.  The order in which the transforms are applied is
300 * defined by the ObservableList specified in the {@link #getTransforms transforms} variable.
301 *
302 * <h4>Bounding Rectangles</h4>
303 * <p>
304 * Since every {@code Node} has transformations, every Node's geometric
305 * bounding rectangle can be described differently depending on whether
306 * transformations are accounted for or not.
307 * <p>
308 * Each {@code Node} has a read-only {@link #boundsInLocalProperty boundsInLocal}
309 * variable which specifies the bounding rectangle of the {@code Node} in
310 * untransformed local coordinates. {@code boundsInLocal} includes the
311 * Node's shape geometry, including any space required for a
312 * non-zero stroke that may fall outside the local position/size variables,
313 * and its {@link #clipProperty clip} and {@link #effectProperty effect} variables.
314 * <p>
315 * Each {@code Node} also has a read-only {@link #boundsInParentProperty boundsInParent} variable which
316 * specifies the bounding rectangle of the {@code Node} after all transformations
317 * have been applied, including those set in {@link #getTransforms transforms},
318 * {@link #scaleXProperty scaleX}/{@link #scaleYProperty scaleY}, {@link #rotateProperty rotate},
319 * {@link #translateXProperty translateX}/{@link #translateYProperty translateY}, and {@link #layoutXProperty layoutX}/{@link #layoutYProperty layoutY}.
320 * It is called "boundsInParent" because the rectangle will be relative to the
321 * parent's coordinate system.  This is the 'visual' bounds of the node.
322 * <p>
323 * Finally, the {@link #layoutBoundsProperty layoutBounds} variable defines the rectangular bounds of
324 * the {@code Node} that should be used as the basis for layout calculations and
325 * may differ from the visual bounds of the node.  For shapes, Text, and ImageView,
326 * layoutBounds by default includes only the shape geometry, including space required
327 * for a non-zero {@code strokeWidth}, but does <i>not</i> include the effect,
328 * clip, or any transforms. For resizable classes (Regions and Controls)
329 * layoutBounds will always map to {@code 0,0 width x height}.
330 *
331 * <p> The image shows a node with transformation (rotation by 20 degrees)
332 * and its bounds. The red rectangle represents {@code boundsInParent} in the
333 * coordinate space of the Node's parent. The green rectangle represents {@code boundsInLocal}
334 * in coordinate space of the Node. </p>
335 * <p> <img src="doc-files/bounds-complex.png"/> </p>
336 *
337 * <p> The images show a filled and stroked rectangle and their bounds. The
338 * first rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:0]}
339 * has the following bounds bounds: {@code [x:10.0 y:10.0 width:100.0 height:100.0]}.
340 *
341 * The second rectangle {@code [x:10.0 y:10.0 width:100.0 height:100.0 strokeWidth:5]}
342 * has the following bounds: {@code [x:5.0 y:5.0 width:110.0 height:110.0]}.
343 *
344 * Since neither of the rectangles has any transformation applied,
345 * {@code boundsInParent} and {@code boundsInLocal} are the same. </p>
346 * <p> <img src="doc-files/bounds.png"/> </p>
347 *
348 *
349 * <h4>CSS</h4>
350 * <p>
351 * The {@code Node} class contains {@code id}, {@code styleClass}, and
352 * {@code style} variables that are used in styling this node from
353 * CSS. The {@code id} and {@code styleClass} variables are used in
354 * CSS style sheets to identify nodes to which styles should be
355 * applied. The {@code style} variable contains style properties and
356 * values that are applied directly to this node.
357 * <p>
358 * For further information about CSS and how to apply CSS styles
359 * to nodes, see the <a href="doc-files/cssref.html">CSS Reference
360 * Guide</a>.
361 */
362@IDProperty("id")
363public abstract class Node implements EventTarget, Styleable {
364
365     static {
366          PerformanceTracker.logEvent("Node class loaded");
367     }
368
369    /**************************************************************************
370     *                                                                        *
371     * Methods and state for managing the dirty bits of a Node. The dirty     *
372     * bits are flags used to keep track of what things are dirty on the      *
373     * node and therefore need processing on the next pulse. Since the pulse  *
374     * happens asynchronously to the change that made the node dirty (for     *
375     * performance reasons), we need to keep track of what things have        *
376     * changed.                                                               *
377     *                                                                        *
378     *************************************************************************/
379
380    /**
381     * Set of dirty bits that are set when state is invalidated and cleared by
382     * the updateState method, which is called from the synchronizer.
383     */
384    private int dirtyBits;
385
386    /**
387     * Mark the specified bit as dirty, and add this node to the scene's dirty list.
388     *
389     * @treatAsPrivate implementation detail
390     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
391     */
392    @Deprecated
393    protected void impl_markDirty(DirtyBits dirtyBit) {
394        if (impl_isDirtyEmpty()) {
395            addToSceneDirtyList();
396        }
397
398        dirtyBits |= dirtyBit.getMask();
399    }
400
401    private void addToSceneDirtyList() {
402        Scene s = getScene();
403        if (s != null) {
404            s.addToDirtyList(this);
405            if (getSubScene() != null) {
406                getSubScene().setDirty(this);
407            }
408        }
409    }
410
411    /**
412     * Test whether the specified dirty bit is set
413     *
414     * @treatAsPrivate implementation detail
415     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
416     */
417    @Deprecated
418    protected final boolean impl_isDirty(DirtyBits dirtyBit) {
419        return (dirtyBits & dirtyBit.getMask()) != 0;
420    }
421
422    /**
423     * Clear the specified dirty bit
424     *
425     * @treatAsPrivate implementation detail
426     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
427     */
428    @Deprecated
429    protected final void impl_clearDirty(DirtyBits dirtyBit) {
430        dirtyBits &= ~dirtyBit.getMask();
431    }
432
433    /**
434     * Set all dirty bits
435     */
436    private void setDirty() {
437        dirtyBits = ~0;
438    }
439
440    /**
441     * Clear all dirty bits
442     */
443    private void clearDirty() {
444        dirtyBits = 0;
445    }
446
447    /**
448     * Test whether the set of dirty bits is empty
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    protected final boolean impl_isDirtyEmpty() {
455        return dirtyBits == 0;
456    }
457
458    /**************************************************************************
459     *                                                                        *
460     * Methods for synchronizing state from this Node to its PG peer. This    *
461     * should only *ever* be called during synchronization initialized as a   *
462     * result of a pulse. Any attempt to synchronize at any other time may    *
463     * cause rendering artifacts.                                             *
464     *                                                                        *
465     *************************************************************************/
466
467    /**
468     * Called by the synchronizer to update the state and
469     * clear dirtybits of this node in the PG graph
470     *
471     * @treatAsPrivate implementation detail
472     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
473     */
474    @Deprecated
475    public final void impl_syncPGNode() {
476        // Do not synchronize invisible nodes unless their visibility has changed
477        if (!impl_isDirtyEmpty() && (treeVisible || impl_isDirty(DirtyBits.NODE_VISIBLE))) {
478            impl_updatePG();
479            clearDirty();
480        }
481    }
482
483    /**
484     * A temporary rect used for computing bounds by the various bounds
485     * variables. This bounds starts life as a RectBounds, but may be promoted
486     * to a BoxBounds if there is a 3D transform mixed into its computation.
487     * These two fields were held in a thread local, but were then pulled
488     * out of it so that we could compute bounds before holding the
489     * synchronization lock. These objects have to be per-instance so
490     * that we can pass the right data down to the PG side later during
491     * synchronization (rather than statics as they were before).
492     */
493    private BaseBounds _geomBounds = new RectBounds(0, 0, -1, -1);
494    private BaseBounds _txBounds = new RectBounds(0, 0, -1, -1);
495
496    // Happens before we hold the sync lock
497    void updateBounds() {
498        // See impl_syncPGNode()
499        if (!treeVisible && !impl_isDirty(DirtyBits.NODE_VISIBLE)) {
500            return;
501        }
502        if (impl_isDirty(DirtyBits.NODE_TRANSFORM) || impl_isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) {
503            if (impl_isDirty(DirtyBits.NODE_TRANSFORM)) {
504                updateLocalToParentTransform();
505            }
506            _txBounds = getTransformedBounds(_txBounds,
507                                             BaseTransform.IDENTITY_TRANSFORM);
508        }
509
510        if (impl_isDirty(DirtyBits.NODE_BOUNDS)) {
511            _geomBounds = getGeomBounds(_geomBounds,
512                    BaseTransform.IDENTITY_TRANSFORM);
513        }
514
515        Node n = getClip();
516        if (n != null) {
517            n.updateBounds();
518        }
519    }
520
521    /**
522     * This function is called during synchronization to update the state of the
523     * PG Node from the FX Node. Subclasses of Node should override this method
524     * and must call super.impl_updatePG()
525     *
526     * @treatAsPrivate implementation detail
527     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
528     */
529    @Deprecated
530    public void impl_updatePG() {
531        final PGNode peer = impl_getPGNode();
532        if (impl_isDirty(DirtyBits.NODE_TRANSFORM)) {
533            peer.setTransformMatrix(localToParentTx);
534        }
535
536        if (impl_isDirty(DirtyBits.NODE_BOUNDS)) {
537            peer.setContentBounds(_geomBounds);
538        }
539
540        if (impl_isDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS)) {
541            peer.setTransformedBounds(_txBounds, !impl_isDirty(DirtyBits.NODE_BOUNDS));
542        }
543
544        if (impl_isDirty(DirtyBits.NODE_OPACITY)) {
545            peer.setOpacity((float)Utils.clamp(0, getOpacity(), 1));
546        }
547
548        if (impl_isDirty(DirtyBits.NODE_CACHE)) {
549            peer.setCachedAsBitmap(isCache(), toPGCacheHint(getCacheHint()));
550        }
551
552        if (impl_isDirty(DirtyBits.NODE_CLIP)) {
553            peer.setClipNode(getClip() != null ? getClip().impl_getPGNode() : null);
554        }
555
556        if (impl_isDirty(DirtyBits.EFFECT_EFFECT)) {
557            if (getEffect() != null) {
558                getEffect().impl_sync();
559                peer.effectChanged();
560            }
561        }
562
563        if (impl_isDirty(DirtyBits.NODE_EFFECT)) {
564            peer.setEffect(getEffect() != null ? getEffect().impl_getImpl() : null);
565        }
566
567        if (impl_isDirty(DirtyBits.NODE_VISIBLE)) {
568            peer.setVisible(isVisible());
569        }
570
571        if (impl_isDirty(DirtyBits.NODE_DEPTH_TEST)) {
572            peer.setDepthTest(isDerivedDepthTest());
573        }
574
575        if (impl_isDirty(DirtyBits.NODE_BLENDMODE)) {
576            BlendMode mode = getBlendMode();
577            peer.setNodeBlendMode((mode == null)
578                                  ? null
579                                  : Blend.impl_getToolkitMode(mode));
580        }
581    }
582
583    /*************************************************************************
584    *                                                                        *
585    *                                                                        *
586    *                                                                        *
587    *************************************************************************/
588
589    private static final Object USER_DATA_KEY = new Object();
590    // A map containing a set of properties for this node
591    private ObservableMap<Object, Object> properties;
592
593    /**
594      * Returns an observable map of properties on this node for use primarily
595      * by application developers.
596      *
597      * @return an observable map of properties on this node for use primarily
598      * by application developers
599      */
600     public final ObservableMap<Object, Object> getProperties() {
601        if (properties == null) {
602            properties = FXCollections.observableMap(new HashMap<Object, Object>());
603        }
604        return properties;
605    }
606
607    /**
608     * Tests if Node has properties.
609     * @return true if node has properties.
610     */
611     public boolean hasProperties() {
612        return properties != null && !properties.isEmpty();
613    }
614
615    /**
616     * Convenience method for setting a single Object property that can be
617     * retrieved at a later date. This is functionally equivalent to calling
618     * the getProperties().put(Object key, Object value) method. This can later
619     * be retrieved by calling {@link Node#getUserData()}.
620     *
621     * @param value The value to be stored - this can later be retrieved by calling
622     *          {@link Node#getUserData()}.
623     */
624    public void setUserData(Object value) {
625        getProperties().put(USER_DATA_KEY, value);
626    }
627
628    /**
629     * Returns a previously set Object property, or null if no such property
630     * has been set using the {@link Node#setUserData(java.lang.Object)} method.
631     *
632     * @return The Object that was previously set, or null if no property
633     *          has been set or if null was set.
634     */
635    public Object getUserData() {
636        return getProperties().get(USER_DATA_KEY);
637    }
638
639    /**************************************************************************
640     *                                                                        *
641     *
642     *                                                                        *
643     *************************************************************************/
644
645    /**
646     * The parent of this {@code Node}. If this {@code Node} has not been added
647     * to a scene graph, then parent will be null.
648     *
649     * @defaultValue null
650     */
651    private ReadOnlyObjectWrapper<Parent> parent;
652
653    final void setParent(Parent value) {
654        parentPropertyImpl().set(value);
655    }
656
657    public final Parent getParent() {
658        return parent == null ? null : parent.get();
659    }
660
661    public final ReadOnlyObjectProperty<Parent> parentProperty() {
662        return parentPropertyImpl().getReadOnlyProperty();
663    }
664
665    private ReadOnlyObjectWrapper<Parent> parentPropertyImpl() {
666        if (parent == null) {
667            parent = new ReadOnlyObjectWrapper<Parent>() {
668                private Parent oldParent;
669
670                @Override
671                protected void invalidated() {
672                    if (oldParent != null) {
673                        oldParent.disabledProperty().removeListener(parentDisabledChangedListener);
674                        oldParent.impl_treeVisibleProperty().removeListener(parentTreeVisibleChangedListener);
675                        if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
676                            ((Node) oldParent).localToSceneTransformProperty().removeListener(
677                                    nodeTransformation.getLocalToSceneInvalidationListener());
678                        }
679                    }
680                    updateDisabled();
681                    computeDerivedDepthTest();
682                    final Parent newParent = get();
683                    if (newParent != null) {
684                        newParent.disabledProperty().addListener(parentDisabledChangedListener);
685                        newParent.impl_treeVisibleProperty().addListener(parentTreeVisibleChangedListener);
686                        if (nodeTransformation != null && nodeTransformation.listenerReasons > 0) {
687                            ((Node) newParent).localToSceneTransformProperty().addListener(
688                                    nodeTransformation.getLocalToSceneInvalidationListener());
689                        }
690                        //
691                        // if parent changed, then CSS needs to be reapplied so
692                        // that this node will get the right styles. This used
693                        // to be done from Parent.children's onChanged method.
694                        // See the comments there, also.
695                        //
696                        impl_reapplyCSS();
697                    }
698                    updateTreeVisible();
699                    oldParent = newParent;
700                    invalidateLocalToSceneTransform();
701                    parentResolvedOrientationInvalidated();
702                }
703
704                @Override
705                public Object getBean() {
706                    return Node.this;
707                }
708
709                @Override
710                public String getName() {
711                    return "parent";
712                }
713            };
714        }
715        return parent;
716    }
717
718    private final InvalidationListener parentDisabledChangedListener = new InvalidationListener() {
719        @Override public void invalidated(Observable valueModel) {
720            updateDisabled();
721        }
722    };
723
724    private final InvalidationListener parentTreeVisibleChangedListener = new InvalidationListener() {
725        @Override public void invalidated(Observable valueModel) {
726            updateTreeVisible();
727        }
728    };
729
730    private SubScene subScene = null;
731
732    /**
733     * The {@link Scene} that this {@code Node} is part of. If the Node is not
734     * part of a scene, then this variable will be null.
735     *
736     * @defaultValue null
737     */
738    private ReadOnlyObjectWrapperManualFire<Scene> scene = new ReadOnlyObjectWrapperManualFire<Scene>();
739
740    private class ReadOnlyObjectWrapperManualFire<T> extends ReadOnlyObjectWrapper<T> {
741        @Override
742        public Object getBean() {
743            return Node.this;
744        }
745
746        @Override
747        public String getName() {
748            return "scene";
749        }
750
751        @Override
752        protected void fireValueChangedEvent() {
753            /*
754             * Note: This method has been intentionally made into a no-op. In
755             * order to override the default set behavior. By default calling
756             * set(...) on a different scene will trigger:
757             * - invalidated();
758             * - fireValueChangedEvent();
759             * Both of the above are no-ops, but are handled manually via
760             * - Node.this.setScenes(...)
761             * - Node.this.invalidatedScenes(...)
762             * - forceValueChangedEvent()
763             */
764        }
765
766        public void fireSuperValueChangedEvent() {
767            super.fireValueChangedEvent();
768        }
769    }
770
771    private void invalidatedScenes(Scene oldScene, SubScene oldSubScene) {
772        Scene newScene = sceneProperty().get();
773        boolean sceneChanged = oldScene != newScene;
774        SubScene newSubScene = subScene;
775
776        if (getClip() != null) {
777            getClip().setScenes(newScene, newSubScene);
778        }
779        if (sceneChanged) {
780            updateCanReceiveFocus();
781            if (isFocusTraversable()) {
782                if (oldScene != null) {
783                    oldScene.unregisterTraversable(Node.this);
784                }
785                if (newScene != null) {
786                    newScene.registerTraversable(Node.this);
787                }
788            }
789            focusSetDirty(oldScene);
790            focusSetDirty(newScene);
791        }
792        scenesChanged(newScene, newSubScene, oldScene, oldSubScene);
793        impl_reapplyCSS();
794
795        if (sceneChanged && !impl_isDirtyEmpty()) {
796            //Note: no need to remove from scene's dirty list
797            //Scene's is checking if the node's scene is correct
798            /* TODO: looks like an existing bug when a node is moved from one
799             * location to another, setScenes will be called twice by
800             * Parent.VetoableListDecorator onProposedChange and onChanged
801             * respectively. Removing the node and setting setScense(null,null)
802             * then adding it back to potentially the same scene. Causing the
803             * same node to being added twice to the same scene.
804             */
805            addToSceneDirtyList();
806        }
807
808        if (newScene == null && peer != null) {
809            peer.release();
810        }
811        if (getParent() == null) {
812            // if we are the root we need to handle scene change
813            parentResolvedOrientationInvalidated();
814        }
815
816        if (sceneChanged) { scene.fireSuperValueChangedEvent(); }
817    }
818
819    final void setScenes(Scene newScene, SubScene newSubScene) {
820        Scene oldScene = sceneProperty().get();
821        if (newScene != oldScene || newSubScene != subScene) {
822            scene.set(newScene);
823            SubScene oldSubScene = subScene;
824            subScene = newSubScene;
825            invalidatedScenes(oldScene, oldSubScene);
826            if (this instanceof SubScene) { // TODO: find better solution
827                SubScene thisSubScene = (SubScene)this;
828                thisSubScene.getRoot().setScenes(newScene, thisSubScene);
829            }
830        }
831    }
832
833    final SubScene getSubScene() {
834        return subScene;
835    }
836
837    public final Scene getScene() {
838        return scene.get();
839    }
840
841    public final ReadOnlyObjectProperty<Scene> sceneProperty() {
842        return scene.getReadOnlyProperty();
843    }
844
845    /**
846     * Exists for Parent and LightBase
847     */
848    void scenesChanged(final Scene newScene, final SubScene newSubScene,
849                       final Scene oldScene, final SubScene oldSubScene) { }
850
851
852    /**
853     * The id of this {@code Node}. This simple string identifier is useful for
854     * finding a specific Node within the scene graph. While the id of a Node
855     * should be unique within the scene graph, this uniqueness is not enforced.
856     * This is analogous to the "id" attribute on an HTML element
857     * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
858     * <p>
859     *     For example, if a Node is given the id of "myId", then the lookup method can
860     *     be used to find this node as follows: <code>scene.lookup("#myId");</code>.
861     * </p>
862     *
863     * @defaultValue null
864     */
865    private StringProperty id;
866
867    public final void setId(String value) {
868        idProperty().set(value);
869    }
870
871    //TODO: this is copied from the property in order to add the @return statement.
872    //      We should have a better, general solution without the need to copy it.
873    /**
874     * The id of this {@code Node}. This simple string identifier is useful for
875     * finding a specific Node within the scene graph. While the id of a Node
876     * should be unique within the scene graph, this uniqueness is not enforced.
877     * This is analogous to the "id" attribute on an HTML element
878     * (<a href="http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier">CSS ID Specification</a>).
879     *
880     * @return the id assigned to this {@code Node} using the {@code setId}
881     *         method or {@code null}, if no id has been assigned.
882     * @defaultValue null
883     */
884    public final String getId() {
885        return id == null ? null : id.get();
886    }
887
888    public final StringProperty idProperty() {
889        if (id == null) {
890            id = new StringPropertyBase() {
891
892                @Override
893                protected void invalidated() {
894                     impl_reapplyCSS();
895                }
896
897                @Override
898                public Object getBean() {
899                    return Node.this;
900                }
901
902                @Override
903                public String getName() {
904                    return "id";
905                }
906            };
907        }
908        return id;
909    }
910
911    /**
912     * A list of String identifiers which can be used to logically group
913     * Nodes, specifically for an external style engine. This variable is
914     * analogous to the "class" attribute on an HTML element and, as such,
915     * each element of the list is a style class to which this Node belongs.
916     *
917     * @see <a href="http://www.w3.org/TR/css3-selectors/#class-html">CSS3 class selectors</a>
918     * @defaultValue null
919     */
920    private ObservableList<String> styleClass = new TrackableObservableList<String>() {
921        @Override
922        protected void onChanged(Change<String> c) {
923            impl_reapplyCSS();
924        }
925
926        @Override
927        public String toString() {
928            if (size() == 0) {
929                return "";
930            } else if (size() == 1) {
931                return get(0);
932            } else {
933                StringBuilder buf = new StringBuilder();
934                for (int i = 0; i < size(); i++) {
935                    buf.append(get(i));
936                    if (i + 1 < size()) {
937                        buf.append(' ');
938                    }
939                }
940                return buf.toString();
941            }
942        }
943    };
944
945    @Override
946    public final ObservableList<String> getStyleClass() {
947        return styleClass;
948    }
949
950    /**
951     * A string representation of the CSS style associated with this
952     * specific {@code Node}. This is analogous to the "style" attribute of an
953     * HTML element. Note that, like the HTML style attribute, this
954     * variable contains style properties and values and not the
955     * selector portion of a style rule.
956     * @defaultValue empty string
957     */
958    private StringProperty style;
959
960    /**
961     * A string representation of the CSS style associated with this
962     * specific {@code Node}. This is analogous to the "style" attribute of an
963     * HTML element. Note that, like the HTML style attribute, this
964     * variable contains style properties and values and not the
965     * selector portion of a style rule.
966     * @param value The inline CSS style to use for this {@code Node}.
967     *         {@code null} is implicitly converted to an empty String.
968     * @defaultValue empty string
969     */
970    public final void setStyle(String value) {
971        styleProperty().set(value);
972    }
973
974    // TODO: javadoc copied from property for the sole purpose of providing a return tag
975    /**
976     * A string representation of the CSS style associated with this
977     * specific {@code Node}. This is analogous to the "style" attribute of an
978     * HTML element. Note that, like the HTML style attribute, this
979     * variable contains style properties and values and not the
980     * selector portion of a style rule.
981     * @defaultValue empty string
982     * @return The inline CSS style associated with this {@code Node}.
983     *         If this {@code Node} does not have an inline style,
984     *         an empty String is returned.
985     */
986    public final String getStyle() {
987        return style == null ? "" : style.get();
988    }
989
990    public final StringProperty styleProperty() {
991        if (style == null) {
992            style = new StringPropertyBase("") {
993
994                @Override public void set(String value) {
995                    // getStyle returns an empty string if the style property
996                    // is null. To be consistent, getStyle should also return
997                    // an empty string when the style property's value is null.
998                    super.set((value != null) ? value : "");
999                }
1000
1001                @Override
1002                protected void invalidated() {
1003
1004                    if (getScene() == null) return;
1005
1006                    // If the style has changed, then styles of this node
1007                    // and child nodes might be affected. So if the cssFlag
1008                    // is not already set to reapply or recalculate, make it so.
1009                    if (cssFlag != CssFlags.REAPPLY ||
1010                            cssFlag != CssFlags.RECALCULATE) {
1011                        cssFlag = CssFlags.RECALCULATE;
1012                        notifyParentsOfInvalidatedCSS();
1013                    }
1014                }
1015
1016                @Override
1017                public Object getBean() {
1018                    return Node.this;
1019                }
1020
1021                @Override
1022                public String getName() {
1023                    return "style";
1024                }
1025            };
1026        }
1027        return style;
1028    }
1029
1030    /**
1031     * Specifies whether this {@code Node} and any subnodes should be rendered
1032     * as part of the scene graph. A node may be visible and yet not be shown
1033     * in the rendered scene if, for instance, it is off the screen or obscured
1034     * by another Node. Invisible nodes never receive mouse events or
1035     * keyboard focus and never maintain keyboard focus when they become
1036     * invisible.
1037     *
1038     * @defaultValue true
1039     */
1040    private BooleanProperty visible;
1041
1042    public final void setVisible(boolean value) {
1043        visibleProperty().set(value);
1044    }
1045
1046    public final boolean isVisible() {
1047        return visible == null ? true : visible.get();
1048    }
1049
1050    public final BooleanProperty visibleProperty() {
1051        if (visible == null) {
1052            visible = new StyleableBooleanProperty(true) {
1053                boolean oldValue = true;
1054                @Override
1055                protected void invalidated() {
1056                    if (oldValue != get()) {
1057                        impl_markDirty(DirtyBits.NODE_VISIBLE);
1058                        impl_geomChanged();
1059                        updateTreeVisible();
1060                        if (getParent() != null) {
1061                            // notify the parent of the potential change in visibility
1062                            // of this node, since visibility affects bounds of the
1063                            // parent node
1064                            getParent().childVisibilityChanged(Node.this);
1065                        }
1066                        oldValue = get();
1067                    }
1068                }
1069
1070                @Override
1071                public CssMetaData getCssMetaData() {
1072                    return StyleableProperties.VISIBILITY;
1073                }
1074
1075                @Override
1076                public Object getBean() {
1077                    return Node.this;
1078                }
1079
1080                @Override
1081                public String getName() {
1082                    return "visible";
1083                }
1084            };
1085        }
1086        return visible;
1087    }
1088
1089    public final void setCursor(Cursor value) {
1090        cursorProperty().set(value);
1091    }
1092
1093    public final Cursor getCursor() {
1094        return (miscProperties == null) ? DEFAULT_CURSOR
1095                                        : miscProperties.getCursor();
1096    }
1097
1098    /**
1099     * Defines the mouse cursor for this {@code Node} and subnodes. If null,
1100     * then the cursor of the first parent node with a non-null cursor will be
1101     * used. If no Node in the scene graph defines a cursor, then the cursor
1102     * of the {@code Scene} will be used.
1103     *
1104     * @defaultValue null
1105     */
1106    public final ObjectProperty<Cursor> cursorProperty() {
1107        return getMiscProperties().cursorProperty();
1108    }
1109
1110    /**
1111     * Specifies how opaque (that is, solid) the {@code Node} appears. A Node
1112     * with 0% opacity is fully translucent. That is, while it is still
1113     * {@link #visibleProperty visible} and rendered, you generally won't be able to see it. The
1114     * exception to this rule is when the {@code Node} is combined with a
1115     * blending mode and blend effect in which case a translucent Node may still
1116     * have an impact in rendering. An opacity of 50% will render the node as
1117     * being 50% transparent.
1118     * <p>
1119     * A {@link #visibleProperty visible} node with any opacity setting still receives mouse
1120     * events and can receive keyboard focus. For example, if you want to have
1121     * a large invisible rectangle overlay all {@code Node}s in the scene graph
1122     * in order to intercept mouse events but not be visible to the user, you could
1123     * create a large {@code Rectangle} that had an opacity of 0%.
1124     * <p>
1125     * Opacity is specified as a value between 0 and 1. Values less than 0 are
1126     * treated as 0, values greater than 1 are treated as 1.
1127     * <p>
1128     * On some platforms ImageView might not support opacity variable.
1129     *
1130     * <p>
1131     * There is a known limitation of mixing opacity < 1.0 with a 3D Transform.
1132     * Opacity/Blending is essentially a 2D image operation. The result of
1133     * an opacity < 1.0 set on a {@link Group} node with 3D transformed children
1134     * will cause its children to be rendered in order without Z-buffering
1135     * applied between those children.
1136     *
1137     * @defaultValue 1.0
1138     */
1139    private DoubleProperty opacity;
1140
1141    public final void setOpacity(double value) {
1142        opacityProperty().set(value);
1143    }
1144    public final double getOpacity() {
1145        return opacity == null ? 1 : opacity.get();
1146    }
1147
1148    public final DoubleProperty opacityProperty() {
1149        if (opacity == null) {
1150            opacity = new StyleableDoubleProperty(1) {
1151
1152                @Override
1153                public void invalidated() {
1154                    impl_markDirty(DirtyBits.NODE_OPACITY);
1155                }
1156
1157                @Override
1158                public CssMetaData getCssMetaData() {
1159                    return StyleableProperties.OPACITY;
1160                }
1161
1162                @Override
1163                public Object getBean() {
1164                    return Node.this;
1165                }
1166
1167                @Override
1168                public String getName() {
1169                    return "opacity";
1170                }
1171            };
1172        }
1173        return opacity;
1174    }
1175
1176    /**
1177     * The {@link javafx.scene.effect.BlendMode} used to blend this individual node
1178     * into the scene behind it. If this node happens to be a Group then all of the
1179     * children will be composited individually into a temporary buffer using their
1180     * own blend modes and then that temporary buffer will be composited into the
1181     * scene using the specified blend mode.
1182     *
1183     * A value of {@code null} is treated as pass-though this means no effect on a
1184     * parent such as a Group and the equivalent of SRC_OVER for a single Node.
1185     *
1186     * @defaultValue null
1187     */
1188    private javafx.beans.property.ObjectProperty<BlendMode> blendMode;
1189
1190    public final void setBlendMode(BlendMode value) {
1191        blendModeProperty().set(value);
1192    }
1193    public final BlendMode getBlendMode() {
1194        return blendMode == null ? null : blendMode.get();
1195    }
1196
1197    public final ObjectProperty<BlendMode> blendModeProperty() {
1198        if (blendMode == null) {
1199            blendMode = new StyleableObjectProperty<BlendMode>(null) {
1200                @Override public void invalidated() {
1201                    impl_markDirty(DirtyBits.NODE_BLENDMODE);
1202                }
1203
1204                @Override
1205                public CssMetaData getCssMetaData() {
1206                    return StyleableProperties.BLEND_MODE;
1207                }
1208
1209                @Override
1210                public Object getBean() {
1211                    return Node.this;
1212                }
1213
1214                @Override
1215                public String getName() {
1216                    return "blendMode";
1217                }
1218            };
1219        }
1220        return blendMode;
1221    }
1222
1223    public final void setClip(Node value) {
1224        clipProperty().set(value);
1225    }
1226
1227    public final Node getClip() {
1228        return (miscProperties == null) ? DEFAULT_CLIP
1229                                        : miscProperties.getClip();
1230    }
1231
1232    /**
1233     * Specifies a {@code Node} to use to define the the clipping shape for this
1234     * Node. This clipping Node is not a child of this {@code Node} in the scene
1235     * graph sense. Rather, it is used to define the clip for this {@code Node}.
1236     * <p>
1237     * For example, you can use an {@link javafx.scene.image.ImageView} Node as
1238     * a mask to represent the Clip. Or you could use one of the geometric shape
1239     * Nodes such as {@link javafx.scene.shape.Rectangle} or
1240     * {@link javafx.scene.shape.Circle}. Or you could use a
1241     * {@link javafx.scene.text.Text} node to represent the Clip.
1242     * <p>
1243     * See the class documentation for {@link Node} for scene graph structure
1244     * restrictions on setting the clip. If these restrictions are violated by
1245     * a change to the clip variable, the change is ignored and the
1246     * previous value of the clip variable is restored.
1247     * <p>
1248     * Note that this is a conditional feature. See
1249     * {@link javafx.application.ConditionalFeature#SHAPE_CLIP ConditionalFeature.SHAPE_CLIP}
1250     * for more information.
1251     * <p>
1252     * There is a known limitation of mixing Clip with a 3D Transform.
1253     * Clipping is essentially a 2D image operation. The result of
1254     * a Clip set on a {@link Group} node with 3D transformed children
1255     * will cause its children to be rendered in order without Z-buffering
1256     * applied between those children.
1257     *
1258     * @defaultValue null
1259     */
1260    public final ObjectProperty<Node> clipProperty() {
1261        return getMiscProperties().clipProperty();
1262    }
1263
1264    private com.sun.javafx.sg.PGNode.CacheHint toPGCacheHint(CacheHint ch) {
1265        if (ch == CacheHint.DEFAULT) {
1266            return PGNode.CacheHint.DEFAULT;
1267        } else if (ch == CacheHint.SCALE) {
1268            return PGNode.CacheHint.SCALE;
1269        } else if (ch == CacheHint.ROTATE) {
1270            return PGNode.CacheHint.ROTATE;
1271        } else if (ch == CacheHint.SCALE_AND_ROTATE) {
1272            return PGNode.CacheHint.SCALE_AND_ROTATE;
1273        } else if (ch == CacheHint.SPEED) {
1274            return PGNode.CacheHint.SCALE_AND_ROTATE;
1275        } else if (ch == CacheHint.QUALITY) {
1276            return PGNode.CacheHint.DEFAULT;
1277        } else { // impossible
1278            return PGNode.CacheHint.DEFAULT;
1279        }
1280    }
1281
1282    public final void setCache(boolean value) {
1283        cacheProperty().set(value);
1284    }
1285
1286    public final boolean isCache() {
1287        return (miscProperties == null) ? DEFAULT_CACHE
1288                                        : miscProperties.isCache();
1289    }
1290
1291    /**
1292     * A performance hint to the system to indicate that this {@code Node}
1293     * should be cached as a bitmap. Rendering a bitmap representation of a node
1294     * will be faster than rendering primitives in many cases, especially in the
1295     * case of primitives with effects applied (such as a blur). However, it
1296     * also increases memory usage. This hint indicates whether that trade-off
1297     * (increased memory usage for increased performance) is worthwhile. Also
1298     * note that on some platforms such as GPU accelerated platforms there is
1299     * little benefit to caching Nodes as bitmaps when blurs and other effects
1300     * are used since they are very fast to render on the GPU.
1301     *
1302     * The {@link #cacheHintProperty} variable provides additional options for enabling
1303     * more aggressive bitmap caching.
1304     *
1305     * <p>
1306     * Caching may be disabled for any node that has a 3D transform on itself,
1307     * any of its ancestors, or any of its descendants.
1308     *
1309     * @see #cacheHintProperty
1310     * @defaultValue false
1311     */
1312    public final BooleanProperty cacheProperty() {
1313        return getMiscProperties().cacheProperty();
1314    }
1315
1316    public final void setCacheHint(CacheHint value) {
1317        cacheHintProperty().set(value);
1318    }
1319
1320    public final CacheHint getCacheHint() {
1321        return (miscProperties == null) ? DEFAULT_CACHE_HINT
1322                                        : miscProperties.getCacheHint();
1323    }
1324
1325    /**
1326     * Additional hint for controlling bitmap caching.
1327     * <p>
1328     * Under certain circumstances, such as animating nodes that are very
1329     * expensive to render, it is desirable to be able to perform
1330     * transformations on the node without having to regenerate the cached
1331     * bitmap.  An option in such cases is to perform the transforms on the
1332     * cached bitmap itself.
1333     * <p>
1334     * This technique can provide a dramatic improvement to animation
1335     * performance, though may also result in a reduction in visual quality.
1336     * The {@code cacheHint} variable provides a hint to the system about how
1337     * and when that trade-off (visual quality for animation performance) is
1338     * acceptable.
1339     * <p>
1340     * It is possible to enable the cacheHint only at times when your node is
1341     * animating.  In this way, expensive nodes can appear on screen with full
1342     * visual quality, yet still animate smoothly.
1343     * <p>
1344     * Example:
1345     * <pre><code>
1346        expensiveNode.setCache(true);
1347        expensiveNode.setCacheHint(CacheHint.QUALITY);
1348        ...
1349        // Do an animation
1350        expensiveNode.setCacheHint(CacheHint.SPEED);
1351        new Timeline(
1352            new KeyFrame(Duration.seconds(2),
1353                new KeyValue(expensiveNode.scaleXProperty(), 2.0),
1354                new KeyValue(expensiveNode.scaleYProperty(), 2.0),
1355                new KeyValue(expensiveNode.rotateProperty(), 360),
1356                new KeyValue(expensiveNode.cacheHintProperty(), CacheHint.QUALITY)
1357            )
1358        ).play();
1359     </code></pre>
1360     *
1361     * Note that {@code cacheHint} is only a hint to the system.  Depending on
1362     * the details of the node or the transform, this hint may be ignored.
1363     *
1364     * <p>
1365     * If {@code Node.cache} is false, cacheHint is ignored.
1366     * Caching may be disabled for any node that has a 3D transform on itself,
1367     * any of its ancestors, or any of its descendants.
1368     *
1369     * @see #cacheProperty
1370     * @since JavaFX 1.3
1371     * @defaultValue CacheHint.DEFAULT
1372     */
1373    public final ObjectProperty<CacheHint> cacheHintProperty() {
1374        return getMiscProperties().cacheHintProperty();
1375    }
1376
1377    public final void setEffect(Effect value) {
1378        effectProperty().set(value);
1379    }
1380
1381    public final Effect getEffect() {
1382        return (miscProperties == null) ? DEFAULT_EFFECT
1383                                        : miscProperties.getEffect();
1384    }
1385
1386    /**
1387     * Specifies an effect to apply to this {@code Node}.
1388     * <p>
1389     * Note that this is a conditional feature. See
1390     * {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT}
1391     * for more information.
1392     *
1393     * <p>
1394     * There is a known limitation of mixing Effect with a 3D Transform. Effect is
1395     * essentially a 2D image operation. The result of an Effect set on
1396     * a {@link Group} node with 3D transformed children will cause its children
1397     * to be rendered in order without Z-buffering applied between those
1398     * children.
1399     *
1400     * @defaultValue null
1401     */
1402    public final ObjectProperty<Effect> effectProperty() {
1403        return getMiscProperties().effectProperty();
1404    }
1405
1406    public final void setDepthTest(DepthTest value) {
1407        depthTestProperty().set(value);
1408    }
1409
1410    public final DepthTest getDepthTest() {
1411        return (miscProperties == null) ? DEFAULT_DEPTH_TEST
1412                                        : miscProperties.getDepthTest();
1413    }
1414
1415    /**
1416     * Indicates whether depth testing is used when rendering this node.
1417     * If the depthTest flag is {@code DepthTest.DISABLE}, then depth testing
1418     * is disabled for this node.
1419     * If the depthTest flag is {@code DepthTest.ENABLE}, then depth testing
1420     * is enabled for this node.
1421     * If the depthTest flag is {@code DepthTest.INHERIT}, then depth testing
1422     * is enabled for this node if it is enabled for the parent node or the
1423     * parent node is null.
1424     * <p>
1425     * The depthTest flag is only used when the depthBuffer flag for
1426     * the {@link Scene} is true (meaning that the
1427     * {@link Scene} has an associated depth buffer)
1428     * <p>
1429     * Depth test comparison is only done among nodes with depthTest enabled.
1430     * A node with depthTest disabled does not read, test, or write the depth buffer,
1431     * that is to say its Z value will not be considered for depth testing
1432     * with other nodes.
1433     * <p>
1434     * Note that this is a conditional feature. See
1435     * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
1436     * for more information.
1437     *<p>
1438     * See the constructor in Scene with depthBuffer as one of its input
1439     * arguments.
1440     *
1441     * @see javafx.scene.Scene
1442     * @defaultValue INHERIT
1443     */
1444    public final ObjectProperty<DepthTest> depthTestProperty() {
1445        return getMiscProperties().depthTestProperty();
1446    }
1447
1448    /**
1449     * Recompute the derived depth test flag. This flag is true
1450     * if the depthTest flag for this node is true and the
1451     * depth test flag for each ancestor node is true. It is false
1452     * otherwise. Equivalently, the derived depth flag is true
1453     * if the depthTest flag for this node is true and the derivedDepthTest
1454     * flag for its parent is true.
1455     */
1456    void computeDerivedDepthTest() {
1457        boolean newDDT;
1458        if (getDepthTest() == DepthTest.INHERIT) {
1459            if (getParent() != null) {
1460                newDDT = getParent().isDerivedDepthTest();
1461            } else {
1462                newDDT = true;
1463            }
1464        } else if (getDepthTest() == DepthTest.ENABLE) {
1465            newDDT = true;
1466        } else {
1467            newDDT = false;
1468        }
1469
1470        if (isDerivedDepthTest() != newDDT) {
1471            impl_markDirty(DirtyBits.NODE_DEPTH_TEST);
1472            setDerivedDepthTest(newDDT);
1473        }
1474    }
1475
1476    // This is the derived depthTest value to pass to PG level
1477    private boolean derivedDepthTest = true;
1478
1479    void setDerivedDepthTest(boolean value) {
1480        derivedDepthTest = value;
1481    }
1482
1483    boolean isDerivedDepthTest() {
1484        return derivedDepthTest;
1485    }
1486
1487    public final void setDisable(boolean value) {
1488        disableProperty().set(value);
1489    }
1490
1491    public final boolean isDisable() {
1492        return (miscProperties == null) ? DEFAULT_DISABLE
1493                                        : miscProperties.isDisable();
1494    }
1495
1496    /**
1497     * Defines the individual disabled state of this {@code Node}. Setting
1498     * {@code disable} to true will cause this {@code Node} and any subnodes to
1499     * become disabled. This property should be used only to set the disabled
1500     * state of a {@code Node}.  For querying the disabled state of a
1501     * {@code Node}, the {@link #disabledProperty disabled} property should instead be used,
1502     * since it is possible that a {@code Node} was disabled as a result of an
1503     * ancestor being disabled even if the individual {@code disable} state on
1504     * this {@code Node} is {@code false}.
1505     *
1506     * @defaultValue false
1507     */
1508    public final BooleanProperty disableProperty() {
1509        return getMiscProperties().disableProperty();
1510    }
1511
1512    /**************************************************************************
1513     *                                                                        *
1514     *
1515     *                                                                        *
1516     *************************************************************************/
1517    /**
1518     * Defines how the picking computation is done for this node when
1519     * triggered by a {@code MouseEvent} or a {@code contains} function call.
1520     *
1521     * If {@code pickOnBounds} is true, then picking is computed by
1522     * intersecting with the bounds of this node, else picking is computed
1523     * by intersecting with the geometric shape of this node.
1524     *
1525     * @defaultValue false
1526     * @since JavaFX 1.3
1527     */
1528    private BooleanProperty pickOnBounds;
1529
1530    public final void setPickOnBounds(boolean value) {
1531        pickOnBoundsProperty().set(value);
1532    }
1533
1534    public final boolean isPickOnBounds() {
1535        return pickOnBounds == null ? false : pickOnBounds.get();
1536    }
1537
1538    public final BooleanProperty pickOnBoundsProperty() {
1539        if (pickOnBounds == null) {
1540            pickOnBounds = new SimpleBooleanProperty(this, "pickOnBounds");
1541        }
1542        return pickOnBounds;
1543    }
1544
1545    /**
1546     * Indicates whether or not this {@code Node} is disabled.  A {@code Node}
1547     * will become disabled if {@link #disableProperty disable} is set to {@code true} on either
1548     * itself or one of its ancestors in the scene graph.
1549     * <p>
1550     * A disabled {@code Node} should render itself differently to indicate its
1551     * disabled state to the user.
1552     * Such disabled rendering is dependent on the implementation of the
1553     * {@code Node}. The shape classes contained in {@code javafx.scene.shape}
1554     * do not implement such rendering by default, therefore applications using
1555     * shapes for handling input must implement appropriate disabled rendering
1556     * themselves. The user-interface controls defined in
1557     * {@code javafx.scene.control} will implement disabled-sensitive rendering,
1558     * however.
1559     * <p>
1560     * A disabled {@code Node} does not receive mouse or key events.
1561     *
1562     * @defaultValue false
1563     */
1564    private ReadOnlyBooleanWrapper disabled;
1565
1566    protected final void setDisabled(boolean value) {
1567        disabledPropertyImpl().set(value);
1568    }
1569
1570    public final boolean isDisabled() {
1571        return disabled == null ? false : disabled.get();
1572    }
1573
1574    public final ReadOnlyBooleanProperty disabledProperty() {
1575        return disabledPropertyImpl().getReadOnlyProperty();
1576    }
1577
1578    private ReadOnlyBooleanWrapper disabledPropertyImpl() {
1579        if (disabled == null) {
1580            disabled = new ReadOnlyBooleanWrapper() {
1581
1582                @Override
1583                protected void invalidated() {
1584                    pseudoClassStateChanged(DISABLED_PSEUDOCLASS_STATE, get());
1585                    updateCanReceiveFocus();
1586                    focusSetDirty(getScene());
1587                }
1588
1589                @Override
1590                public Object getBean() {
1591                    return Node.this;
1592                }
1593
1594                @Override
1595                public String getName() {
1596                    return "disabled";
1597                }
1598            };
1599        }
1600        return disabled;
1601    }
1602
1603    private void updateDisabled() {
1604        boolean isDisabled = isDisable();
1605        if (!isDisabled) {
1606            isDisabled = getParent() != null ? getParent().isDisabled() :
1607                    getSubScene() != null && getSubScene().isDisabled();
1608        }
1609        setDisabled(isDisabled);
1610        if (this instanceof SubScene) {
1611            ((SubScene)this).getRoot().setDisabled(isDisabled);
1612        }
1613    }
1614
1615    /**
1616     * Finds this {@code Node}, or the first sub-node, based on the given CSS selector.
1617     * If this node is a {@code Parent}, then this function will traverse down
1618     * into the branch until it finds a match. If more than one sub-node matches the
1619     * specified selector, this function returns the first of them.
1620     * <p>
1621     *     For example, if a Node is given the id of "myId", then the lookup method can
1622     *     be used to find this node as follows: <code>scene.lookup("#myId");</code>.
1623     * </p>
1624     *
1625     * @param selector The css selector of the node to find
1626     * @return The first node, starting from this {@code Node}, which matches
1627     *         the CSS {@code selector}, null if none is found.
1628     */
1629    public Node lookup(String selector) {
1630        if (selector == null) return null;
1631        Selector s = Selector.createSelector(selector);
1632        return s != null && s.applies(this) ? this : null;
1633    }
1634
1635    /**
1636     * Finds all {@code Node}s, including this one and any children, which match
1637     * the given CSS selector. If no matches are found, an empty unmodifiable set is
1638     * returned. The set is explicitly unordered.
1639     *
1640     * @param selector The css selector of the nodes to find
1641     * @return All nodes, starting from and including this {@code Node}, which match
1642     *         the CSS {@code selector}. The returned set is always unordered and
1643     *         unmodifiable, and never null.
1644     */
1645    public Set<Node> lookupAll(String selector) {
1646        final Selector s = Selector.createSelector(selector);
1647        final Set<Node> empty = Collections.emptySet();
1648        if (s == null) return empty;
1649        List<Node> results = lookupAll(s, null);
1650        return results == null ? empty : new UnmodifiableListSet<Node>(results);
1651    }
1652
1653    /**
1654     * Used by Node and Parent for traversing the tree and adding all nodes which
1655     * match the given selector.
1656     *
1657     * @param selector The Selector. This will never be null.
1658     * @param results The results. This will never be null.
1659     */
1660    List<Node> lookupAll(Selector selector, List<Node> results) {
1661        if (selector.applies(this)) {
1662            // Lazily create the set to reduce some trash.
1663            if (results == null) {
1664                results = new LinkedList<Node>();
1665            }
1666            results.add(this);
1667        }
1668        return results;
1669    }
1670
1671    /**
1672     * Moves this {@code Node} to the back of its sibling nodes in terms of
1673     * z-order.  This is accomplished by moving this {@code Node} to the
1674     * first position in its parent's {@code content} ObservableList.
1675     * This function has no effect if this {@code Node} is not part of a group.
1676     */
1677    public void toBack() {
1678        if (getParent() != null) {
1679            getParent().impl_toBack(this);
1680        }
1681    }
1682
1683    /**
1684     * Moves this {@code Node} to the front of its sibling nodes in terms of
1685     * z-order.  This is accomplished by moving this {@code Node} to the
1686     * last position in its parent's {@code content} ObservableList.
1687     * This function has no effect if this {@code Node} is not part of a group.
1688     */
1689    public void toFront() {
1690        if (getParent() != null) {
1691            getParent().impl_toFront(this);
1692        }
1693    }
1694
1695    // TODO: need to verify whether this is OK to do starting from a node in
1696    // the scene graph other than the root.
1697    private void doCSSPass() {
1698        if (this.cssFlag != CssFlags.CLEAN) {
1699            // The dirty bit isn't checked but we must ensure it is cleared.
1700            // The cssFlag is set to clean in either Node.processCSS or
1701            // Node.impl_processCSS(boolean)
1702
1703            // Don't clear the dirty bit in case it will cause problems
1704            // with a full CSS pass on the scene.
1705            // TODO: is this the right thing to do?
1706            // this.impl_clearDirty(com.sun.javafx.scene.DirtyBits.NODE_CSS);
1707
1708            this.processCSS();
1709        }
1710    }
1711
1712    /**
1713     * Recursive function for synchronizing a node and all descendents
1714     */
1715    private static void syncAll(Node node) {
1716        node.impl_syncPGNode();
1717        if (node instanceof Parent) {
1718            Parent p = (Parent) node;
1719            final int childrenCount = p.getChildren().size();
1720
1721            for (int i = 0; i < childrenCount; i++) {
1722                Node n = p.getChildren().get(i);
1723                if (n != null) {
1724                    syncAll(n);
1725                }
1726            }
1727        }
1728        if (node.getClip() != null) {
1729            syncAll(node.getClip());
1730        }
1731    }
1732
1733    private void doLayoutPass() {
1734        if (this instanceof Parent) {
1735            // TODO: As an optimization we only need to layout those dirty
1736            // roots that are descendents of this node
1737            Parent p = (Parent)this;
1738            for (int i = 0; i < 3; i++) {
1739                p.layout();
1740            }
1741        }
1742    }
1743
1744    private void doCSSLayoutSyncForSnapshot() {
1745        doCSSPass();
1746        doLayoutPass();
1747        updateBounds();
1748        Scene.impl_setAllowPGAccess(true);
1749        syncAll(this);
1750        Scene.impl_setAllowPGAccess(false);
1751    }
1752
1753    private WritableImage doSnapshot(SnapshotParameters params, WritableImage img) {
1754        if (getScene() != null) {
1755            getScene().doCSSLayoutSyncForSnapshot(this);
1756        } else {
1757            doCSSLayoutSyncForSnapshot();
1758        }
1759
1760        BaseTransform transform = BaseTransform.IDENTITY_TRANSFORM;
1761        if (params.getTransform() != null) {
1762            Affine3D tempTx = new Affine3D();
1763            params.getTransform().impl_apply(tempTx);
1764            transform = tempTx;
1765        }
1766        double x;
1767        double y;
1768        double w;
1769        double h;
1770        Rectangle2D viewport = params.getViewport();
1771        if (viewport != null) {
1772            // Use the specified viewport
1773            x = viewport.getMinX();
1774            y = viewport.getMinY();
1775            w = viewport.getWidth();
1776            h = viewport.getHeight();
1777        } else {
1778            // Get the bounds in parent of this node, transformed by the
1779            // specified transform.
1780            BaseBounds tempBounds = TempState.getInstance().bounds;
1781            tempBounds = getTransformedBounds(tempBounds, transform);
1782            x = tempBounds.getMinX();
1783            y = tempBounds.getMinY();
1784            w = tempBounds.getWidth();
1785            h = tempBounds.getHeight();
1786        }
1787        WritableImage result = Scene.doSnapshot(getScene(), x, y, w, h,
1788                this, transform, params.isDepthBufferInternal(),
1789                params.getFill(), params.getEffectiveCamera(), img);
1790
1791        return result;
1792    }
1793
1794    /**
1795     * Takes a snapshot of this node and returns the rendered image when
1796     * it is ready.
1797     * CSS and layout processing will be done for the node, and any of its
1798     * children, prior to rendering it.
1799     * The entire destination image is cleared to the fill {@code Paint}
1800     * specified by the SnapshotParameters. This node is then rendered to
1801     * the image.
1802     * If the viewport specified by the SnapshotParameters is null, the
1803     * upper-left pixel of the {@code boundsInParent} of this
1804     * node, after first applying the transform specified by the
1805     * SnapshotParameters,
1806     * is mapped to the upper-left pixel (0,0) in the image.
1807     * If a non-null viewport is specified,
1808     * the upper-left pixel of the viewport is mapped to upper-left pixel
1809     * (0,0) in the image.
1810     * In both cases, this mapping to (0,0) of the image is done with an integer
1811     * translation. The portion of the node that is outside of the rendered
1812     * image will be clipped by the image.
1813     *
1814     * <p>
1815     * When taking a snapshot of a scene that is being animated, either
1816     * explicitly by the application or implicitly (such as chart animation),
1817     * the snapshot will be rendered based on the state of the scene graph at
1818     * the moment the snapshot is taken and will not reflect any subsequent
1819     * animation changes.
1820     * </p>
1821     *
1822     * <p>
1823     * NOTE: In order for CSS and layout to function correctly, the node
1824     * must be part of a Scene (the Scene may be attached to a Stage, but need
1825     * not be).
1826     * </p>
1827     *
1828     * @param params the snapshot parameters containing attributes that
1829     * will control the rendering. If the SnapshotParameters object is null,
1830     * then the Scene's attributes will be used if this node is part of a scene,
1831     * or default attributes will be used if this node is not part of a scene.
1832     *
1833     * @param image the writable image that will be used to hold the rendered node.
1834     * It may be null in which case a new WritableImage will be constructed.
1835     * The new image is constructed using integer width and
1836     * height values that are derived either from the transformed bounds of this
1837     * Node or from the size of the viewport as specified in the
1838     * SnapShotParameters. These integer values are chosen such that the image
1839     * will wholly contain the bounds of this Node or the specified viewport.
1840     * If the image is non-null, the node will be rendered into the
1841     * existing image.
1842     * In this case, the width and height of the image determine the area
1843     * that is rendered instead of the width and height of the bounds or
1844     * viewport.
1845     *
1846     * @throws IllegalStateException if this method is called on a thread
1847     *     other than the JavaFX Application Thread.
1848     *
1849     * @return the rendered image
1850     * @since 2.2
1851     */
1852    public WritableImage snapshot(SnapshotParameters params, WritableImage image) {
1853        Toolkit.getToolkit().checkFxUserThread();
1854
1855        if (params == null) {
1856            params = new SnapshotParameters();
1857            Scene s = getScene();
1858            if (s != null) {
1859                params.setCamera(s.getEffectiveCamera());
1860                params.setDepthBuffer(s.isDepthBufferInteral());
1861                params.setFill(s.getFill());
1862            }
1863        }
1864
1865        return doSnapshot(params, image);
1866    }
1867
1868    /**
1869     * Takes a snapshot of this node at the next frame and calls the
1870     * specified callback method when the image is ready.
1871     * CSS and layout processing will be done for the node, and any of its
1872     * children, prior to rendering it.
1873     * The entire destination image is cleared to the fill {@code Paint}
1874     * specified by the SnapshotParameters. This node is then rendered to
1875     * the image.
1876     * If the viewport specified by the SnapshotParameters is null, the
1877     * upper-left pixel of the {@code boundsInParent} of this
1878     * node, after first applying the transform specified by the
1879     * SnapshotParameters,
1880     * is mapped to the upper-left pixel (0,0) in the image.
1881     * If a non-null viewport is specified,
1882     * the upper-left pixel of the viewport is mapped to upper-left pixel
1883     * (0,0) in the image.
1884     * In both cases, this mapping to (0,0) of the image is done with an integer
1885     * translation. The portion of the node that is outside of the rendered
1886     * image will be clipped by the image.
1887     *
1888     * <p>
1889     * This is an asynchronous call, which means that other
1890     * events or animation might be processed before the node is rendered.
1891     * If any such events modify the node, or any of its children, that
1892     * modification will be reflected in the rendered image (just like it
1893     * will also be reflected in the frame rendered to the Stage, if this node
1894     * is part of a live scene graph).
1895     * </p>
1896     *
1897     * <p>
1898     * When taking a snapshot of a node that is being animated, either
1899     * explicitly by the application or implicitly (such as chart animation),
1900     * the snapshot will be rendered based on the state of the scene graph at
1901     * the moment the snapshot is taken and will not reflect any subsequent
1902     * animation changes.
1903     * </p>
1904     *
1905     * <p>
1906     * NOTE: In order for CSS and layout to function correctly, the node
1907     * must be part of a Scene (the Scene may be attached to a Stage, but need
1908     * not be).
1909     * </p>
1910     *
1911     * @param callback a class whose call method will be called when the image
1912     * is ready. The SnapshotResult that is passed into the call method of
1913     * the callback will contain the rendered image, the source node
1914     * that was rendered, and a copy of the SnapshotParameters.
1915     * The callback parameter must not be null.
1916     *
1917     * @param params the snapshot parameters containing attributes that
1918     * will control the rendering. If the SnapshotParameters object is null,
1919     * then the Scene's attributes will be used if this node is part of a scene,
1920     * or default attributes will be used if this node is not part of a scene.
1921     *
1922     * @param image the writable image that will be used to hold the rendered node.
1923     * It may be null in which case a new WritableImage will be constructed.
1924     * The new image is constructed using integer width and
1925     * height values that are derived either from the transformed bounds of this
1926     * Node or from the size of the viewport as specified in the
1927     * SnapShotParameters. These integer values are chosen such that the image
1928     * will wholly contain the bounds of this Node or the specified viewport.
1929     * If the image is non-null, the node will be rendered into the
1930     * existing image.
1931     * In this case, the width and height of the image determine the area
1932     * that is rendered instead of the width and height of the bounds or
1933     * viewport.
1934     *
1935     * @throws IllegalStateException if this method is called on a thread
1936     *     other than the JavaFX Application Thread.
1937     *
1938     * @throws NullPointerException if the callback parameter is null.
1939     * @since 2.2
1940     */
1941    public void snapshot(Callback<SnapshotResult, Void> callback,
1942            SnapshotParameters params, WritableImage image) {
1943
1944        Toolkit.getToolkit().checkFxUserThread();
1945        if (callback == null) {
1946            throw new NullPointerException("The callback must not be null");
1947        }
1948
1949        if (params == null) {
1950            params = new SnapshotParameters();
1951            Scene s = getScene();
1952            if (s != null) {
1953                params.setCamera(s.getEffectiveCamera());
1954                params.setDepthBuffer(s.isDepthBufferInteral());
1955                params.setFill(s.getFill());
1956            }
1957        } else {
1958            params = params.copy();
1959        }
1960
1961        final SnapshotParameters theParams = params;
1962        final Callback<SnapshotResult, Void> theCallback = callback;
1963        final WritableImage theImage = image;
1964
1965        // Create a deferred runnable that will be run from a pulse listener
1966        // that is called after all of the scenes have been synced but before
1967        // any of them have been rendered.
1968        final Runnable snapshotRunnable = new Runnable() {
1969            @Override public void run() {
1970                WritableImage img = doSnapshot(theParams, theImage);
1971                SnapshotResult result = new SnapshotResult(img, Node.this, theParams);
1972//                System.err.println("Calling snapshot callback");
1973                try {
1974                    Void v = theCallback.call(result);
1975                } catch (Throwable th) {
1976                    System.err.println("Exception in snapshot callback");
1977                    th.printStackTrace(System.err);
1978                }
1979            }
1980        };
1981
1982//        System.err.println("Schedule a snapshot in the future");
1983        Scene.addSnapshotRunnable(snapshotRunnable);
1984    }
1985
1986    /* ************************************************************************
1987     *                                                                        *
1988     *
1989     *                                                                        *
1990     *************************************************************************/
1991
1992    public final void setOnDragEntered(
1993            EventHandler<? super DragEvent> value) {
1994        onDragEnteredProperty().set(value);
1995    }
1996
1997    public final EventHandler<? super DragEvent> getOnDragEntered() {
1998        return (eventHandlerProperties == null)
1999                ? null : eventHandlerProperties.getOnDragEntered();
2000    }
2001
2002    /**
2003     * Defines a function to be called when drag gesture
2004     * enters this {@code Node}.
2005     */
2006    public final ObjectProperty<EventHandler<? super DragEvent>>
2007            onDragEnteredProperty() {
2008        return getEventHandlerProperties().onDragEnteredProperty();
2009    }
2010
2011    public final void setOnDragExited(
2012            EventHandler<? super DragEvent> value) {
2013        onDragExitedProperty().set(value);
2014    }
2015
2016    public final EventHandler<? super DragEvent> getOnDragExited() {
2017        return (eventHandlerProperties == null)
2018                ? null : eventHandlerProperties.getOnDragExited();
2019    }
2020
2021    /**
2022     * Defines a function to be called when drag gesture
2023     * exits this {@code Node}.
2024     */
2025    public final ObjectProperty<EventHandler<? super DragEvent>>
2026            onDragExitedProperty() {
2027        return getEventHandlerProperties().onDragExitedProperty();
2028    }
2029
2030    public final void setOnDragOver(
2031            EventHandler<? super DragEvent> value) {
2032        onDragOverProperty().set(value);
2033    }
2034
2035    public final EventHandler<? super DragEvent> getOnDragOver() {
2036        return (eventHandlerProperties == null)
2037                ? null : eventHandlerProperties.getOnDragOver();
2038    }
2039
2040    /**
2041     * Defines a function to be called when drag gesture progresses within
2042     * this {@code Node}.
2043     */
2044    public final ObjectProperty<EventHandler<? super DragEvent>>
2045            onDragOverProperty() {
2046        return getEventHandlerProperties().onDragOverProperty();
2047    }
2048
2049    // Do we want DRAG_TRANSFER_MODE_CHANGED event?
2050//    public final void setOnDragTransferModeChanged(
2051//            EventHandler<? super DragEvent> value) {
2052//        onDragTransferModeChangedProperty().set(value);
2053//    }
2054//
2055//    public final EventHandler<? super DragEvent> getOnDragTransferModeChanged() {
2056//        return (eventHandlerProperties == null)
2057//                ? null : eventHandlerProperties.getOnDragTransferModeChanged();
2058//    }
2059//
2060//    /**
2061//     * Defines a function to be called this {@code Node} if it is a potential
2062//     * drag-and-drop target when the user takes action to change the intended
2063//     * {@code TransferMode}.
2064//     * The user can change the intended {@link TransferMode} by holding down
2065//     * or releasing key modifiers.
2066//     */
2067//    public final ObjectProperty<EventHandler<? super DragEvent>>
2068//            onDragTransferModeChangedProperty() {
2069//        return getEventHandlerProperties().onDragTransferModeChangedProperty();
2070//    }
2071
2072    public final void setOnDragDropped(
2073            EventHandler<? super DragEvent> value) {
2074        onDragDroppedProperty().set(value);
2075    }
2076
2077    public final EventHandler<? super DragEvent> getOnDragDropped() {
2078        return (eventHandlerProperties == null)
2079                ? null : eventHandlerProperties.getOnDragDropped();
2080    }
2081
2082    /**
2083     * Defines a function to be called when the mouse button is released
2084     * on this {@code Node} during drag and drop gesture. Transfer of data from
2085     * the {@link DragEvent}'s {@link DragEvent#dragboard dragboard} should
2086     * happen in this function.
2087     */
2088    public final ObjectProperty<EventHandler<? super DragEvent>>
2089            onDragDroppedProperty() {
2090        return getEventHandlerProperties().onDragDroppedProperty();
2091    }
2092
2093    public final void setOnDragDone(
2094            EventHandler<? super DragEvent> value) {
2095        onDragDoneProperty().set(value);
2096    }
2097
2098    public final EventHandler<? super DragEvent> getOnDragDone() {
2099        return (eventHandlerProperties == null)
2100                ? null : eventHandlerProperties.getOnDragDone();
2101    }
2102
2103    /**
2104     * Defines a function to be called when this {@code Node} is a
2105     * drag and drop gesture source after its data has
2106     * been dropped on a drop target. The {@code transferMode} of the
2107     * event shows what just happened at the drop target.
2108     * If {@code transferMode} has the value {@code MOVE}, then the source can
2109     * clear out its data. Clearing the source's data gives the appropriate
2110     * appearance to a user that the data has been moved by the drag and drop
2111     * gesture. A {@code transferMode} that has the value {@code NONE}
2112     * indicates that no data was transferred during the drag and drop gesture.
2113     */
2114    public final ObjectProperty<EventHandler<? super DragEvent>>
2115            onDragDoneProperty() {
2116        return getEventHandlerProperties().onDragDoneProperty();
2117    }
2118
2119    /**
2120     * Confirms a potential drag and drop gesture that is recognized over this
2121     * {@code Node}.
2122     * Can be called only from a DRAG_DETECTED event handler. The returned
2123     * {@link Dragboard} is used to transfer data during
2124     * the drag and drop gesture. Placing this {@code Node}'s data on the
2125     * {@link Dragboard} also identifies this {@code Node} as the source of
2126     * the drag and drop gesture.
2127     * More detail about drag and drop gestures is described in the overivew
2128     * of {@link DragEvent}.
2129     *
2130     * @see DragEvent
2131     * @param transferModes The supported {@code TransferMode}(s) of this {@code Node}
2132     * @return A {@code Dragboard} to place this {@code Node}'s data on
2133     * @throws IllegalStateException if drag and drop cannot be started at this
2134     * moment (it's called outside of {@code DRAG_DETECTED} event handling or
2135     * this node is not in scene).
2136     */
2137    public Dragboard startDragAndDrop(TransferMode... transferModes) {
2138        if (getScene() != null) {
2139            return getScene().startDragAndDrop(this, transferModes);
2140        }
2141
2142        throw new IllegalStateException("Cannot start drag and drop on node "
2143                + "that is not in scene");
2144    }
2145
2146    /**
2147     * Starts a full press-drag-release gesture with this node as gesture
2148     * source. This method can be called only from a {@code DRAG_DETECTED} mouse
2149     * event handler. More detail about dragging gestures can be found
2150     * in the overview of {@link MouseEvent} and {@link MouseDragEvent}.
2151     *
2152     * @see MouseEvent
2153     * @see MouseDragEvent
2154     * @throws IllegalStateException if the full press-drag-release gesture
2155     * cannot be started at this moment (it's called outside of
2156     * {@code DRAG_DETECTED} event handling or this node is not in scene).
2157     */
2158    public void startFullDrag() {
2159        if (getScene() != null) {
2160            getScene().startFullDrag(this);
2161            return;
2162        }
2163
2164        throw new IllegalStateException("Cannot start full drag on node "
2165                + "that is not in scene");
2166    }
2167
2168    ////////////////////////////
2169    //  Private Implementation
2170    ////////////////////////////
2171
2172    /**
2173     * If this Node is being used as the clip of another Node, that other node
2174     * is referred to as the clipParent. If the boundsInParent of this Node
2175     * changes, it must update the clipParent's bounds as well.
2176     */
2177    private Node clipParent;
2178    // Use a getter function instead of giving clipParent package access,
2179    // so that clipParent doesn't get turned into a Location.
2180    final Node getClipParent() {
2181        return clipParent;
2182    }
2183
2184    /**
2185     * Determines whether this node is connected anywhere in the scene graph.
2186     */
2187    boolean isConnected() {
2188        // don't need to check scene, because if scene is non-null
2189        // parent must also be non-null
2190        return getParent() != null || clipParent != null;
2191    }
2192
2193    /**
2194     * Tests whether creating a parent-child relationship between these
2195     * nodes would cause a cycle. The parent relationship includes not only
2196     * the "real" parent (child of Group) but also the clipParent.
2197     */
2198    boolean wouldCreateCycle(Node parent, Node child) {
2199        if (child != null && child.getClip() == null && (!(child instanceof Parent))) {
2200            return false;
2201    }
2202
2203        Node n = parent;
2204        while (n != child) {
2205            if (n.getParent() != null) {
2206                n = n.getParent();
2207            } else if (n.getSubScene() != null) {
2208                n = n.getSubScene();
2209            } else if (n.clipParent != null) {
2210                n = n.clipParent;
2211            } else {
2212                return false;
2213            }
2214        }
2215        return true;
2216    }
2217
2218    /**
2219     * The peer node created by the graphics Toolkit/Pipeline implementation
2220     */
2221    private PGNode peer;
2222
2223    /**
2224     * @treatAsPrivate implementation detail
2225     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2226     */
2227    @Deprecated
2228    @SuppressWarnings("CallToPrintStackTrace")
2229    public PGNode impl_getPGNode() {
2230        if (Utils.assertionEnabled()) {
2231            // Assertion checking code
2232            if (getScene() != null && !Scene.isPGAccessAllowed()) {
2233                java.lang.System.err.println();
2234                java.lang.System.err.println("*** unexpected PG access");
2235                java.lang.Thread.dumpStack();
2236            }
2237        }
2238
2239        if (peer == null) {
2240            //if (PerformanceTracker.isLoggingEnabled()) {
2241            //    PerformanceTracker.logEvent("Creating PGNode for [{this}, id=\"{id}\"]");
2242            //}
2243            peer = impl_createPGNode();
2244            //if (PerformanceTracker.isLoggingEnabled()) {
2245            //    PerformanceTracker.logEvent("PGNode created");
2246            //}
2247        }
2248        return peer;
2249    }
2250
2251    /**
2252     * @treatAsPrivate implementation detail
2253     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
2254     */
2255    @Deprecated
2256    protected abstract PGNode impl_createPGNode();
2257
2258    /***************************************************************************
2259     *                                                                         *
2260     *                              Initialization                             *
2261     *                                                                         *
2262     *  To Note limit the number of bounds computations and improve startup    *
2263     *  performance.                                                           *
2264     *                                                                         *
2265     **************************************************************************/
2266
2267    /**
2268     * Creates a new instance of Node.
2269     */
2270    protected Node() {
2271        //if (PerformanceTracker.isLoggingEnabled()) {
2272        //    PerformanceTracker.logEvent("Node.init for [{this}, id=\"{id}\"]");
2273        //}
2274        setDirty();
2275        updateTreeVisible();
2276        //if (PerformanceTracker.isLoggingEnabled()) {
2277        //    PerformanceTracker.logEvent("Node.postinit " +
2278        //                                "for [{this}, id=\"{id}\"] finished");
2279        //}
2280    }
2281
2282    /***************************************************************************
2283     *                                                                         *
2284     * Layout related APIs.                                                    *
2285     *                                                                         *
2286     **************************************************************************/
2287    /**
2288     * Defines whether or not this node's layout will be managed by it's parent.
2289     * If the node is managed, it's parent will factor the node's geometry
2290     * into its own preferred size and {@link #layoutBoundsProperty layoutBounds}
2291     * calculations and will lay it
2292     * out during the scene's layout pass.  If a managed node's layoutBounds
2293     * changes, it will automatically trigger relayout up the scene-graph
2294     * to the nearest layout root (which is typically the scene's root node).
2295     * <p>
2296     * If the node is unmanaged, its parent will ignore the child in both preferred
2297     * size computations and layout.   Changes in layoutBounds will not trigger
2298     * relayout above it.   If an unmanaged node is of type {@link javafx.scene.Parent Parent},
2299     * it will act as a "layout root", meaning that calls to {@link Parent#requestLayout()}
2300     * beneath it will cause only the branch rooted by the node to be relayed out,
2301     * thereby isolating layout changes to that root and below.  It's the application's
2302     * responsibility to set the size and position of an unmanaged node.
2303     * <p>
2304     * By default all nodes are managed.
2305     * </p>
2306     *
2307     * @see #isResizable()
2308     * @see #layoutBoundsProperty()
2309     * @see Parent#requestLayout()
2310     *
2311     */
2312    private BooleanProperty managed;
2313
2314    public final void setManaged(boolean value) {
2315        managedProperty().set(value);
2316    }
2317
2318    public final boolean isManaged() {
2319        return managed == null ? true : managed.get();
2320    }
2321
2322    public final BooleanProperty managedProperty() {
2323        if (managed == null) {
2324            managed = new BooleanPropertyBase(true) {
2325
2326                @Override
2327                protected void invalidated() {
2328                    final Parent parent = getParent();
2329                    if (parent != null) {
2330                        parent.managedChildChanged();
2331                    }
2332                    notifyManagedChanged();
2333                }
2334
2335                @Override
2336                public Object getBean() {
2337                    return Node.this;
2338                }
2339
2340                @Override
2341                public String getName() {
2342                    return "managed";
2343                }
2344
2345            };
2346        }
2347        return managed;
2348    }
2349
2350    /**
2351     * Called whenever the "managed" flag has changed. This is only
2352     * used by Parent as an optimization to keep track of whether a
2353     * Parent node is a layout root or not.
2354     */
2355    void notifyManagedChanged() { }
2356
2357    /**
2358     * Defines the x coordinate of the translation that is added to this {@code Node}'s
2359     * transform for the purpose of layout.  The value should be computed as the
2360     * offset required to adjust the position of the node from its current
2361     * {@link #layoutBoundsProperty() layoutBounds minX} position (which might not be 0) to the desired location.
2362     *
2363     * <p>For example, if {@code textnode} should be positioned at {@code finalX}
2364     * <code><pre>
2365     *     textnode.setLayoutX(finalX - textnode.getLayoutBounds().getMinX());
2366     * </pre></code>
2367     * <p>
2368     * Failure to subtract {@code layoutBounds minX} may result in misplacement
2369     * of the node.  The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
2370     * correct computation and should generally be used over setting layoutX directly.
2371     * <p>
2372     * The node's final translation will be computed as {@code layoutX} + {@link #translateXProperty translateX},
2373     * where {@code layoutX} establishes the node's stable position
2374     * and {@code translateX} optionally makes dynamic adjustments to that
2375     * position.
2376     * <p>
2377     * If the node is managed and has a {@link javafx.scene.layout.Region}
2378     * as its parent, then the layout region will set {@code layoutX} according to its
2379     * own layout policy.   If the node is unmanaged or parented by a {@link Group},
2380     * then the application may set {@code layoutX} directly to position it.
2381     * <p>
2382     * @see #relocate(double, double)
2383     * @see #layoutBoundsProperty()
2384     *
2385     */
2386    private DoubleProperty layoutX;
2387
2388    public final void setLayoutX(double value) {
2389        layoutXProperty().set(value);
2390    }
2391
2392    public final double getLayoutX() {
2393        return layoutX == null ? 0.0 : layoutX.get();
2394    }
2395
2396    public final DoubleProperty layoutXProperty() {
2397        if (layoutX == null) {
2398            layoutX = new DoublePropertyBase(0.0) {
2399
2400                @Override
2401                protected void invalidated() {
2402                    impl_transformsChanged();
2403                }
2404
2405                @Override
2406                public Object getBean() {
2407                    return Node.this;
2408                }
2409
2410                @Override
2411                public String getName() {
2412                    return "layoutX";
2413                }
2414            };
2415        }
2416        return layoutX;
2417    }
2418
2419    /**
2420     * Defines the y coordinate of the translation that is added to this {@code Node}'s
2421     * transform for the purpose of layout.  The value should be computed as the
2422     * offset required to adjust the position of the node from its current
2423     * {@link #layoutBoundsProperty() layoutBounds minY} position (which might not be 0) to the desired location.
2424     *
2425     * <p>For example, if {@code textnode} should be positioned at {@code finalY}
2426     * <code><pre>
2427     *     textnode.setLayoutY(finalY - textnode.getLayoutBounds().getMinY());
2428     * </pre></code>
2429     * <p>
2430     * Failure to subtract {@code layoutBounds minY} may result in misplacement
2431     * of the node.  The {@link #relocate(double, double) relocate(x, y)} method will automatically do the
2432     * correct computation and should generally be used over setting layoutY directly.
2433     * <p>
2434     * The node's final translation will be computed as {@code layoutY} + {@link #translateYProperty translateY},
2435     * where {@code layoutY} establishes the node's stable position
2436     * and {@code translateY} optionally makes dynamic adjustments to that
2437     * position.
2438     * <p>
2439     * If the node is managed and has a {@link javafx.scene.layout.Region}
2440     * as its parent, then the region will set {@code layoutY} according to its
2441     * own layout policy.   If the node is unmanaged or parented by a {@link Group},
2442     * then the application may set {@code layoutY} directly to position it.
2443     *
2444     * @see #relocate(double, double)
2445     * @see #layoutBoundsProperty()
2446     */
2447    private DoubleProperty layoutY;
2448
2449    public final void setLayoutY(double value) {
2450        layoutYProperty().set(value);
2451    }
2452
2453    public final double getLayoutY() {
2454        return layoutY == null ? 0.0 : layoutY.get();
2455    }
2456
2457    public final DoubleProperty layoutYProperty() {
2458        if (layoutY == null) {
2459            layoutY = new DoublePropertyBase(0.0) {
2460
2461                @Override
2462                protected void invalidated() {
2463                    impl_transformsChanged();
2464                }
2465
2466                @Override
2467                public Object getBean() {
2468                    return Node.this;
2469                }
2470
2471                @Override
2472                public String getName() {
2473                    return "layoutY";
2474                }
2475
2476            };
2477        }
2478        return layoutY;
2479    }
2480
2481    /**
2482     * Sets the node's layoutX and layoutY translation properties in order to
2483     * relocate this node to the x,y location in the parent.
2484     * <p>
2485     * This method does not alter translateX or translateY, which if also set
2486     * will be added to layoutX and layoutY, adjusting the final location by
2487     * corresponding amounts.
2488     *
2489     * @param x the target x coordinate location
2490     * @param y the target y coordinate location
2491     */
2492    public void relocate(double x, double y) {
2493        setLayoutX(x - getLayoutBounds().getMinX());
2494        setLayoutY(y - getLayoutBounds().getMinY());
2495
2496        PlatformLogger logger = Logging.getLayoutLogger();
2497        if (logger.isLoggable(PlatformLogger.FINER)) {
2498            logger.finer(this.toString()+" moved to ("+x+","+y+")");
2499        }
2500    }
2501
2502    /**
2503     * Indicates whether this node is a type which can be resized by its parent.
2504     * If this method returns true, then the parent will resize the node (ideally
2505     * within its size range) by calling node.resize(width,height) during the
2506     * layout pass.  All Regions, Controls, and WebView are resizable classes
2507     * which depend on their parents resizing them during layout once all sizing
2508     * and CSS styling information has been applied.
2509     * <p>
2510     * If this method returns false, then the parent cannot resize it during
2511     * layout (resize() is a no-op) and it should return its layoutBounds for
2512     * minimum, preferred, and maximum sizes.  Group, Text, and all Shapes are not
2513     * resizable and hence depend on the application to establish their sizing
2514     * by setting appropriate properties (e.g.  width/height for Rectangle,
2515     * text on Text, and so on).  Non-resizable nodes may still be relocated
2516     * during layout.
2517     *
2518     * @see #getContentBias()
2519     * @see #minWidth(double)
2520     * @see #minHeight(double)
2521     * @see #prefWidth(double)
2522     * @see #prefHeight(double)
2523     * @see #maxWidth(double)
2524     * @see #maxHeight(double)
2525     * @see #resize(double, double)
2526     * @see #getLayoutBounds()
2527     *
2528     * @return whether or not this node type can be resized by its parent during layout
2529     */
2530    public boolean isResizable() {
2531        return false;
2532    }
2533
2534    /**
2535     * Returns the orientation of a node's resizing bias for layout purposes.
2536     * If the node type has no bias, returns null.  If the node is resizable and
2537     * it's height depends on its width, returns HORIZONTAL, else if its width
2538     * depends on its height, returns VERTICAL.
2539     * <p>
2540     * Resizable subclasses should override this method to return an
2541     * appropriate value.
2542     *
2543     * @see #isResizable()
2544     * @see #minWidth(double)
2545     * @see #minHeight(double)
2546     * @see #prefWidth(double)
2547     * @see #prefHeight(double)
2548     * @see #maxWidth(double)
2549     * @see #maxHeight(double)
2550     *
2551     * @return orientation of width/height dependency or null if there is none
2552     */
2553    public Orientation getContentBias() {
2554        return null;
2555    }
2556
2557    /**
2558     * Returns the node's minimum width for use in layout calculations.
2559     * If the node is resizable, its parent should not resize its width any
2560     * smaller than this value.  If the node is not resizable, returns its
2561     * layoutBounds width.
2562     * <p>
2563     * Layout code which calls this method should first check the content-bias
2564     * of the node.  If the node has a vertical content-bias, then callers
2565     * should pass in a height value that the minimum width should be based on.
2566     * If the node has either a horizontal or null content-bias, then the caller
2567     * should pass in -1.
2568     * <p>
2569     * Node subclasses with a vertical content-bias should honor the height
2570     * parameter whether -1 or a positive value.   All other subclasses may ignore
2571     * the height parameter (which will likely be -1).
2572     * <p>
2573     * If Node's {@link #maxWidth(double)} is lower than this number,
2574     * {@code minWidth} takes precedence. This means the Node should never be resized below {@code minWidth}.
2575     * <p>
2576     * @see #isResizable()
2577     * @see #getContentBias()
2578     *
2579     * @param height the height that should be used if minimum width depends on it
2580     * @return the minimum width that the node should be resized to during layout
2581     *
2582     */
2583    public double minWidth(double height) {
2584        return prefWidth(height);
2585    }
2586
2587    /**
2588     * Returns the node's minimum height for use in layout calculations.
2589     * If the node is resizable, its parent should not resize its height any
2590     * smaller than this value.  If the node is not resizable, returns its
2591     * layoutBounds height.
2592     * <p>
2593     * Layout code which calls this method should first check the content-bias
2594     * of the node.  If the node has a horizontal content-bias, then callers
2595     * should pass in a width value that the minimum height should be based on.
2596     * If the node has either a vertical or null content-bias, then the caller
2597     * should pass in -1.
2598     * <p>
2599     * Node subclasses with a horizontal content-bias should honor the width
2600     * parameter whether -1 or a positive value.   All other subclasses may ignore
2601     * the width parameter (which will likely be -1).
2602     * <p>
2603     * If Node's {@link #maxHeight(double)} is lower than this number,
2604     * {@code minHeight} takes precedence. This means the Node should never be resized below {@code minHeight}.
2605     * <p>
2606     * @see #isResizable()
2607     * @see #getContentBias()
2608     *
2609     * @param width the width that should be used if minimum height depends on it
2610     * @return the minimum height that the node should be resized to during layout
2611     *
2612     */
2613    public double minHeight(double width) {
2614        return prefHeight(width);
2615    }
2616
2617
2618    /**
2619     * Returns the node's preferred width for use in layout calculations.
2620     * If the node is resizable, its parent should treat this value as the
2621     * node's ideal width within its range.  If the node is not resizable,
2622     * just returns its layoutBounds width, which should be treated as the rigid
2623     * width of the node.
2624     * <p>
2625     * Layout code which calls this method should first check the content-bias
2626     * of the node.  If the node has a vertical content-bias, then callers
2627     * should pass in a height value that the preferred width should be based on.
2628     * If the node has either a horizontal or null content-bias, then the caller
2629     * should pass in -1.
2630     * <p>
2631     * Node subclasses with a vertical content-bias should honor the height
2632     * parameter whether -1 or a positive value.   All other subclasses may ignore
2633     * the height parameter (which will likely be -1).
2634     * <p>
2635     * @see #isResizable()
2636     * @see #getContentBias()
2637     * @see #autosize()
2638     *
2639     * @param height the height that should be used if preferred width depends on it
2640     * @return the preferred width that the node should be resized to during layout
2641     */
2642    public double prefWidth(double height) {
2643        return getLayoutBounds().getWidth();
2644    }
2645
2646    /**
2647     * Returns the node's preferred height for use in layout calculations.
2648     * If the node is resizable, its parent should treat this value as the
2649     * node's ideal height within its range.  If the node is not resizable,
2650     * just returns its layoutBounds height, which should be treated as the rigid
2651     * height of the node.
2652     * <p>
2653     * Layout code which calls this method should first check the content-bias
2654     * of the node.  If the node has a horizontal content-bias, then callers
2655     * should pass in a width value that the preferred height should be based on.
2656     * If the node has either a vertical or null content-bias, then the caller
2657     * should pass in -1.
2658     * <p>
2659     * Node subclasses with a horizontal content-bias should honor the height
2660     * parameter whether -1 or a positive value.   All other subclasses may ignore
2661     * the height parameter (which will likely be -1).
2662     * <p>
2663     * @see #getContentBias()
2664     * @see #autosize()
2665     *
2666     * @param width the width that should be used if preferred height depends on it
2667     * @return the preferred height that the node should be resized to during layout
2668     */
2669    public double prefHeight(double width) {
2670        return getLayoutBounds().getHeight();
2671    }
2672
2673    /**
2674     * Returns the node's maximum width for use in layout calculations.
2675     * If the node is resizable, its parent should not resize its width any
2676     * larger than this value.  A value of Double.MAX_VALUE indicates the
2677     * parent may expand the node's width beyond its preferred without limits.
2678     * <p>
2679     * If the node is not resizable, returns its layoutBounds width.
2680     * <p>
2681     * Layout code which calls this method should first check the content-bias
2682     * of the node.  If the node has a vertical content-bias, then callers
2683     * should pass in a height value that the maximum width should be based on.
2684     * If the node has either a horizontal or null content-bias, then the caller
2685     * should pass in -1.
2686     * <p>
2687     * Node subclasses with a vertical content-bias should honor the height
2688     * parameter whether -1 or a positive value.   All other subclasses may ignore
2689     * the height parameter (which will likely be -1).
2690     * <p>
2691     * If Node's {@link #minWidth(double)} is greater, it should take precedence
2692     * over the {@code maxWidth}. This means the Node should never be resized below {@code minWidth}.
2693     * <p>
2694     * @see #isResizable()
2695     * @see #getContentBias()
2696     *
2697     * @param height the height that should be used if maximum width depends on it
2698     * @return the maximum width that the node should be resized to during layout
2699     *
2700     */
2701    public double maxWidth(double height) {
2702        return prefWidth(height);
2703    }
2704
2705    /**
2706     * Returns the node's maximum height for use in layout calculations.
2707     * If the node is resizable, its parent should not resize its height any
2708     * larger than this value.  A value of Double.MAX_VALUE indicates the
2709     * parent may expand the node's height beyond its preferred without limits.
2710     * <p>
2711     * If the node is not resizable, returns its layoutBounds height.
2712     * <p>
2713     * Layout code which calls this method should first check the content-bias
2714     * of the node.  If the node has a horizontal content-bias, then callers
2715     * should pass in a width value that the maximum height should be based on.
2716     * If the node has either a vertical or null content-bias, then the caller
2717     * should pass in -1.
2718     * <p>
2719     * Node subclasses with a horizontal content-bias should honor the width
2720     * parameter whether -1 or a positive value.   All other subclasses may ignore
2721     * the width parameter (which will likely be -1).
2722     * <p>
2723     * If Node's {@link #minHeight(double)} is greater, it should take precedence
2724     * over the {@code maxHeight}.  This means the Node should never be resized below {@code minHeight}.
2725     * <p>
2726     * @see #isResizable()
2727     * @see #getContentBias()
2728     *
2729     * @param width the width that should be used if maximum height depends on it
2730     * @return the maximum height that the node should be resized to during layout
2731     *
2732     */
2733    public double maxHeight(double width) {
2734        return prefHeight(width);
2735    }
2736
2737    /**
2738     * If the node is resizable, will set its layout bounds to the specified
2739     * width and height.   If the node is not resizable, this method is a no-op.
2740     * <p>
2741     * This method should generally only be called by parent nodes from their
2742     * layoutChildren() methods.   All Parent classes will automatically resize
2743     * resizable children, so resizing done directly by the application will be
2744     * overridden by the node's parent, unless the child is unmanaged.
2745     * <p>
2746     * Parents are responsible for ensuring the width and height values fall
2747     * within the resizable node's preferred range.  The autosize() method may
2748     * be used if the parent just needs to resize the node to its preferred size.
2749     *
2750     * <p>
2751     * @see #isResizable()
2752     * @see #getContentBias()
2753     * @see #autosize()
2754     * @see #minWidth(double)
2755     * @see #minHeight(double)
2756     * @see #prefWidth(double)
2757     * @see #prefHeight(double)
2758     * @see #maxWidth(double)
2759     * @see #maxHeight(double)
2760     * @see #getLayoutBounds()
2761     *
2762     * @param width the target layout bounds width
2763     * @param height the target layout bounds height
2764     */
2765    public void resize(double width, double height) {
2766    }
2767
2768    /**
2769     * If the node is resizable, will set its layout bounds to its current preferred
2770     * width and height. If the node is not resizable, this method is a no-op.
2771     * <p>
2772     * This method automatically queries the node's content-bias and if it's
2773     * horizontal, will pass in the node's preferred width to get the preferred
2774     * height; if vertical, will pass in the node's preferred height to get the width,
2775     * and if null, will compute the preferred width/height independently.
2776     * <p>
2777     *
2778     * @see #isResizable()
2779     * @see #getContentBias()
2780     *
2781     */
2782    public final void autosize() {
2783        if (isResizable()) {
2784            Orientation contentBias = getContentBias();
2785            double w, h;
2786            if (contentBias == null) {
2787                w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
2788                h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
2789            } else if (contentBias == Orientation.HORIZONTAL) {
2790                w = boundedSize(prefWidth(-1), minWidth(-1), maxWidth(-1));
2791                h = boundedSize(prefHeight(w), minHeight(w), maxHeight(w));
2792            } else { // bias == VERTICAL
2793                h = boundedSize(prefHeight(-1), minHeight(-1), maxHeight(-1));
2794                w = boundedSize(prefWidth(h), minWidth(h), maxWidth(h));
2795            }
2796            resize(w,h);
2797        }
2798    }
2799
2800    double boundedSize(double value, double min, double max) {
2801        // if max < value, return max
2802        // if min > value, return min
2803        // if min > max, return min
2804        return Math.min(Math.max(value, min), Math.max(min,max));
2805    }
2806
2807    /**
2808     * If the node is resizable, will set its layout bounds to the specified
2809     * width and height.   If the node is not resizable, the resize step is skipped.
2810     * <p>
2811     * Once the node has been resized (if resizable) then sets the node's layoutX
2812     * and layoutY translation properties in order to relocate it to x,y in the
2813     * parent's coordinate space.
2814     * <p>
2815     * This method should generally only be called by parent nodes from their
2816     * layoutChildren() methods.   All Parent classes will automatically resize
2817     * resizable children, so resizing done directly by the application will be
2818     * overridden by the node's parent, unless the child is unmanaged.
2819     * <p>
2820     * Parents are responsible for ensuring the width and height values fall
2821     * within the resizable node's preferred range.  The autosize() and relocate()
2822     * methods may be used if the parent just needs to resize the node to its
2823     * preferred size and reposition it.
2824     * <p>
2825     * @see #isResizable()
2826     * @see #getContentBias()
2827     * @see #autosize()
2828     * @see #minWidth(double)
2829     * @see #minHeight(double)
2830     * @see #prefWidth(double)
2831     * @see #prefHeight(double)
2832     * @see #maxWidth(double)
2833     * @see #maxHeight(double)
2834     *
2835     * @param x the target x coordinate location
2836     * @param y the target y coordinate location
2837     * @param width the target layout bounds width
2838     * @param height the target layout bounds height
2839     *
2840     */
2841    public void resizeRelocate(double x, double y, double width, double height) {
2842        resize(width, height);
2843        relocate(x,y);
2844    }
2845
2846    /**
2847     * The 'alphabetic' (or 'roman') baseline offset from the node's layoutBounds.minY location
2848     * that should be used when this node is being vertically aligned by baseline with
2849     * other nodes.  By default this returns the layoutBounds height of the node.  Subclasses
2850     * which contain text should override this method to return their actual text baseline offset.
2851     *
2852     * @return offset of text baseline from layoutBounds.minY
2853     */
2854    public double getBaselineOffset() {
2855        return getLayoutBounds().getHeight();
2856    }
2857
2858    /**
2859     * Returns the area of this {@code Node} projected onto the
2860     * physical screen in pixel units.
2861     */
2862    public double computeAreaInScreen() {
2863        return impl_computeAreaInScreen();
2864    }
2865
2866    /*
2867     * Help application or utility to implement LOD support by returning the
2868     * projected area of a Node in pixel unit. The projected area is not clipped.
2869     *
2870     * For perspective camera, this method first exams node's bounds against
2871     * camera's clipping plane to cut off those out of viewing frustrum. After
2872     * computing areaInScreen, it applys a tight viewing frustrum check using
2873     * canonical view volume.
2874     *
2875     * The result of areaInScreen comes from the product of
2876     * (projViewTx x localToSceneTransform x localBounds).
2877     *
2878     * Returns 0 for those fall outside viewing frustrum.
2879     */
2880    private double impl_computeAreaInScreen() {
2881        Scene tmpScene = getScene();
2882        if (tmpScene != null) {
2883            Bounds bounds = getBoundsInLocal();
2884            Camera camera = tmpScene.getEffectiveCamera();
2885            boolean isPerspective = camera instanceof PerspectiveCamera ? true : false;
2886            Transform localToSceneTx = getLocalToSceneTransform();
2887            Affine3D tempTx = TempState.getInstance().tempTx;
2888            BaseBounds localBounds = new BoxBounds((float) bounds.getMinX(),
2889                                                   (float) bounds.getMinY(),
2890                                                   (float) bounds.getMinZ(),
2891                                                   (float) bounds.getMaxX(),
2892                                                   (float) bounds.getMaxY(),
2893                                                   (float) bounds.getMaxZ());
2894
2895            // NOTE: Viewing frustrum check on camera's clipping plane is now only
2896            // for perspective camera.
2897            // TODO: Need to hook up parallel camera's nearClip and farClip.
2898            if (isPerspective) {
2899                Transform cameraL2STx = camera.getLocalToSceneTransform();
2900
2901                // If camera transform only contains translate, compare in scene
2902                // coordinate. Otherwise, compare in camera coordinate.
2903                if (cameraL2STx.getMxx() == 1.0
2904                        && cameraL2STx.getMxy() == 0.0
2905                        && cameraL2STx.getMxz() == 0.0
2906                        && cameraL2STx.getMyx() == 0.0
2907                        && cameraL2STx.getMyy() == 1.0
2908                        && cameraL2STx.getMyz() == 0.0
2909                        && cameraL2STx.getMzx() == 0.0
2910                        && cameraL2STx.getMzy() == 0.0
2911                        && cameraL2STx.getMzz() == 1.0) {
2912
2913                    double minZ, maxZ;
2914
2915                    // If node transform only contains translate, only convert
2916                    // minZ and maxZ to scene coordinate. Otherwise, convert
2917                    // node bounds to scene coordinate.
2918                    if (localToSceneTx.getMxx() == 1.0
2919                            && localToSceneTx.getMxy() == 0.0
2920                            && localToSceneTx.getMxz() == 0.0
2921                            && localToSceneTx.getMyx() == 0.0
2922                            && localToSceneTx.getMyy() == 1.0
2923                            && localToSceneTx.getMyz() == 0.0
2924                            && localToSceneTx.getMzx() == 0.0
2925                            && localToSceneTx.getMzy() == 0.0
2926                            && localToSceneTx.getMzz() == 1.0) {
2927
2928                        Vec3d tempV3D = TempState.getInstance().vec3d;
2929                        tempV3D.set(0, 0, bounds.getMinZ());
2930                        localToScene(tempV3D);
2931                        minZ = tempV3D.z;
2932
2933                        tempV3D.set(0, 0, bounds.getMaxZ());
2934                        localToScene(tempV3D);
2935                        maxZ = tempV3D.z;
2936                    } else {
2937                        Bounds nodeInSceneBounds = localToScene(bounds);
2938                        minZ = nodeInSceneBounds.getMinZ();
2939                        maxZ = nodeInSceneBounds.getMaxZ();
2940                    }
2941
2942                    if (minZ > camera.getFarClipInScene()
2943                            || maxZ < camera.getNearClipInScene()) {
2944                        return 0;
2945                    }
2946
2947                } else {
2948                    BaseBounds nodeInCameraBounds = new BoxBounds();
2949
2950                    // We need to set tempTx to identity since it is a recycled transform.
2951                    // This is because impl_apply is a matrix concatenation operation.
2952                    tempTx.setToIdentity();
2953                    localToSceneTx.impl_apply(tempTx);
2954
2955                    // Convert node from local coordinate to camera coordinate
2956                    tempTx.preConcatenate(camera.getSceneToLocalTransform());
2957                    tempTx.transform(localBounds, nodeInCameraBounds);
2958
2959                    // Compare in camera coornidate
2960                    if (nodeInCameraBounds.getMinZ() > camera.getFarClip()
2961                            || nodeInCameraBounds.getMaxZ() < camera.getNearClip()) {
2962                        return 0;
2963                    }
2964                }
2965            }
2966
2967            GeneralTransform3D projViewTx = TempState.getInstance().projViewTx;
2968            projViewTx.set(camera.getProjViewTransform());
2969
2970            // We need to set tempTx to identity since it is a recycled transform.
2971            // This is because impl_apply is a matrix concatenation operation.
2972            tempTx.setToIdentity();
2973            localToSceneTx.impl_apply(tempTx);
2974
2975            // The product of projViewTx * localToSceneTransform
2976            GeneralTransform3D tx = projViewTx.mul(tempTx);
2977
2978            // Transform localBounds to projected bounds
2979            localBounds = tx.transform(localBounds, localBounds);
2980            double area = localBounds.getWidth() * localBounds.getHeight();
2981
2982            // Use canonical view volume to check whether object is outside the
2983            // viewing frustrum
2984            if (isPerspective) {
2985                localBounds.intersectWith(-1, -1, 0, 1, 1, 1);
2986                area = (localBounds.getWidth() < 0 || localBounds.getHeight() < 0) ? 0 : area;
2987            }
2988            return area * (camera.getViewWidth() / 2 * camera.getViewHeight() / 2);
2989        }
2990        return 0;
2991    }
2992
2993    /* *************************************************************************
2994     *                                                                         *
2995     * Bounds related APIs                                                     *
2996     *                                                                         *
2997     **************************************************************************/
2998
2999    public final Bounds getBoundsInParent() {
3000        return boundsInParentProperty().get();
3001    }
3002
3003    /**
3004     * The rectangular bounds of this {@code Node} which include its transforms.
3005     * {@code boundsInParent} is calculated by
3006     * taking the local bounds (defined by {@link #boundsInLocalProperty boundsInLocal}) and applying
3007     * the transform created by setting the following additional variables
3008     * <ol>
3009     * <li>{@link #getTransforms transforms} ObservableList</li>
3010     * <li>{@link #scaleXProperty scaleX}, {@link #scaleYProperty scaleY}</li>
3011     * <li>{@link #rotateProperty rotate}</li>
3012     * <li>{@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}</li>
3013     * <li>{@link #translateXProperty translateX}, {@link #translateYProperty translateY}</li>
3014     * </ol>
3015     * <p>
3016     * The resulting bounds will be conceptually in the coordinate space of the
3017     * {@code Node}'s parent, however the node need not have a parent to calculate
3018     * these bounds.
3019     * <p>
3020     * Note that this method does not take the node's visibility into account;
3021     * the computation is based on the geometry of this {@code Node} only.
3022     * <p>
3023     * This property will always have a non-null value.
3024     * <p>
3025     * Note that boundsInParent is automatically recomputed whenever the
3026     * geometry of a node changes, or when any of the following the change:
3027     * transforms ObservableList, translateX, translateY, layoutX, layoutY,
3028     * scaleX, scaleY, or the rotate variable. For this reason, it is an error
3029     * to bind any of these values in a node to an expression that depends upon
3030     * this variable. For example, the x or y variables of a shape, or
3031     * translateX, translateY should never be bound to boundsInParent
3032     * for the purpose of positioning the node.
3033     */
3034    public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
3035        return getMiscProperties().boundsInParentProperty();
3036    }
3037
3038    private void invalidateBoundsInParent() {
3039        if (miscProperties != null) {
3040            miscProperties.invalidateBoundsInParent();
3041        }
3042    }
3043
3044    public final Bounds getBoundsInLocal() {
3045        return boundsInLocalProperty().get();
3046    }
3047
3048    /**
3049     * The rectangular bounds of this {@code Node} in the node's
3050     * untransformed local coordinate space.  For nodes that extend
3051     * {@link javafx.scene.shape.Shape}, the local bounds will also include
3052     * space required for a non-zero stroke that may fall outside the shape's
3053     * geometry that is defined by position and size attributes.
3054     * The local bounds will also include any clipping set with {@link #clipProperty clip}
3055     * as well as effects set with {@link #effectProperty effect}.
3056     *
3057     * <p>
3058     * Note that this method does not take the node's visibility into account;
3059     * the computation is based on the geometry of this {@code Node} only.
3060     * <p>
3061     * This property will always have a non-null value.
3062     * <p>
3063     * Note that boundsInLocal is automatically recomputed whenever the
3064     * geometry of a node changes. For this reason, it is an error to bind any
3065     * of these values in a node to an expression that depends upon this variable.
3066     * For example, the x or y variables of a shape should never be bound
3067     * to boundsInLocal for the purpose of positioning the node.
3068     */
3069    public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
3070        return getMiscProperties().boundsInLocalProperty();
3071    }
3072
3073    private void invalidateBoundsInLocal() {
3074        if (miscProperties != null) {
3075            miscProperties.invalidateBoundsInLocal();
3076        }
3077    }
3078
3079    /**
3080     * The rectangular bounds that should be used for layout calculations for
3081     * this node. {@code layoutBounds} may differ from the visual bounds
3082     * of the node and is computed differently depending on the node type.
3083     * <p>
3084     * If the node type is resizable ({@link javafx.scene.layout.Region Region},
3085     * {@link javafx.scene.control.Control Control}, or {@link javafx.scene.web.WebView WebView})
3086     * then the layoutBounds will always be {@code 0,0 width x height}.
3087     * If the node type is not resizable ({@link javafx.scene.shape.Shape Shape},
3088     * {@link javafx.scene.text.Text Text}, or {@link Group}), then the layoutBounds
3089     * are computed based on the node's geometric properties and does not include the
3090     * node's clip, effect, or transforms.  See individual class documentation
3091     * for details.
3092     * <p>
3093     * Note that the {@link #layoutXProperty layoutX}, {@link #layoutYProperty layoutY}, {@link #translateXProperty translateX}, and
3094     * {@link #translateYProperty translateY} variables are not included in the layoutBounds.
3095     * This is important because layout code must first determine the current
3096     * size and location of the node (using layoutBounds) and then set
3097     * {@code layoutX} and {@code layoutY} to adjust the translation of the
3098     * node so that it will have the desired layout position.
3099     * <p>
3100     * Because the computation of layoutBounds is often tied to a node's
3101     * geometric variables, it is an error to bind any such variables to an
3102     * expression that depends upon {@code layoutBounds}. For example, the
3103     * x or y variables of a shape should never be bound to layoutBounds
3104     * for the purpose of positioning the node.
3105     * <p>
3106     * The layoutBounds will never be null.
3107     *
3108     */
3109    private LazyBoundsProperty layoutBounds = new LazyBoundsProperty() {
3110        @Override
3111        protected Bounds computeBounds() {
3112            return impl_computeLayoutBounds();
3113        }
3114
3115        @Override
3116        public Object getBean() {
3117            return Node.this;
3118        }
3119
3120        @Override
3121        public String getName() {
3122            return "layoutBounds";
3123        }
3124    };
3125
3126    public final Bounds getLayoutBounds() {
3127        return layoutBoundsProperty().get();
3128    }
3129
3130    public final ReadOnlyObjectProperty<Bounds> layoutBoundsProperty() {
3131        return layoutBounds;
3132    }
3133
3134    /*
3135     *                  Bounds And Transforms Computation
3136     *
3137     *  This section of the code is responsible for computing and caching
3138     *  various bounds and transforms. For optimal performance and minimal
3139     *  recomputation of bounds (which can be quite expensive), we cache
3140     *  values on two different levels. We expose two public immutable
3141     *  Bounds boundsInParent objects and boundsInLocal. Because they are
3142     *  immutable and because they may change quite frequently (especially
3143     *  in the case of a Parent who's children are animated), it is
3144     *  important that the system does not rely on these variables, because
3145     *  doing so would produce a large amount of garbage. Rather, these
3146     *  variables are provided solely for the convenience of application
3147     *  developers and, being lazily bound, should generally be created at
3148     *  most once per frame.
3149     *
3150     *  The second level of caching are within local Bounds2D variables.
3151     *  These variables, txBounds and geomBounds, are mutable and as such
3152     *  can be cached and updated as frequently as necessary without creating
3153     *  excessive garbage. However, since the computation of bounds is still
3154     *  expensive, it is desirable to cache both the geometric bounds and
3155     *  the "complete" transformed bounds (essentially, boundsInParent).
3156     *  Cached txBounds is particularly useful when computing the geometric
3157     *  bounds of a Parent since it would not require complete or partial
3158     *  recomputation of each child.
3159     *
3160     *  Finally, we cache the complete transform for this node which converts
3161     *  its coord system from local to parent coords. This is useful both for
3162     *  minimizing bounds recomputations in the case of the geometry having
3163     *  changed but the transform not having changed, and also because the tx
3164     *  is required for several different computations (for example, it must
3165     *  be computed once during state synchronization with the PG peer, and
3166     *  must also be computed when the pivot point changes, and also when
3167     *  deriving the txBounds of the Node).
3168     *
3169     *  As with any caching system, a subtle and non-trivial amount of code
3170     *  is devoted to invalidating the bounds / transforms at appropriate
3171     *  times and in appropriate places to make sure bounds / transforms
3172     *  are recomputed at all necessary times.
3173     *
3174     *  There are three computeXXX functions. One is for computing the
3175     *  boundsInParent, the second for computing boundsInLocal, and the
3176     *  third for computing the default layout bounds (which, by default,
3177     *  is based on the geometric bounds). These functions are all prefixed
3178     *  with "compute" because they create and return new immutable
3179     *  Bounds objects.
3180     *
3181     *  There are three getXXXBounds functions. One is for returning the
3182     *  complete transformed bounds. The second is for returning the
3183     *  local bounds. The last is for returning the geometric bounds. These
3184     *  functions are all prefixed with "get" because they may well return
3185     *  a cached value, or may actually compute the bounds if necessary. These
3186     *  functions all have the same signature. They take a Bounds2D and
3187     *  BaseTransform, and return a Bounds2D (the same as they took). These
3188     *  functions essentially populate the supplied bounds2D with the
3189     *  appropriate bounds information, leveraging cached bounds if possible.
3190     *
3191     *  There is a single impl_computeGeomBounds function which is abstract.
3192     *  This must be implemented in each subclass, and is responsible for
3193     *  computing the actual geometric bounds for the Node. For example, Parent
3194     *  is written such that this function is the union of the transformed
3195     *  bounds of each child. Rectangle is written such that this takes into
3196     *  account the size and stroke. Text is written such that it is computed
3197     *  based on the actual glyphs.
3198     *
3199     *  There are two updateXXX functions, updateGeomBounds and updateTxBounds.
3200     *  These functions are for ensuring that geomBounds and txBounds are
3201     *  valid. They only execute in the case of the cached value being invalid,
3202     *  so the function call is very cheap in cases where the cached bounds
3203     *  values are still valid.
3204     */
3205
3206    /**
3207     * An affine transform that holds the computed local-to-parent transform.
3208     * This is the concatenation of all transforms in this node, including all
3209     * of the convenience transforms.
3210     */
3211    private final Affine3D localToParentTx = new Affine3D();
3212
3213    /**
3214     * This flag is used to indicate that localToParentTx is dirty and needs
3215     * to be recomputed.
3216     */
3217    private boolean transformDirty = true;
3218
3219    /**
3220     * The cached transformed bounds. This is never null, but is frequently set
3221     * to be invalid whenever the bounds for the node have changed. These are
3222     * "complete" bounds, that is, with transforms and effect and clip applied.
3223     * Note that this is equivalent to boundsInParent
3224     */
3225    private BaseBounds txBounds = new RectBounds();
3226
3227    /**
3228     * The cached bounds. This is never null, but is frequently set to be
3229     * invalid whenever the bounds for the node have changed. These are the
3230     * "content" bounds, that is, without transforms or effects applied.
3231     */
3232    private BaseBounds geomBounds = new RectBounds();
3233
3234    /**
3235     * This special flag is used only by Parent to flag whether or not
3236     * the *parent* has processed the fact that bounds have changed for this
3237     * child Node. We need some way of flagging this on a per-node basis to
3238     * enable the significant performance optimizations and fast paths that
3239     * are in the Parent code.
3240     * <p>
3241     * To reduce confusion, although this variable is defined on Node, it
3242     * really belongs to the Parent of the node and should *only* be modified
3243     * by the parent.
3244     */
3245    boolean boundsChanged;
3246
3247    /**
3248     * Returns geometric bounds, but may be over-ridden by a subclass.
3249     * @treatAsPrivate implementation detail
3250     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3251     */
3252    @Deprecated
3253    protected Bounds impl_computeLayoutBounds() {
3254        BaseBounds tempBounds = TempState.getInstance().bounds;
3255        tempBounds = getGeomBounds(tempBounds,
3256                                   BaseTransform.IDENTITY_TRANSFORM);
3257        return new BoundingBox(tempBounds.getMinX(),
3258                               tempBounds.getMinY(),
3259                               tempBounds.getMinZ(),
3260                               tempBounds.getWidth(),
3261                               tempBounds.getHeight(),
3262                               tempBounds.getDepth());
3263    }
3264
3265    /**
3266     * Subclasses may customize the layoutBounds by means of overriding the
3267     * impl_computeLayoutBounds method. If the layout bounds need to be
3268     * recomputed, the subclass must notify the Node implementation of this
3269     * fact so that appropriate notifications and internal state can be
3270     * kept in sync. Subclasses must call impl_layoutBoundsChanged to
3271     * let Node know that the layout bounds are invalid and need to be
3272     * recomputed.
3273     *
3274     * @treatAsPrivate implementation detail
3275     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3276     */
3277    @Deprecated
3278    protected final void impl_layoutBoundsChanged() {
3279        if (!layoutBounds.valid) {
3280            return;
3281        }
3282        layoutBounds.invalidate();
3283        if ((nodeTransformation != null && nodeTransformation.hasScaleOrRotate()) || hasMirroring()) {
3284            // if either the scale or rotate convenience variables are used,
3285            // then we need a valid pivot point. Since the layoutBounds
3286            // affects the pivot we need to invalidate the transform
3287            impl_transformsChanged();
3288        }
3289        // notify the parent
3290        // Group instanceof check a little hoaky, but it allows us to disable
3291        // unnecessary layout for the case of a non-resizable within a group
3292        Parent p = getParent();
3293        if (isManaged() && (p != null) && !(p instanceof Group && !isResizable())) {
3294            p.requestLayout();
3295        }
3296    }
3297
3298    /**
3299     * Loads the given bounds object with the transformed bounds relative to,
3300     * and based on, the given transform. That is, this is the local bounds
3301     * with the local-to-parent transform applied.
3302     *
3303     * We *never* pass null in as a bounds. This method will
3304     * NOT take a null bounds object. The returned value may be
3305     * the same bounds object passed in, or it may be a new object.
3306     * The reason for this object promotion is in the case of needing
3307     * to promote from a RectBounds to a BoxBounds (3D).
3308     */
3309    BaseBounds getTransformedBounds(BaseBounds bounds, BaseTransform tx) {
3310        updateLocalToParentTransform();
3311        if (tx.isTranslateOrIdentity()) {
3312            updateTxBounds();
3313            bounds = bounds.deriveWithNewBounds(txBounds);
3314            if (!tx.isIdentity()) {
3315                final double translateX = tx.getMxt();
3316                final double translateY = tx.getMyt();
3317                final double translateZ = tx.getMzt();
3318                bounds = bounds.deriveWithNewBounds(
3319                                    (float) (bounds.getMinX() + translateX),
3320                                    (float) (bounds.getMinY() + translateY),
3321                                    (float) (bounds.getMinZ() + translateZ),
3322                                    (float) (bounds.getMaxX() + translateX),
3323                                    (float) (bounds.getMaxY() + translateY),
3324                                    (float) (bounds.getMaxZ() + translateZ));
3325            }
3326            return bounds;
3327        } else if (localToParentTx.isIdentity()) {
3328            return getLocalBounds(bounds, tx);
3329        } else {
3330            double mxx = tx.getMxx();
3331            double mxy = tx.getMxy();
3332            double mxz = tx.getMxz();
3333            double mxt = tx.getMxt();
3334            double myx = tx.getMyx();
3335            double myy = tx.getMyy();
3336            double myz = tx.getMyz();
3337            double myt = tx.getMyt();
3338            double mzx = tx.getMzx();
3339            double mzy = tx.getMzy();
3340            double mzz = tx.getMzz();
3341            double mzt = tx.getMzt();
3342            BaseTransform boundsTx = tx.deriveWithConcatenation(localToParentTx);
3343            bounds = getLocalBounds(bounds, boundsTx);
3344            if (boundsTx == tx) {
3345                tx.restoreTransform(mxx, mxy, mxz, mxt,
3346                                    myx, myy, myz, myt,
3347                                    mzx, mzy, mzz, mzt);
3348            }
3349            return bounds;
3350        }
3351    }
3352
3353    /**
3354     * Loads the given bounds object with the local bounds relative to,
3355     * and based on, the given transform. That is, these are the geometric
3356     * bounds + clip and effect.
3357     *
3358     * We *never* pass null in as a bounds. This method will
3359     * NOT take a null bounds object. The returned value may be
3360     * the same bounds object passed in, or it may be a new object.
3361     * The reason for this object promotion is in the case of needing
3362     * to promote from a RectBounds to a BoxBounds (3D).
3363     */
3364    BaseBounds getLocalBounds(BaseBounds bounds, BaseTransform tx) {
3365        if (getEffect() != null || getClip() != null) {
3366            // We either get the bounds of the effect (if it isn't null)
3367            // or we get the geom bounds (if effect is null). We will then
3368            // intersect this with the clip.
3369            if (getEffect() != null) {
3370                BaseBounds b = getEffect().impl_getBounds(bounds, tx, this, boundsAccessor);
3371                bounds = bounds.deriveWithNewBounds(b);
3372            } else {
3373                bounds = getGeomBounds(bounds, tx);
3374            }
3375            // intersect with the clip. Take care with "bounds" as it may
3376            // actually be TEMP_BOUNDS, so we save off state
3377            if (getClip() != null) {
3378                //TODO: For 3D case the intersecting transformed bounds and
3379                //      transformed clip will not produce the correct bounds.
3380                double x1 = bounds.getMinX();
3381                double y1 = bounds.getMinY();
3382                double x2 = bounds.getMaxX();
3383                double y2 = bounds.getMaxY();
3384                double z1 = bounds.getMinZ();
3385                double z2 = bounds.getMaxZ();
3386                bounds = getClip().getTransformedBounds(bounds, tx);
3387                bounds.intersectWith((float)x1, (float)y1, (float)z1,
3388                        (float)x2, (float)y2, (float)z2);
3389            }
3390            // return the bounds
3391            return bounds;
3392        } else {
3393            // fast path, simply return geom bounds
3394            return getGeomBounds(bounds, tx);
3395        }
3396    }
3397
3398    /**
3399     * Loads the given bounds object with the geometric bounds relative to,
3400     * and based on, the given transform.
3401     *
3402     * We *never* pass null in as a bounds. This method will
3403     * NOT take a null bounds object. The returned value may be
3404     * the same bounds object passed in, or it may be a new object.
3405     * The reason for this object promotion is in the case of needing
3406     * to promote from a RectBounds to a BoxBounds (3D).
3407     */
3408    BaseBounds getGeomBounds(BaseBounds bounds, BaseTransform tx) {
3409        if (tx.isTranslateOrIdentity()) {
3410            // we can take a fast path since we know tx is either a simple
3411            // translation or is identity
3412            updateGeomBounds();
3413            bounds = bounds.deriveWithNewBounds(geomBounds);
3414            if (!tx.isIdentity()) {
3415                double translateX = tx.getMxt();
3416                double translateY = tx.getMyt();
3417                double translateZ = tx.getMzt();
3418                bounds = bounds.deriveWithNewBounds((float) (bounds.getMinX() + translateX),
3419                        (float) (bounds.getMinY() + translateY),
3420                        (float) (bounds.getMinZ() + translateZ),
3421                        (float) (bounds.getMaxX() + translateX),
3422                        (float) (bounds.getMaxY() + translateY),
3423                        (float) (bounds.getMaxZ() + translateZ));
3424            }
3425            return bounds;
3426        } else if (tx.is2D()
3427                && (tx.getType()
3428                & ~(BaseTransform.TYPE_UNIFORM_SCALE | BaseTransform.TYPE_TRANSLATION
3429                | BaseTransform.TYPE_FLIP | BaseTransform.TYPE_QUADRANT_ROTATION)) != 0) {
3430            // this is a non-uniform scale / non-quadrant rotate / skew transform
3431            return impl_computeGeomBounds(bounds, tx);
3432        } else {
3433            // 3D transformations and
3434            // selected 2D transformations (unifrom transform, flip, quadrant rotation).
3435            // These 2D transformation will yield tight bounds when applied on the pre-computed
3436            // geomBounds
3437            // Note: Transforming the local geomBounds into a 3D space will yield a bounds
3438            // that isn't as tight as transforming its geometry and compute it bounds.
3439            updateGeomBounds();
3440            return tx.transform(geomBounds, bounds);
3441        }
3442    }
3443
3444    /**
3445     * Computes the geometric bounds for this Node. This method is abstract
3446     * and must be implemented by each Node subclass.
3447     * @treatAsPrivate implementation detail
3448     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3449     */
3450    @Deprecated
3451    public abstract BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx);
3452
3453    /**
3454     * If necessary, recomputes the cached local bounds. If the bounds are not
3455     * invalid, then this method is a no-op.
3456     */
3457    void updateGeomBounds() {
3458        if (geomBoundsInvalid) {
3459            geomBounds = impl_computeGeomBounds(geomBounds, BaseTransform.IDENTITY_TRANSFORM);
3460            geomBoundsInvalid = false;
3461        }
3462    }
3463
3464    /**
3465     * If necessary, recomputes the cached transformed bounds.
3466     * If the cached transformed bounds are not invalid, then
3467     * this method is a no-op.
3468     */
3469    void updateTxBounds() {
3470        if (txBoundsInvalid) {
3471            updateLocalToParentTransform();
3472            txBounds = getLocalBounds(txBounds, localToParentTx);
3473            txBoundsInvalid = false;
3474        }
3475    }
3476
3477    /**
3478     * @treatAsPrivate implementation detail
3479     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3480     */
3481    @Deprecated
3482    protected abstract boolean impl_computeContains(double localX, double localY);
3483
3484    private double min4(double v1, double v2, double v3, double v4) {
3485        return Math.min(Math.min(v1, v2), Math.min(v3, v4));
3486    }
3487
3488    private double max4(double v1, double v2, double v3, double v4) {
3489        return Math.max(Math.max(v1, v2), Math.max(v3, v4));
3490    }
3491
3492    private double min8(double v1, double v2, double v3, double v4,
3493            double v5, double v6, double v7, double v8) {
3494        return Math.min(min4(v1, v2, v3, v4), min4(v5, v6, v7, v8));
3495    }
3496
3497    private double max8(double v1, double v2, double v3, double v4,
3498            double v5, double v6, double v7, double v8) {
3499        return Math.max(max4(v1, v2, v3, v4), max4(v5, v6, v7, v8));
3500    }
3501
3502    Bounds createBoundingBox(Point3D p1, Point3D p2, Point3D p3, Point3D p4,
3503            Point3D p5, Point3D p6, Point3D p7, Point3D p8) {
3504        double minX = min8(p1.getX(), p2.getX(), p3.getX(), p4.getX(),
3505                p5.getX(), p6.getX(), p7.getX(), p8.getX());
3506        double maxX = max8(p1.getX(), p2.getX(), p3.getX(), p4.getX(),
3507                p5.getX(), p6.getX(), p7.getX(), p8.getX());
3508        double minY = min8(p1.getY(), p2.getY(), p3.getY(), p4.getY(),
3509                p5.getY(), p6.getY(), p7.getY(), p8.getY());
3510        double maxY = max8(p1.getY(), p2.getY(), p3.getY(), p4.getY(),
3511                p5.getY(), p6.getY(), p7.getY(), p8.getY());
3512        double minZ = min8(p1.getZ(), p2.getZ(), p3.getZ(), p4.getZ(),
3513                p5.getZ(), p6.getZ(), p7.getZ(), p8.getZ());
3514        double maxZ = max8(p1.getZ(), p2.getZ(), p3.getZ(), p4.getZ(),
3515                p5.getZ(), p6.getZ(), p7.getZ(), p8.getZ());
3516
3517        return new BoundingBox(minX, minY, minZ, maxX - minX, maxY - minY, maxZ - minZ);
3518    }
3519
3520    Bounds createBoundingBox(Point2D p1, Point2D p2, Point2D p3, Point2D p4) {
3521        double minX = min4(p1.getX(), p2.getX(), p3.getX(), p4.getX());
3522        double maxX = max4(p1.getX(), p2.getX(), p3.getX(), p4.getX());
3523        double minY = min4(p1.getY(), p2.getY(), p3.getY(), p4.getY());
3524        double maxY = max4(p1.getY(), p2.getY(), p3.getY(), p4.getY());
3525
3526        return new BoundingBox(minX, minY, maxX - minX, maxY - minY);
3527    }
3528
3529    /*
3530     *                   Bounds Invalidation And Notification
3531     *
3532     *  The goal of this section is to efficiently propagate bounds
3533     *  invalidation through the scenegraph while also being semantically
3534     *  correct.
3535     *
3536     *  The code path for invalidation of layout bounds is somewhat confusing
3537     *  primarily due to performance enhancements and the desire to reduce the
3538     *  number of requestLayout() calls that are performed when layout bounds
3539     *  change. Before diving into layout bounds, I will first describe how
3540     *  normal bounds invalidation occurs.
3541     *
3542     *  When a node's geometry changes (for example, if the width of a
3543     *  Rectangle is changed) then the Node must call impl_geomChanged().
3544     *  Invoking this function will eventually clear all cached bounds and
3545     *  notify to each parent up the tree that their bounds may have changed.
3546     *
3547     *  After invalidating geomBounds (and after kicking off layout bounds
3548     *  notification), impl_geomChanged calls localBoundsChanged(). It should
3549     *  be noted that impl_geomChanged should only be called when the geometry
3550     *  of the node has changed such that it may result in the geom bounds
3551     *  actually changing.
3552     *
3553     *  localBoundsChanged() simply invalidates boundsInLocal and then calls
3554     *  transformedBoundsChanged().
3555     *
3556     *  transformedBoundsChanged() is responsible for invalidating
3557     *  boundsInParent and txBounds. If the Node is not visible, then there is
3558     *  no need to notify the parent of the bounds change because the parent's
3559     *  bounds do not include invisible nodes. If the node is visible, then
3560     *  it must tell the parent that this child node's bounds have changed.
3561     *  It is up to the parent to eventually invoke its own impl_geomChanged
3562     *  function. If instead of a parent this node has a clipParent, then the
3563     *  clipParent's localBoundsChanged() is called instead.
3564     *
3565     *  There are a few other ways in which we enter the invalidate steps
3566     *  beyond just the geometry changes. If the visibility of a Node changes,
3567     *  its own bounds are not affected but its parent's bounds are. So a
3568     *  special call to parent.childVisibilityChanged is made so the parent
3569     *  can react accordingly.
3570     *
3571     *  If a transform is changed (layoutX, layoutY, rotate, transforms, etc)
3572     *  then the transform must be invalidated. When a transform is invalidated,
3573     *  it must also invalidate the txBounds by invoking
3574     *  transformedBoundsChanged, which will in turn notify the parent as
3575     *  before.
3576     *
3577     *  If an effect is changed or replaced then the local bounds must be
3578     *  invalidated, as well as the transformedBounds and the parent notified
3579     *  of the change in bounds.
3580     *
3581     *  layoutBound is somewhat unique in that it can be redefined in
3582     *  subclasses. By default, the layoutBounds is the geomBounds, and so
3583     *  whenever the impl_geomBounds() function is called the layoutBounds
3584     *  must be invalidated. However in subclasses, especially Resizables,
3585     *  the layout bounds may not be defined to be the same as the geometric
3586     *  bounds. This is both useful and provides a very nice performance
3587     *  optimization for regions and controls. In this case, subclasses
3588     *  need some way to interpose themselves such that a call to
3589     *  impl_geomChanged() *does not* invalidate the layout bounds.
3590     *
3591     *  This interposition happens by providing the
3592     *  impl_notifyLayoutBoundsChanged function. The default implementation
3593     *  simply invalidates boundsInLocal. Subclasses (such as Region and
3594     *  Control) can override this function so that it does not invalidate
3595     *  the layout bounds.
3596     *
3597     *  An on invalidate trigger on layoutBounds handles kicking off the rest
3598     *  of the invalidate process for layoutBounds. Because the layout bounds
3599     *  define the pivot point, if scaleX, scaleY, or rotate contain
3600     *  non-identity values then whenever the layoutBounds change the
3601     *  transformed bounds also change. Finally, if this node's parent is
3602     *  a Region and if the Node is being managed by the Region, then
3603     *  we must call requestLayout on the Region whenever the layout bounds
3604     *  have changed.
3605     */
3606
3607    /**
3608     * Invoked by subclasses whenever their geometric bounds have changed.
3609     * Because the default layout bounds is based on the node geometry, this
3610     * function will invoke impl_notifyLayoutBoundsChanged. The default
3611     * implementation of impl_notifyLayoutBoundsChanged() will simply invalidate
3612     * layoutBounds. Resizable subclasses will want to override this function
3613     * in most cases to be a no-op.
3614     * <p>
3615     * This function will also invalidate the cached geom bounds, and then
3616     * invoke localBoundsChanged() which will eventually end up invoking a
3617     * chain of functions up the tree to ensure that each parent of this
3618     * Node is notified that its bounds may have also changed.
3619     * <p>
3620     * This function should be treated as though it were final. It is not
3621     * intended to be overridden by subclasses.
3622     *
3623     * @treatAsPrivate implementation detail
3624     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3625     */
3626    @Deprecated
3627    protected void impl_geomChanged() {
3628        if (geomBoundsInvalid) {
3629            // GeomBoundsInvalid is false when node geometry changed and
3630            // the untransformed node bounds haven't been recalculated yet.
3631            // Most of the time, the recalculation of layout and transformed
3632            // node bounds don't require validation of untransformed bounds
3633            // and so we can not skip the following notifications.
3634            impl_notifyLayoutBoundsChanged();
3635            transformedBoundsChanged();
3636            return;
3637        }
3638        geomBounds.makeEmpty();
3639        geomBoundsInvalid = true;
3640        impl_markDirty(DirtyBits.NODE_BOUNDS);
3641        impl_notifyLayoutBoundsChanged();
3642        localBoundsChanged();
3643    }
3644
3645    private boolean geomBoundsInvalid = true;
3646    private boolean txBoundsInvalid = true;
3647
3648    /**
3649     * Responds to changes in the local bounds by invalidating boundsInLocal
3650     * and notifying this node that its transformed bounds have changed.
3651     */
3652    void localBoundsChanged() {
3653        invalidateBoundsInLocal();
3654        transformedBoundsChanged();
3655    }
3656
3657    /**
3658     * Responds to changes in the transformed bounds by invalidating txBounds
3659     * and boundsInParent. If this Node is not visible, then we have no need
3660     * to walk further up the tree but can instead simply invalidate state.
3661     * Otherwise, this function will notify parents (either the parent or the
3662     * clipParent) that this child Node's bounds have changed.
3663     */
3664    void transformedBoundsChanged() {
3665        if (txBoundsInvalid) {
3666            return;
3667        }
3668        txBounds.makeEmpty();
3669        txBoundsInvalid = true;
3670        invalidateBoundsInParent();
3671        if (isVisible()) {
3672            notifyParentOfBoundsChange();
3673        }
3674        impl_markDirty(DirtyBits.NODE_TRANSFORMED_BOUNDS);
3675    }
3676
3677    /**
3678     * Invoked by impl_geomChanged(). Since layoutBounds is by default based
3679     * on the geometric bounds, the default implementation of this function will
3680     * invalidate the layoutBounds. Resizable Node subclasses generally base
3681     * layoutBounds on the width/height instead of the geometric bounds, and so
3682     * will generally want to override this function to be a no-op.
3683     *
3684     * @treatAsPrivate implementation detail
3685     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3686     */
3687    @Deprecated
3688    protected void impl_notifyLayoutBoundsChanged() {
3689        impl_layoutBoundsChanged();
3690    }
3691
3692    /**
3693     * Notifies both the real parent and the clip parent (if they exist) that
3694     * the bounds of the child has changed. Note that since FX doesn't throw
3695     * NPE's, things actually are faster if we don't check twice for Null
3696     * (we check once, the compiler checks again)
3697     */
3698    void notifyParentOfBoundsChange() {
3699        // let the parent know which node has changed and the parent will
3700        // deal with marking itself invalid correctly
3701        Parent p = getParent();
3702        if (p != null) {
3703            p.childBoundsChanged(this);
3704        }
3705        // since the clip is used to compute the local bounds (and not the
3706        // geom bounds), we just need to notify that local bounds on the
3707        // clip parent have changed
3708        if (clipParent != null) {
3709            clipParent.localBoundsChanged();
3710        }
3711    }
3712
3713    /***************************************************************************
3714     *                                                                         *
3715     * Geometry and coordinate system related APIs. For example, methods       *
3716     * related to containment, intersection, coordinate space conversion, etc. *
3717     *                                                                         *
3718     **************************************************************************/
3719
3720    /**
3721     * Returns {@code true} if the given point (specified in the local
3722     * coordinate space of this {@code Node}) is contained within the shape of
3723     * this {@code Node}. Note that this method does not take visibility into
3724     * account; the test is based on the geometry of this {@code Node} only.
3725     */
3726    public boolean contains(double localX, double localY) {
3727        if (containsBounds(localX, localY)) {
3728            return (isPickOnBounds() || impl_computeContains(localX, localY));
3729        }
3730        return false;
3731    }
3732
3733    /**
3734     * This method only does the contains check based on the bounds, clip and
3735     * effect of this node, excluding its shape (or geometry).
3736     *
3737     * Returns true if the given point (specified in the local
3738     * coordinate space of this {@code Node}) is contained within the bounds,
3739     * clip and effect of this node.
3740     * @treatAsPrivate implementation detail
3741     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
3742     */
3743    @Deprecated
3744    protected boolean containsBounds(double localX, double localY) {
3745          final TempState tempState = TempState.getInstance();
3746        BaseBounds tempBounds = tempState.bounds;
3747
3748        // first, we do a quick test to see if the point is contained in
3749        // our local bounds. If so, then we will go the next step and check
3750        // the clip, effect, and geometry for containment.
3751        tempBounds = getLocalBounds(tempBounds,
3752                                    BaseTransform.IDENTITY_TRANSFORM);
3753        if (tempBounds.contains((float)localX, (float)localY)) {
3754            // if the clip is defined, then check it for containment, being
3755            // sure to convert from this node's local coordinate system
3756            // to the local coordinate system of the clip node
3757            if (getClip() != null) {
3758                tempState.point.x = (float)localX;
3759                tempState.point.y = (float)localY;
3760                try {
3761                    getClip().parentToLocal(tempState.point);
3762                } catch (NoninvertibleTransformException e) {
3763                    return false;
3764                }
3765                if (!getClip().contains(tempState.point.x, tempState.point.y)) {
3766                    return false;
3767                }
3768            }
3769            // if there is an effect, then we need to check the effect
3770            // for containment as well.
3771            if (getEffect() != null) {
3772                tempBounds = getEffect().impl_getBounds(
3773                                    tempBounds,
3774                                    BaseTransform.IDENTITY_TRANSFORM,
3775                                    this, boundsAccessor);
3776
3777                if (!tempBounds.contains((float)localX, (float)localY)) {
3778                    return false;
3779                }
3780            }
3781            return true;
3782        }
3783        return false;
3784    }
3785
3786    /**
3787     * Returns {@code true} if the given point (specified in the local
3788     * coordinate space of this {@code Node}) is contained within the shape of
3789     * this {@code Node}. Note that this method does not take visibility into
3790     * account; the test is based on the geometry of this {@code Node} only.
3791     */
3792    public boolean contains(Point2D localPoint) {
3793        return contains(localPoint.getX(), localPoint.getY());
3794    }
3795
3796    /**
3797     * Returns {@code true} if the given rectangle (specified in the local
3798     * coordinate space of this {@code Node}) intersects the shape of this
3799     * {@code Node}. Note that this method does not take visibility into
3800     * account; the test is based on the geometry of this {@code Node} only.
3801     * The default behavior of this function is simply to check if the
3802     * given coordinates intersect with the local bounds.
3803     */
3804    public boolean intersects(double localX, double localY, double localWidth, double localHeight) {
3805        BaseBounds tempBounds = TempState.getInstance().bounds;
3806        tempBounds = getLocalBounds(tempBounds,
3807                                    BaseTransform.IDENTITY_TRANSFORM);
3808        return tempBounds.intersects((float)localX,
3809                                     (float)localY,
3810                                     (float)localWidth,
3811                                     (float)localHeight);
3812    }
3813
3814    /**
3815     * Returns {@code true} if the given bounds (specified in the local
3816     * coordinate space of this {@code Node}) intersects the shape of this
3817     * {@code Node}. Note that this method does not take visibility into
3818     * account; the test is based on the geometry of this {@code Node} only.
3819     * The default behavior of this function is simply to check if the
3820     * given coordinates intersect with the local bounds.
3821     */
3822    public boolean intersects(Bounds localBounds) {
3823        return intersects(localBounds.getMinX(), localBounds.getMinY(), localBounds.getWidth(), localBounds.getHeight());
3824    }
3825
3826    /**
3827     * Transforms a point from the coordinate space of the {@link Screen}
3828     * into the local coordinate space of this {@code Node}.
3829     * @param screenX x coordinate of a point on a Screen
3830     * @param screenY y coordinate of a point on a Screen
3831     * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
3832     * Null is also returned if the transformation from local to Scene is not invertible.
3833     */
3834    public Point2D screenToLocal(double screenX, double screenY) {
3835        Scene scene = getScene();
3836        Window window = scene.getWindow();
3837        if (scene == null || window == null) {
3838            return null;
3839        }
3840        final com.sun.javafx.geom.Point2D tempPt =
3841                TempState.getInstance().point;
3842
3843        tempPt.setLocation((float)(screenX - scene.getX() - window.getX()),
3844                           (float)(screenY - scene.getY() - window.getY()));
3845
3846        final SubScene subScene = getSubScene();
3847        if (subScene != null) {
3848            final Point2D ssCoord = SceneUtils.sceneToSubScenePlane(subScene,
3849                    new Point2D(tempPt.x, tempPt.y));
3850            if (ssCoord == null) {
3851                return null;
3852            }
3853            tempPt.setLocation((float) ssCoord.getX(), (float) ssCoord.getY());
3854        }
3855
3856        final Point3D ppIntersect =
3857                scene.getEffectiveCamera().pickProjectPlane(tempPt.x, tempPt.y);
3858        tempPt.setLocation((float) ppIntersect.getX(), (float) ppIntersect.getY());
3859
3860        try {
3861            sceneToLocal(tempPt);
3862        } catch (NoninvertibleTransformException e) {
3863            return null;
3864        }
3865        return new Point2D(tempPt.x, tempPt.y);
3866    }
3867
3868    /**
3869     * Transforms a point from the coordinate space of the {@link javafx.stage.Screen}
3870     * into the local coordinate space of this {@code Node}.
3871     * @param screenPoint a point on a Screen
3872     * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
3873     * Null is also returned if the transformation from local to Scene is not invertible.
3874     */
3875    public Point2D screenToLocal(Point2D screenPoint) {
3876        return screenToLocal(screenPoint.getX(), screenPoint.getY());
3877    }
3878
3879    /**
3880     * Transforms a rectangle from the coordinate space of the
3881     * {@link javafx.stage.Screen} into the local coordinate space of this
3882     * {@code Node}. Returns reasonable result only in 2D space.
3883     * @param screenBounds bounds on a Screen
3884     * @return bounds in the local Node'space or null if Node is not in a {@link Window}.
3885     * Null is also returned if the transformation from local to Scene is not invertible.
3886     */
3887    public Bounds screenToLocal(Bounds screenBounds) {
3888        final Point2D p1 = screenToLocal(screenBounds.getMinX(), screenBounds.getMinY());
3889        final Point2D p2 = screenToLocal(screenBounds.getMinX(), screenBounds.getMaxY());
3890        final Point2D p3 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMinY());
3891        final Point2D p4 = screenToLocal(screenBounds.getMaxX(), screenBounds.getMaxY());
3892
3893        if (p1 == null || p2 == null || p3 == null || p4 == null) {
3894            return null;
3895        }
3896
3897        final double minX = min4(p1.getX(), p2.getX(), p3.getX(), p4.getX());
3898        final double maxX = max4(p1.getX(), p2.getX(), p3.getX(), p4.getX());
3899        final double minY = min4(p1.getY(), p2.getY(), p3.getY(), p4.getY());
3900        final double maxY = max4(p1.getY(), p2.getY(), p3.getY(), p4.getY());
3901
3902        return new BoundingBox(minX, minY, 0.0, maxX - minX, maxY - minY, 0.0);
3903    }
3904
3905    /**
3906     * Transforms a point from the coordinate space of the {@link Scene}
3907     * into the local coordinate space of this {@code Node}.
3908     * @param sceneX x coordinate of a point on a Scene
3909     * @param sceneY y coordinate of a point on a Scene
3910     * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
3911     * Null is also returned if the transformation from local to Scene is not invertible.
3912     */
3913    public Point2D sceneToLocal(double sceneX, double sceneY) {
3914        final com.sun.javafx.geom.Point2D tempPt =
3915                TempState.getInstance().point;
3916        tempPt.setLocation((float)sceneX, (float)sceneY);
3917        try {
3918            sceneToLocal(tempPt);
3919        } catch (NoninvertibleTransformException e) {
3920            return null;
3921        }
3922        return new Point2D(tempPt.x, tempPt.y);
3923    }
3924
3925    /**
3926     * Transforms a point from the coordinate space of the {@link javafx.scene.Scene}
3927     * into the local coordinate space of this {@code Node}.
3928     * @param scenePoint a point on a Scene
3929     * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
3930     * Null is also returned if the transformation from local to Scene is not invertible.
3931     */
3932    public Point2D sceneToLocal(Point2D scenePoint) {
3933        return sceneToLocal(scenePoint.getX(), scenePoint.getY());
3934    }
3935
3936    /**
3937     * Transforms a point from the coordinate space of the {@link javafx.scene.Scene}
3938     * into the local coordinate space of this {@code Node}.
3939     * @param scenePoint a point on a Scene
3940     * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
3941     * Null is also returned if the transformation from local to Scene is not invertible.
3942     */
3943    public Point3D sceneToLocal(Point3D scenePoint) {
3944        return sceneToLocal(scenePoint.getX(), scenePoint.getY(), scenePoint.getZ());
3945    }
3946
3947    /**
3948     * Transforms a point from the coordinate space of the {@link javafx.scene.Scene}
3949     * into the local coordinate space of this {@code Node}.
3950     * @param sceneX x coordinate of a point on a Scene
3951     * @param sceneY y coordinate of a point on a Scene
3952     * @param sceneZ z coordinate of a point on a Scene
3953     * @return local Node's coordinates of the point or null if Node is not in a {@link Window}.
3954     * Null is also returned if the transformation from local to Scene is not invertible.
3955     */
3956    public Point3D sceneToLocal(double sceneX, double sceneY, double sceneZ) {
3957        try {
3958            return sceneToLocal0(sceneX, sceneY, sceneZ);
3959        } catch (NoninvertibleTransformException ex) {
3960            return null;
3961        }
3962    }
3963
3964    /**
3965     * Internal method to transform a point from scene to local coordinates.
3966     */
3967    private Point3D sceneToLocal0(double x, double y, double z) throws NoninvertibleTransformException {
3968        final com.sun.javafx.geom.Vec3d tempV3D =
3969                TempState.getInstance().vec3d;
3970        tempV3D.set(x, y, z);
3971        sceneToLocal(tempV3D);
3972        return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
3973    }
3974
3975    /**
3976     * Transforms a rectangle from the coordinate space of the
3977     * {@link javafx.scene.Scene} into the local coordinate space of this
3978     * {@code Node}.
3979     * @param sceneBounds bounds on a Scene
3980     * @return bounds in the local Node'space or null if Node is not in a {@link Window}.
3981     * Null is also returned if the transformation from local to Scene is not invertible.
3982     */
3983    public Bounds sceneToLocal(Bounds sceneBounds) {
3984        // Do a quick update of localToParentTransform so that we can determine
3985        // if this tx is 2D transform
3986        updateLocalToParentTransform();
3987        if (localToParentTx.is2D() && (sceneBounds.getMinZ() == 0) && (sceneBounds.getMaxZ() == 0)) {
3988            Point2D p1 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMinY());
3989            Point2D p2 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMinY());
3990            Point2D p3 = sceneToLocal(sceneBounds.getMaxX(), sceneBounds.getMaxY());
3991            Point2D p4 = sceneToLocal(sceneBounds.getMinX(), sceneBounds.getMaxY());
3992
3993            return createBoundingBox(p1, p2, p3, p4);
3994        }
3995        try {
3996            Point3D p1 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
3997            Point3D p2 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
3998            Point3D p3 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
3999            Point3D p4 = sceneToLocal0(sceneBounds.getMinX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
4000            Point3D p5 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMinZ());
4001            Point3D p6 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMaxY(), sceneBounds.getMaxZ());
4002            Point3D p7 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMinZ());
4003            Point3D p8 = sceneToLocal0(sceneBounds.getMaxX(), sceneBounds.getMinY(), sceneBounds.getMaxZ());
4004            return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4005        } catch (NoninvertibleTransformException e) {
4006            return null;
4007        }
4008    }
4009
4010    /**
4011     * Transforms a point from the local coordinate space of this {@code Node}
4012     * into the coordinate space of its {@link javafx.stage.Screen}.
4013     * @param localX x coordinate of a point in Node's space
4014     * @param localY y coordinate of a point in Node's space
4015     * @return screen coordinates of the point or null if Node is not in a {@link Window}
4016     */
4017    public Point2D localToScreen(double localX, double localY) {
4018        return localToScreen(localX, localY, 0.0);
4019    }
4020
4021    /**
4022     * Transforms a point from the local coordinate space of this {@code Node}
4023     * into the coordinate space of its {@link javafx.stage.Screen}.
4024     * @param localPoint a point in Node's space
4025     * @return screen coordinates of the point or null if Node is not in a {@link Window}
4026     */
4027    public Point2D localToScreen(Point2D localPoint) {
4028        return localToScreen(localPoint.getX(), localPoint.getY());
4029    }
4030
4031    /**
4032     * Transforms a point from the local coordinate space of this {@code Node}
4033     * into the coordinate space of its {@link javafx.stage.Screen}.
4034     * @param localX x coordinate of a point in Node's space
4035     * @param localY y coordinate of a point in Node's space
4036     * @param localZ z coordinate of a point in Node's space
4037     * @return screen coordinates of the point or null if Node is not in a {@link Window}
4038     */
4039    public Point2D localToScreen(double localX, double localY, double localZ) {
4040        Scene scene = getScene();
4041        Window window = scene.getWindow();
4042        if (scene == null || window == null) {
4043            return null;
4044        }
4045
4046        Point3D pt = localToScene(localX, localY, localZ);
4047        final SubScene subScene = getSubScene();
4048        if (subScene != null) {
4049            pt = SceneUtils.subSceneToScene(subScene, pt);
4050        }
4051        final Point2D projection = CameraHelper.project(
4052                SceneHelper.getEffectiveCamera(getScene()), pt);
4053
4054        return new Point2D(projection.getX() + scene.getX() + window.getX(),
4055                           projection.getY() + scene.getY() + window.getY());
4056    }
4057
4058    /**
4059     * Transforms a point from the local coordinate space of this {@code Node}
4060     * into the coordinate space of its {@link javafx.stage.Screen}.
4061     * @param localPoint a point in Node's space
4062     * @return screen coordinates of the point or null if Node is not in a {@link Window}
4063     */
4064    public Point2D localToScreen(Point3D localPoint) {
4065        return localToScreen(localPoint.getX(), localPoint.getY(), localPoint.getZ());
4066    }
4067
4068    /**
4069     * Transforms a bounds from the local coordinate space of this
4070     * {@code Node} into the coordinate space of its {@link javafx.stage.Screen}.
4071     * @param localBounds bounds in Node's space
4072     * @return the bounds in screen coordinates or null if Node is not in a {@link Window}
4073     */
4074    public Bounds localToScreen(Bounds localBounds) {
4075        final Point2D p1 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
4076        final Point2D p2 = localToScreen(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
4077        final Point2D p3 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
4078        final Point2D p4 = localToScreen(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
4079        final Point2D p5 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
4080        final Point2D p6 = localToScreen(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
4081        final Point2D p7 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
4082        final Point2D p8 = localToScreen(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
4083
4084        if (p1 == null || p2 == null || p3 == null || p4 == null
4085                || p5 == null || p6 == null || p7 == null || p8 == null) {
4086            return null;
4087        }
4088
4089        final double minX = min8(p1.getX(), p2.getX(), p3.getX(), p4.getX(),
4090                p5.getX(), p6.getX(), p7.getX(), p8.getX());
4091        final double maxX = max8(p1.getX(), p2.getX(), p3.getX(), p4.getX(),
4092                p5.getX(), p6.getX(), p7.getX(), p8.getX());
4093        final double minY = min8(p1.getY(), p2.getY(), p3.getY(), p4.getY(),
4094                p5.getY(), p6.getY(), p7.getY(), p8.getY());
4095        final double maxY = max8(p1.getY(), p2.getY(), p3.getY(), p4.getY(),
4096                p5.getY(), p6.getY(), p7.getY(), p8.getY());
4097
4098        return new BoundingBox(minX, minY, maxX - minX, maxY - minY);
4099    }
4100
4101    /**
4102     * Transforms a point from the local coordinate space of this {@code Node}
4103     * into the coordinate space of its {@link javafx.scene.Scene}.
4104     * @param localX x coordinate of a point in Node's space
4105     * @param localY y coordinate of a point in Node's space
4106     * @return scene coordinates of the point or null if Node is not in a {@link Window}
4107     */
4108    public Point2D localToScene(double localX, double localY) {
4109        final com.sun.javafx.geom.Point2D tempPt =
4110                TempState.getInstance().point;
4111        tempPt.setLocation((float)localX, (float)localY);
4112        localToScene(tempPt);
4113        return new Point2D(tempPt.x, tempPt.y);
4114    }
4115
4116    /**
4117     * Transforms a point from the local coordinate space of this {@code Node}
4118     * into the coordinate space of its {@link javafx.scene.Scene}.
4119     * @param localPoint a point in Node's space
4120     * @return scene coordinates of the point or null if Node is not in a {@link Window}
4121     */
4122    public Point2D localToScene(Point2D localPoint) {
4123        return localToScene(localPoint.getX(), localPoint.getY());
4124    }
4125
4126    /**
4127     * Transforms a point from the local coordinate space of this {@code Node}
4128     * into the coordinate space of its {@link javafx.scene.Scene}.
4129     */
4130    public Point3D localToScene(Point3D localPoint) {
4131        return localToScene(localPoint.getX(), localPoint.getY(), localPoint.getZ());
4132    }
4133
4134    /**
4135     * Transforms a point from the local coordinate space of this {@code Node}
4136     * into the coordinate space of its {@link javafx.scene.Scene}.
4137     */
4138    public Point3D localToScene(double x, double y, double z) {
4139        final com.sun.javafx.geom.Vec3d tempV3D =
4140                TempState.getInstance().vec3d;
4141        tempV3D.set(x, y, z);
4142        localToScene(tempV3D);
4143        return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
4144    }
4145
4146    /**
4147     * Transforms a bounds from the local coordinate space of this
4148     * {@code Node} into the coordinate space of its {@link javafx.scene.Scene}.
4149     * @param localBounds bounds in Node's space
4150     * @return the bounds in the scene coordinates or null if Node is not in a {@link Window}
4151     */
4152    public Bounds localToScene(Bounds localBounds) {
4153        // Do a quick update of localToParentTransform so that we can determine
4154        // if this tx is 2D transform
4155        updateLocalToParentTransform();
4156        if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
4157            Point2D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY());
4158            Point2D p2 = localToScene(localBounds.getMaxX(), localBounds.getMinY());
4159            Point2D p3 = localToScene(localBounds.getMaxX(), localBounds.getMaxY());
4160            Point2D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY());
4161
4162            return createBoundingBox(p1, p2, p3, p4);
4163        }
4164        Point3D p1 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
4165        Point3D p2 = localToScene(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
4166        Point3D p3 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
4167        Point3D p4 = localToScene(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
4168        Point3D p5 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
4169        Point3D p6 = localToScene(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
4170        Point3D p7 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
4171        Point3D p8 = localToScene(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
4172        return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4173
4174    }
4175
4176    /**
4177     * Transforms a point from the coordinate space of the parent into the
4178     * local coordinate space of this {@code Node}.
4179     */
4180    public Point2D parentToLocal(double parentX, double parentY) {
4181        final com.sun.javafx.geom.Point2D tempPt =
4182                TempState.getInstance().point;
4183        tempPt.setLocation((float)parentX, (float)parentY);
4184        try {
4185            parentToLocal(tempPt);
4186        } catch (NoninvertibleTransformException e) {
4187            return null;
4188        }
4189        return new Point2D(tempPt.x, tempPt.y);
4190    }
4191
4192    /**
4193     * Transforms a point from the coordinate space of the parent into the
4194     * local coordinate space of this {@code Node}.
4195     */
4196    public Point2D parentToLocal(Point2D parentPoint) {
4197        return parentToLocal(parentPoint.getX(), parentPoint.getY());
4198    }
4199
4200    /**
4201     * Transforms a point from the coordinate space of the parent into the
4202     * local coordinate space of this {@code Node}.
4203     */
4204    public Point3D parentToLocal(Point3D parentPoint) {
4205        return parentToLocal(parentPoint.getX(), parentPoint.getY(), parentPoint.getZ());
4206    }
4207
4208    /**
4209     * Transforms a point from the coordinate space of the parent into the
4210     * local coordinate space of this {@code Node}.
4211     */
4212    public Point3D parentToLocal(double parentX, double parentY, double parentZ) {
4213        final com.sun.javafx.geom.Vec3d tempV3D =
4214                TempState.getInstance().vec3d;
4215        tempV3D.set(parentX, parentY, parentZ);
4216        try {
4217            parentToLocal(tempV3D);
4218        } catch (NoninvertibleTransformException e) {
4219            return null;
4220        }
4221        return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
4222    }
4223
4224    /**
4225     * Transforms a rectangle from the coordinate space of the parent into the
4226     * local coordinate space of this {@code Node}.
4227     */
4228    public Bounds parentToLocal(Bounds parentBounds) {
4229        // Do a quick update of localToParentTransform so that we can determine
4230        // if this tx is 2D transform
4231        updateLocalToParentTransform();
4232        if (localToParentTx.is2D() && (parentBounds.getMinZ() == 0) && (parentBounds.getMaxZ() == 0)) {
4233            Point2D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY());
4234            Point2D p2 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY());
4235            Point2D p3 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY());
4236            Point2D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY());
4237
4238            return createBoundingBox(p1, p2, p3, p4);
4239        }
4240        Point3D p1 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMinZ());
4241        Point3D p2 = parentToLocal(parentBounds.getMinX(), parentBounds.getMinY(), parentBounds.getMaxZ());
4242        Point3D p3 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMinZ());
4243        Point3D p4 = parentToLocal(parentBounds.getMinX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
4244        Point3D p5 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMinZ());
4245        Point3D p6 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMaxY(), parentBounds.getMaxZ());
4246        Point3D p7 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMinZ());
4247        Point3D p8 = parentToLocal(parentBounds.getMaxX(), parentBounds.getMinY(), parentBounds.getMaxZ());
4248        return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4249    }
4250
4251    /**
4252     * Transforms a point from the local coordinate space of this {@code Node}
4253     * into the coordinate space of its parent.
4254     */
4255    public Point2D localToParent(double localX, double localY) {
4256        final com.sun.javafx.geom.Point2D tempPt =
4257                TempState.getInstance().point;
4258        tempPt.setLocation((float)localX, (float)localY);
4259        localToParent(tempPt);
4260        return new Point2D(tempPt.x, tempPt.y);
4261    }
4262
4263    /**
4264     * Transforms a point from the local coordinate space of this {@code Node}
4265     * into the coordinate space of its parent.
4266     */
4267    public Point2D localToParent(Point2D localPoint) {
4268        return localToParent(localPoint.getX(), localPoint.getY());
4269    }
4270
4271    /**
4272     * Transforms a point from the local coordinate space of this {@code Node}
4273     * into the coordinate space of its parent.
4274     */
4275    public Point3D localToParent(Point3D localPoint) {
4276        return localToParent(localPoint.getX(), localPoint.getY(), localPoint.getZ());
4277    }
4278
4279    /**
4280     * Transforms a point from the local coordinate space of this {@code Node}
4281     * into the coordinate space of its parent.
4282     */
4283    public Point3D localToParent(double x, double y, double z) {
4284        final com.sun.javafx.geom.Vec3d tempV3D =
4285                TempState.getInstance().vec3d;
4286        tempV3D.set(x, y, z);
4287        localToParent(tempV3D);
4288        return new Point3D(tempV3D.x, tempV3D.y, tempV3D.z);
4289    }
4290
4291    /**
4292     * Transforms a bounds from the local coordinate space of this
4293     * {@code Node} into the coordinate space of its parent.
4294     */
4295    public Bounds localToParent(Bounds localBounds) {
4296        // Do a quick update of localToParentTransform so that we can determine
4297        // if this tx is 2D transform
4298        updateLocalToParentTransform();
4299        if (localToParentTx.is2D() && (localBounds.getMinZ() == 0) && (localBounds.getMaxZ() == 0)) {
4300            Point2D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY());
4301            Point2D p2 = localToParent(localBounds.getMaxX(), localBounds.getMinY());
4302            Point2D p3 = localToParent(localBounds.getMaxX(), localBounds.getMaxY());
4303            Point2D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY());
4304
4305            return createBoundingBox(p1, p2, p3, p4);
4306        }
4307        Point3D p1 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMinZ());
4308        Point3D p2 = localToParent(localBounds.getMinX(), localBounds.getMinY(), localBounds.getMaxZ());
4309        Point3D p3 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMinZ());
4310        Point3D p4 = localToParent(localBounds.getMinX(), localBounds.getMaxY(), localBounds.getMaxZ());
4311        Point3D p5 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMinZ());
4312        Point3D p6 = localToParent(localBounds.getMaxX(), localBounds.getMaxY(), localBounds.getMaxZ());
4313        Point3D p7 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMinZ());
4314        Point3D p8 = localToParent(localBounds.getMaxX(), localBounds.getMinY(), localBounds.getMaxZ());
4315        return createBoundingBox(p1, p2, p3, p4, p5, p6, p7, p8);
4316    }
4317
4318    /**
4319     * Copy the localToParent transform into specified transform.
4320     */
4321    BaseTransform getLocalToParentTransform(BaseTransform tx) {
4322        updateLocalToParentTransform();
4323        tx.setTransform(localToParentTx);
4324        return tx;
4325    }
4326
4327    /**
4328     * Currently used only by AnimationPath.createAnimationPathHelper().
4329     * @treatAsPrivate implementation detail
4330     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4331     */
4332    @Deprecated
4333    public final BaseTransform impl_getLeafTransform() {
4334        return getLocalToParentTransform(TempState.getInstance().leafTx);
4335    }
4336
4337    /**
4338     * Invoked whenever the transforms[] ObservableList changes, or by the transforms
4339     * in that ObservableList whenever they are changed.
4340     * @treatAsPrivate implementation detail
4341     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4342     */
4343    @Deprecated
4344    public void impl_transformsChanged() {
4345        if (!transformDirty) {
4346            impl_markDirty(DirtyBits.NODE_TRANSFORM);
4347            transformDirty = true;
4348            transformedBoundsChanged();
4349        }
4350        invalidateLocalToParentTransform();
4351        invalidateLocalToSceneTransform();
4352    }
4353
4354    /**
4355     * @treatAsPrivate implementation detail
4356     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4357     */
4358    @Deprecated
4359    public final double impl_getPivotX() {
4360        final Bounds bounds = getLayoutBounds();
4361        return bounds.getMinX() + bounds.getWidth()/2;
4362    }
4363
4364    /**
4365     * @treatAsPrivate implementation detail
4366     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4367     */
4368    @Deprecated
4369    public final double impl_getPivotY() {
4370        final Bounds bounds = getLayoutBounds();
4371        return bounds.getMinY() + bounds.getHeight()/2;
4372    }
4373
4374    /**
4375     * @treatAsPrivate implementation detail
4376     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4377     */
4378    @Deprecated
4379    public final double impl_getPivotZ() {
4380        final Bounds bounds = getLayoutBounds();
4381        return bounds.getMinZ() + bounds.getDepth()/2;
4382    }
4383
4384    /**
4385     * This helper function will update the transform matrix on the peer based
4386     * on the "complete" transform for this node.
4387     */
4388    void updateLocalToParentTransform() {
4389        if (transformDirty) {
4390            localToParentTx.setToIdentity();
4391            if (getScaleX() != 1 || getScaleY() != 1 || getScaleZ() != 1 || getRotate() != 0) {
4392                // recompute pivotX, pivotY and pivotZ
4393                double pivotX = impl_getPivotX();
4394                double pivotY = impl_getPivotY();
4395                double pivotZ = impl_getPivotZ();
4396                localToParentTx.translate(getTranslateX() + getLayoutX() + pivotX, getTranslateY() + getLayoutY() + pivotY, getTranslateZ() + pivotZ);
4397                localToParentTx.rotate(Math.toRadians(getRotate()), getRotationAxis().getX(), getRotationAxis().getY(), getRotationAxis().getZ());
4398                localToParentTx.scale(getScaleX(), getScaleY(), getScaleZ());
4399                localToParentTx.translate(-pivotX, -pivotY, -pivotZ);
4400            } else {
4401                localToParentTx.translate(getTranslateX() + getLayoutX(), getTranslateY() + getLayoutY(), getTranslateZ());
4402            }
4403
4404            if (impl_hasTransforms()) {
4405                for (Transform t : getTransforms()) {
4406                    t.impl_apply(localToParentTx);
4407                }
4408            }
4409
4410            // Check to see whether the node requires mirroring
4411            if (hasMirroring()) {
4412                final double xOffset = getMirroringCenter();
4413                localToParentTx.translate(xOffset, 0, 0);
4414                localToParentTx.scale(-1, 1);
4415                localToParentTx.translate(-xOffset, 0, 0);
4416            }
4417
4418            transformDirty = false;
4419        }
4420    }
4421
4422    private double getMirroringCenter() {
4423        final Scene sceneValue = getScene();
4424        return ((sceneValue != null) && (sceneValue.getRoot() == this))
4425                   ? sceneValue.getWidth() / 2
4426                   : impl_getPivotX();
4427    }
4428
4429    /**
4430     * Transforms in place the specified point from parent coords to local
4431     * coords. Made package private for the sake of testing.
4432     */
4433    void parentToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
4434        updateLocalToParentTransform();
4435        localToParentTx.inverseTransform(pt, pt);
4436    }
4437
4438    void parentToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
4439        updateLocalToParentTransform();
4440        localToParentTx.inverseTransform(pt, pt);
4441    }
4442
4443    void sceneToLocal(com.sun.javafx.geom.Point2D pt) throws NoninvertibleTransformException {
4444        if (getParent() != null) {
4445            getParent().sceneToLocal(pt);
4446        }
4447        parentToLocal(pt);
4448    }
4449
4450    void sceneToLocal(com.sun.javafx.geom.Vec3d pt) throws NoninvertibleTransformException {
4451        if (getParent() != null) {
4452            getParent().sceneToLocal(pt);
4453        }
4454        parentToLocal(pt);
4455    }
4456
4457    void localToScene(com.sun.javafx.geom.Point2D pt) {
4458        localToParent(pt);
4459        if (getParent() != null) {
4460            getParent().localToScene(pt);
4461        }
4462    }
4463
4464    void localToScene(com.sun.javafx.geom.Vec3d pt) {
4465        localToParent(pt);
4466        if (getParent() != null) {
4467            getParent().localToScene(pt);
4468        }
4469    }
4470
4471    /***************************************************************************
4472     *                                                                         *
4473     * Mouse event related APIs                                                *
4474     *                                                                         *
4475     **************************************************************************/
4476
4477    /**
4478     * Transforms in place the specified point from local coords to parent
4479     * coords. Made package private for the sake of testing.
4480     */
4481    void localToParent(com.sun.javafx.geom.Point2D pt) {
4482        updateLocalToParentTransform();
4483        localToParentTx.transform(pt, pt);
4484    }
4485
4486    void localToParent(com.sun.javafx.geom.Vec3d pt) {
4487        updateLocalToParentTransform();
4488        localToParentTx.transform(pt, pt);
4489    }
4490
4491    /**
4492     * Finds a top-most child node that contains the given local coordinates.
4493     *
4494     * The result argument is used for storing the picking result.
4495     * @treatAsPrivate implementation detail
4496     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4497     */
4498    @Deprecated
4499    protected void impl_pickNodeLocal(PickRay localPickRay, PickResultChooser result) {
4500        impl_intersects(localPickRay, result);
4501    }
4502
4503    /**
4504     * Finds a top-most child node that intersects the given ray.
4505     *
4506     * The result argument is used for storing the picking result.
4507     * @treatAsPrivate implementation detail
4508     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4509     */
4510    @Deprecated
4511    public final void impl_pickNode(PickRay pickRay, PickResultChooser result) {
4512
4513        // In some conditions we can omit picking this node or subgraph
4514        if (!isVisible() || isDisable() || isMouseTransparent()) {
4515            return;
4516        }
4517
4518        final Vec3d o = pickRay.getOriginNoClone();
4519        final double ox = o.x;
4520        final double oy = o.y;
4521        final double oz = o.z;
4522        final Vec3d d = pickRay.getDirectionNoClone();
4523        final double dx = d.x;
4524        final double dy = d.y;
4525        final double dz = d.z;
4526
4527        updateLocalToParentTransform();
4528        try {
4529            localToParentTx.inverseTransform(o, o);
4530            localToParentTx.inverseDeltaTransform(d, d);
4531
4532            // Delegate to a function which can be overridden by subclasses which
4533            // actually does the pick. The implementation is markedly different
4534            // for leaf nodes vs. parent nodes vs. region nodes.
4535            impl_pickNodeLocal(pickRay, result);
4536        } catch (NoninvertibleTransformException e) {
4537            // in this case we just don't pick anything
4538        }
4539
4540        pickRay.setOrigin(ox, oy, oz);
4541        pickRay.setDirection(dx, dy, dz);
4542    }
4543
4544    /**
4545     * Returns {@code true} if the given ray (start, dir), specified in the
4546     * local coordinate space of this {@code Node}, intersects the
4547     * shape of this {@code Node}. Note that this method does not take visibility
4548     * into account; the test is based on the geometry of this {@code Node} only.
4549     * <p>
4550     * The pickResult is updated if the found intersection is closer than
4551     * the currently held one.
4552     * <p>
4553     * Note that this is a conditional feature. See
4554     * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
4555     * for more information.
4556     *
4557     * @treatAsPrivate implementation detail
4558     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4559     */
4560    @Deprecated
4561    protected final boolean impl_intersects(PickRay pickRay, PickResultChooser pickResult) {
4562        double boundsDistance = impl_intersectsBounds(pickRay);
4563        if (!Double.isNaN(boundsDistance)) {
4564            if (isPickOnBounds()) {
4565                if (pickResult != null) {
4566                    pickResult.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance));
4567                }
4568                return true;
4569            } else {
4570                return impl_computeIntersects(pickRay, pickResult);
4571            }
4572        }
4573        return false;
4574    }
4575
4576    /**
4577     * Computes the intersection of the pickRay with this node.
4578     * The pickResult argument is updated if the found intersection
4579     * is closer than the passed one. On the other hand, the return value
4580     * specifies whether the intersection exists, regardless of its comparison
4581     * with the given pickResult.
4582     *
4583     * @treatAsPrivate implementation detail
4584     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4585     */
4586    @Deprecated
4587    protected boolean impl_computeIntersects(PickRay pickRay, PickResultChooser pickResult) {
4588        double origZ = pickRay.getOriginNoClone().z;
4589        double dirZ = pickRay.getDirectionNoClone().z;
4590        // Handle the case where pickRay is almost parallel to the Z-plane
4591        if (almostZero(dirZ)) {
4592            return false;
4593        }
4594        double t = -origZ / dirZ;
4595        if (t < pickRay.getNearClip() || t > pickRay.getFarClip()) {
4596            return false;
4597        }
4598        double x = pickRay.getOriginNoClone().x + (pickRay.getDirectionNoClone().x * t);
4599        double y = pickRay.getOriginNoClone().y + (pickRay.getDirectionNoClone().y * t);
4600
4601        if (contains((float) x, (float) y)) {
4602            if (pickResult != null) {
4603                pickResult.offer(this, t, PickResultChooser.computePoint(pickRay, t));
4604            }
4605            return true;
4606        }
4607        return false;
4608    }
4609
4610    /**
4611     * Computes the intersection of the pickRay with the bounds of this node.
4612     * The return value is the distance between the camera and the intersection
4613     * point, measured in pickRay direction magnitudes. If there is
4614     * no intersection, it returns NaN.
4615     *
4616     * @param pickRay The pick ray
4617     * @return Distance of the intersection point, a NaN if there
4618     *         is no intersection
4619     * @treatAsPrivate implementation detail
4620     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
4621     */
4622    @Deprecated
4623    protected final double impl_intersectsBounds(PickRay pickRay) {
4624
4625        final Vec3d dir = pickRay.getDirectionNoClone();
4626        double tmin, tmax;
4627
4628        final Vec3d origin = pickRay.getOriginNoClone();
4629        final double originX = origin.x;
4630        final double originY = origin.y;
4631        final double originZ = origin.z;
4632
4633        final TempState tempState = TempState.getInstance();
4634        BaseBounds tempBounds = tempState.bounds;
4635
4636        tempBounds = getLocalBounds(tempBounds,
4637                                    BaseTransform.IDENTITY_TRANSFORM);
4638
4639        if (dir.x == 0.0 && dir.y == 0.0) {
4640            // fast path for the usual 2D picking
4641
4642            if (dir.z == 0.0) {
4643                return Double.NaN;
4644            }
4645
4646            if (originX < tempBounds.getMinX() ||
4647                    originX > tempBounds.getMaxX() ||
4648                    originY < tempBounds.getMinY() ||
4649                    originY > tempBounds.getMaxY()) {
4650                return Double.NaN;
4651            }
4652
4653            final double invDirZ = 1.0 / dir.z;
4654            final boolean signZ = invDirZ < 0.0;
4655
4656            final double minZ = tempBounds.getMinZ();
4657            final double maxZ = tempBounds.getMaxZ();
4658            tmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ;
4659            tmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ;
4660
4661        } else if (tempBounds.getDepth() == 0.0) {
4662            // fast path for 3D picking of 2D bounds
4663
4664            if (almostZero(dir.z)) {
4665                return Double.NaN;
4666            }
4667
4668            final double t = (tempBounds.getMinZ() - originZ) / dir.z;
4669            final double x = originX + (dir.x * t);
4670            final double y = originY + (dir.y * t);
4671
4672            if (x < tempBounds.getMinX() ||
4673                    x > tempBounds.getMaxX() ||
4674                    y < tempBounds.getMinY() ||
4675                    y > tempBounds.getMaxY()) {
4676                return Double.NaN;
4677            }
4678
4679            tmin = tmax = t;
4680
4681        } else {
4682
4683            final double invDirX = dir.x == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.x);
4684            final double invDirY = dir.y == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.y);
4685            final double invDirZ = dir.z == 0.0 ? Double.POSITIVE_INFINITY : (1.0 / dir.z);
4686            final boolean signX = invDirX < 0.0;
4687            final boolean signY = invDirY < 0.0;
4688            final boolean signZ = invDirZ < 0.0;
4689            final double minX = tempBounds.getMinX();
4690            final double minY = tempBounds.getMinY();
4691            final double maxX = tempBounds.getMaxX();
4692            final double maxY = tempBounds.getMaxY();
4693
4694            tmin = Double.NEGATIVE_INFINITY;
4695            tmax = Double.POSITIVE_INFINITY;
4696            if (Double.isInfinite(invDirX)) {
4697                if (minX <= originX && maxX >= originX) {
4698                    // move on, we are inside for the whole length
4699                } else {
4700                    return Double.NaN;
4701                }
4702            } else {
4703                tmin = ((signX ? maxX : minX) - originX) * invDirX;
4704                tmax = ((signX ? minX : maxX) - originX) * invDirX;
4705            }
4706
4707            if (Double.isInfinite(invDirY)) {
4708                if (minY <= originY && maxY >= originY) {
4709                    // move on, we are inside for the whole length
4710                } else {
4711                    return Double.NaN;
4712                }
4713            } else {
4714                final double tymin = ((signY ? maxY : minY) - originY) * invDirY;
4715                final double tymax = ((signY ? minY : maxY) - originY) * invDirY;
4716
4717                if ((tmin > tymax) || (tymin > tmax)) {
4718                    return Double.NaN;
4719                }
4720                if (tymin > tmin) {
4721                    tmin = tymin;
4722                }
4723                if (tymax < tmax) {
4724                    tmax = tymax;
4725                }
4726            }
4727
4728            final double minZ = tempBounds.getMinZ();
4729            final double maxZ = tempBounds.getMaxZ();
4730            if (Double.isInfinite(invDirZ)) {
4731                if (minZ <= originZ && maxZ >= originZ) {
4732                    // move on, we are inside for the whole length
4733                } else {
4734                    return Double.NaN;
4735                }
4736            } else {
4737                final double tzmin = ((signZ ? maxZ : minZ) - originZ) * invDirZ;
4738                final double tzmax = ((signZ ? minZ : maxZ) - originZ) * invDirZ;
4739
4740                if ((tmin > tzmax) || (tzmin > tmax)) {
4741                    return Double.NaN;
4742                }
4743                if (tzmin > tmin) {
4744                    tmin = tzmin;
4745                }
4746                if (tzmax < tmax) {
4747                    tmax = tzmax;
4748                }
4749            }
4750        }
4751
4752        // For clip we use following semantics: pick the node normally
4753        // if there is an intersection with the clip node. We don't consider
4754        // clip node distance.
4755        Node clip = getClip();
4756        if (clip != null) {
4757            final double dirX = dir.x;
4758            final double dirY = dir.y;
4759            final double dirZ = dir.z;
4760
4761            clip.updateLocalToParentTransform();
4762
4763            boolean hitClip = true;
4764            try {
4765                clip.localToParentTx.inverseTransform(origin, origin);
4766                clip.localToParentTx.inverseDeltaTransform(dir, dir);
4767            } catch (NoninvertibleTransformException e) {
4768                hitClip = false;
4769            }
4770            hitClip = hitClip && clip.impl_intersects(pickRay, null);
4771            pickRay.setOrigin(originX, originY, originZ);
4772            pickRay.setDirection(dirX, dirY, dirZ);
4773
4774            if (!hitClip) {
4775                return Double.NaN;
4776            }
4777        }
4778
4779        if (Double.isInfinite(tmin) || Double.isNaN(tmin)) {
4780            // We've got a nonsense pick ray or bounds.
4781            return Double.NaN;
4782        }
4783
4784        final double minDistance = pickRay.getNearClip();
4785        final double maxDistance = pickRay.getFarClip();
4786        if (tmin < minDistance) {
4787            if (tmax >= minDistance && tmax <= maxDistance) {
4788                // we are inside bounds
4789                return 0.0;
4790            } else {
4791                return Double.NaN;
4792            }
4793        } else if (tmin > maxDistance) {
4794            return Double.NaN;
4795        }
4796
4797        return tmin;
4798    }
4799
4800
4801    // Good to find a home for commonly use util. code such as EPS.
4802    // and almostZero. This code currently defined in multiple places,
4803    // such as Affine3D and GeneralTransform3D.
4804    private static final double EPSILON_ABSOLUTE = 1.0e-5;
4805
4806    static boolean almostZero(double a) {
4807        return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE));
4808    }
4809    /***************************************************************************
4810     *                                                                         *
4811     *                             Transformations                             *
4812     *                                                                         *
4813     **************************************************************************/
4814    /**
4815     * Defines the ObservableList of {@link javafx.scene.transform.Transform} objects
4816     * to be applied to this {@code Node}. This ObservableList of transforms is applied
4817     * before {@link #translateXProperty translateX}, {@link #translateYProperty translateY}, {@link #scaleXProperty scaleX}, and
4818     * {@link #scaleYProperty scaleY}, {@link #rotateProperty rotate} transforms.
4819     *
4820     * @defaultValue empty
4821     */
4822    public final ObservableList<Transform> getTransforms() {
4823        return transformsProperty();
4824    }
4825
4826    private ObservableList<Transform> transformsProperty() {
4827        return getNodeTransformation().getTransforms();
4828    }
4829
4830    public final void setTranslateX(double value) {
4831        translateXProperty().set(value);
4832    }
4833
4834    public final double getTranslateX() {
4835        return (nodeTransformation == null)
4836                ? DEFAULT_TRANSLATE_X
4837                : nodeTransformation.getTranslateX();
4838    }
4839
4840    /**
4841     * Defines the x coordinate of the translation that is added to this {@code Node}'s
4842     * transform.
4843     * <p>
4844     * The node's final translation will be computed as {@link #layoutXProperty layoutX} + {@code translateX},
4845     * where {@code layoutX} establishes the node's stable position and {@code translateX}
4846     * optionally makes dynamic adjustments to that position.
4847     *<p>
4848     * This variable can be used to alter the location of a node without disturbing
4849     * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
4850     *
4851     * @defaultValue 0
4852     */
4853    public final DoubleProperty translateXProperty() {
4854        return getNodeTransformation().translateXProperty();
4855    }
4856
4857    public final void setTranslateY(double value) {
4858        translateYProperty().set(value);
4859    }
4860
4861    public final double getTranslateY() {
4862        return (nodeTransformation == null)
4863                ? DEFAULT_TRANSLATE_Y
4864                : nodeTransformation.getTranslateY();
4865    }
4866
4867    /**
4868     * Defines the y coordinate of the translation that is added to this {@code Node}'s
4869     * transform.
4870     * <p>
4871     * The node's final translation will be computed as {@link #layoutYProperty layoutY} + {@code translateY},
4872     * where {@code layoutY} establishes the node's stable position and {@code translateY}
4873     * optionally makes dynamic adjustments to that position.
4874     *<p>
4875     * This variable can be used to alter the location of a node without disturbing
4876     * its {@link #layoutBoundsProperty layoutBounds}, which makes it useful for animating a node's location.
4877     *
4878     * @defaultValue 0
4879     */
4880    public final DoubleProperty translateYProperty() {
4881        return getNodeTransformation().translateYProperty();
4882    }
4883
4884    public final void setTranslateZ(double value) {
4885        translateZProperty().set(value);
4886    }
4887
4888    public final double getTranslateZ() {
4889        return (nodeTransformation == null)
4890                ? DEFAULT_TRANSLATE_Z
4891                : nodeTransformation.getTranslateZ();
4892    }
4893
4894    /**
4895     * Defines the Z coordinate of the translation that is added to the
4896     * transformed coordinates of this {@code Node}.  This value will be added
4897     * to any translation defined by the {@code transforms} ObservableList and
4898     * {@code layoutZ}.
4899     *<p>
4900     * This variable can be used to alter the location of a Node without
4901     * disturbing its layout bounds, which makes it useful for animating a
4902     * node's location.
4903     * <p>
4904     * Note that this is a conditional feature. See
4905     * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
4906     * for more information.
4907     *
4908     * @defaultValue 0
4909     * @since JavaFX 1.3
4910     */
4911    public final DoubleProperty translateZProperty() {
4912        return getNodeTransformation().translateZProperty();
4913    }
4914
4915    public final void setScaleX(double value) {
4916        scaleXProperty().set(value);
4917    }
4918
4919    public final double getScaleX() {
4920        return (nodeTransformation == null) ? DEFAULT_SCALE_X
4921                                            : nodeTransformation.getScaleX();
4922    }
4923
4924    /**
4925     * Defines the factor by which coordinates are scaled about the center of the
4926     * object along the X axis of this {@code Node}. This is used to stretch or
4927     * animate the node either manually or by using an animation.
4928     * <p>
4929     * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
4930     * default, which makes it ideal for scaling the entire node after
4931     * all effects and transforms have been taken into account.
4932     * <p>
4933     * The pivot point about which the scale occurs is the center of the
4934     * untransformed {@link #layoutBoundsProperty layoutBounds}.
4935     *
4936     * @defaultValue 1.0
4937     */
4938    public final DoubleProperty scaleXProperty() {
4939        return getNodeTransformation().scaleXProperty();
4940    }
4941
4942    public final void setScaleY(double value) {
4943        scaleYProperty().set(value);
4944    }
4945
4946    public final double getScaleY() {
4947        return (nodeTransformation == null) ? DEFAULT_SCALE_Y
4948                                            : nodeTransformation.getScaleY();
4949    }
4950
4951    /**
4952     * Defines the factor by which coordinates are scaled about the center of the
4953     * object along the Y axis of this {@code Node}. This is used to stretch or
4954     * animate the node either manually or by using an animation.
4955     * <p>
4956     * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
4957     * default, which makes it ideal for scaling the entire node after
4958     * all effects and transforms have been taken into account.
4959     * <p>
4960     * The pivot point about which the scale occurs is the center of the
4961     * untransformed {@link #layoutBoundsProperty layoutBounds}.
4962     *
4963     * @defaultValue 1.0
4964     */
4965    public final DoubleProperty scaleYProperty() {
4966        return getNodeTransformation().scaleYProperty();
4967    }
4968
4969    public final void setScaleZ(double value) {
4970        scaleZProperty().set(value);
4971    }
4972
4973    public final double getScaleZ() {
4974        return (nodeTransformation == null) ? DEFAULT_SCALE_Z
4975                                            : nodeTransformation.getScaleZ();
4976    }
4977
4978    /**
4979     * Defines the factor by which coordinates are scaled about the center of the
4980     * object along the Z axis of this {@code Node}. This is used to stretch or
4981     * animate the node either manually or by using an animation.
4982     * <p>
4983     * This scale factor is not included in {@link #layoutBoundsProperty layoutBounds} by
4984     * default, which makes it ideal for scaling the entire node after
4985     * all effects and transforms have been taken into account.
4986     * <p>
4987     * The pivot point about which the scale occurs is the center of the
4988     * rectangular bounds formed by taking {@link #boundsInLocalProperty boundsInLocal} and applying
4989     * all the transforms in the {@link #getTransforms transforms} ObservableList.
4990     * <p>
4991     * Note that this is a conditional feature. See
4992     * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
4993     * for more information.
4994     *
4995     * @defaultValue 1.0
4996     * @since JavaFX 1.3
4997     */
4998    public final DoubleProperty scaleZProperty() {
4999        return getNodeTransformation().scaleZProperty();
5000    }
5001
5002    public final void setRotate(double value) {
5003        rotateProperty().set(value);
5004    }
5005
5006    public final double getRotate() {
5007        return (nodeTransformation == null) ? DEFAULT_ROTATE
5008                                            : nodeTransformation.getRotate();
5009    }
5010
5011    /**
5012     * Defines the angle of rotation about the {@code Node}'s center, measured in
5013     * degrees. This is used to rotate the {@code Node}.
5014     * <p>
5015     * This rotation factor is not included in {@link #layoutBoundsProperty layoutBounds} by
5016     * default, which makes it ideal for rotating the entire node after
5017     * all effects and transforms have been taken into account.
5018     * <p>
5019     * The pivot point about which the rotation occurs is the center of the
5020     * untransformed {@link #layoutBoundsProperty layoutBounds}.
5021     * <p>
5022     * Note that because the pivot point is computed as the center of this
5023     * {@code Node}'s layout bounds, any change to the layout bounds will cause
5024     * the pivot point to change, which can move the object. For a leaf node,
5025     * any change to the geometry will cause the layout bounds to change.
5026     * For a group node, any change to any of its children, including a
5027     * change in a child's geometry, clip, effect, position, orientation, or
5028     * scale, will cause the group's layout bounds to change. If this movement
5029     * of the pivot point is not
5030     * desired, applications should instead use the Node's {@link #getTransforms transforms}
5031     * ObservableList, and add a {@link javafx.scene.transform.Rotate} transform,
5032     * which has a user-specifiable pivot point.
5033     *
5034     * @defaultValue 0.0
5035     */
5036    public final DoubleProperty rotateProperty() {
5037        return getNodeTransformation().rotateProperty();
5038    }
5039
5040    public final void setRotationAxis(Point3D value) {
5041        rotationAxisProperty().set(value);
5042    }
5043
5044    public final Point3D getRotationAxis() {
5045        return (nodeTransformation == null)
5046                ? DEFAULT_ROTATION_AXIS
5047                : nodeTransformation.getRotationAxis();
5048    }
5049
5050    /**
5051     * Defines the axis of rotation of this {@code Node}.
5052     * <p>
5053     * Note that this is a conditional feature. See
5054     * {@link javafx.application.ConditionalFeature#SCENE3D ConditionalFeature.SCENE3D}
5055     * for more information.
5056     *
5057     * @defaultValue Rotate.Z_AXIS
5058     * @since JavaFX 1.3
5059     */
5060    public final ObjectProperty<Point3D> rotationAxisProperty() {
5061        return getNodeTransformation().rotationAxisProperty();
5062    }
5063
5064    /**
5065     * An affine transform that holds the computed local-to-parent transform.
5066     * This is the concatenation of all transforms in this node, including all
5067     * of the convenience transforms.
5068     * @since 2.2
5069     */
5070    public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
5071        return getNodeTransformation().localToParentTransformProperty();
5072    }
5073
5074    private void invalidateLocalToParentTransform() {
5075        if (nodeTransformation != null) {
5076            nodeTransformation.invalidateLocalToParentTransform();
5077        }
5078    }
5079
5080    public final Transform getLocalToParentTransform() {
5081        return localToParentTransformProperty().get();
5082    }
5083
5084    /**
5085     * An affine transform that holds the computed local-to-scene transform.
5086     * This is the concatenation of all transforms in this node's parents and
5087     * in this node, including all of the convenience transforms.
5088     *
5089     * <p>
5090     * Note that when you register a listener or a binding to this property,
5091     * it needs to listen for invalidation on all its parents to the root node.
5092     * This means that registering a listener on this
5093     * property on many nodes may negatively affect performance of
5094     * transformation changes in their common parents.
5095     * </p>
5096     * @since 2.2
5097     */
5098    public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
5099        return getNodeTransformation().localToSceneTransformProperty();
5100    }
5101
5102    private void invalidateLocalToSceneTransform() {
5103        if (nodeTransformation != null) {
5104            nodeTransformation.invalidateLocalToSceneTransform();
5105        }
5106    }
5107
5108    public final Transform getLocalToSceneTransform() {
5109        return localToSceneTransformProperty().get();
5110    }
5111
5112    private NodeTransformation nodeTransformation;
5113
5114    private NodeTransformation getNodeTransformation() {
5115        if (nodeTransformation == null) {
5116            nodeTransformation = new NodeTransformation();
5117        }
5118
5119        return nodeTransformation;
5120    }
5121
5122    /**
5123     * @treatAsPrivate implementation detail
5124     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
5125     */
5126    @Deprecated
5127    public boolean impl_hasTransforms() {
5128        return (nodeTransformation != null)
5129                && nodeTransformation.hasTransforms();
5130    }
5131
5132    // for tests only
5133    Transform getCurrentLocalToSceneTransformState() {
5134        if (nodeTransformation == null ||
5135                nodeTransformation.localToSceneTransform == null) {
5136            return null;
5137        }
5138
5139        return nodeTransformation.localToSceneTransform.transform;
5140    }
5141
5142    private static final double DEFAULT_TRANSLATE_X = 0;
5143    private static final double DEFAULT_TRANSLATE_Y = 0;
5144    private static final double DEFAULT_TRANSLATE_Z = 0;
5145    private static final double DEFAULT_SCALE_X = 1;
5146    private static final double DEFAULT_SCALE_Y = 1;
5147    private static final double DEFAULT_SCALE_Z = 1;
5148    private static final double DEFAULT_ROTATE = 0;
5149    private static final Point3D DEFAULT_ROTATION_AXIS = Rotate.Z_AXIS;
5150
5151    private final class NodeTransformation {
5152        private DoubleProperty translateX;
5153        private DoubleProperty translateY;
5154        private DoubleProperty translateZ;
5155        private DoubleProperty scaleX;
5156        private DoubleProperty scaleY;
5157        private DoubleProperty scaleZ;
5158        private DoubleProperty rotate;
5159        private ObjectProperty<Point3D> rotationAxis;
5160        private ObservableList<Transform> transforms;
5161        private LazyTransformProperty localToParentTransform;
5162        private LazyTransformProperty localToSceneTransform;
5163        private int listenerReasons = 0;
5164        private InvalidationListener localToSceneInvLstnr;
5165
5166        private InvalidationListener getLocalToSceneInvalidationListener() {
5167            if (localToSceneInvLstnr == null) {
5168                localToSceneInvLstnr = new InvalidationListener() {
5169                    @Override
5170                    public void invalidated(Observable observable) {
5171                        invalidateLocalToSceneTransform();
5172                    }
5173                };
5174            }
5175            return localToSceneInvLstnr;
5176        }
5177
5178        public void incListenerReasons() {
5179            if (listenerReasons == 0) {
5180                Node n = Node.this.getParent();
5181                if (n != null) {
5182                    n.localToSceneTransformProperty().addListener(
5183                            getLocalToSceneInvalidationListener());
5184                }
5185            }
5186            listenerReasons++;
5187        }
5188
5189        public void decListenerReasons() {
5190            listenerReasons--;
5191            if (listenerReasons == 0) {
5192                Node n = Node.this.getParent();
5193                if (n != null) {
5194                    n.localToSceneTransformProperty().removeListener(
5195                            getLocalToSceneInvalidationListener());
5196                }
5197            }
5198        }
5199
5200        public final Transform getLocalToParentTransform() {
5201            return localToParentTransformProperty().get();
5202        }
5203
5204        public final ReadOnlyObjectProperty<Transform> localToParentTransformProperty() {
5205            if (localToParentTransform == null) {
5206                localToParentTransform = new LazyTransformProperty() {
5207                    @Override
5208                    protected Transform computeTransform(Transform reuse) {
5209                        updateLocalToParentTransform();
5210                        return TransformUtils.immutableTransform(reuse,
5211                                localToParentTx.getMxx(), localToParentTx.getMxy(), localToParentTx.getMxz(), localToParentTx.getMxt(),
5212                                localToParentTx.getMyx(), localToParentTx.getMyy(), localToParentTx.getMyz(), localToParentTx.getMyt(),
5213                                localToParentTx.getMzx(), localToParentTx.getMzy(), localToParentTx.getMzz(), localToParentTx.getMzt());
5214                    }
5215
5216                    @Override
5217                    protected boolean validityKnown() {
5218                        return true;
5219                    }
5220
5221                    @Override
5222                    protected int computeValidity() {
5223                        return valid;
5224                    }
5225
5226                    @Override
5227                    public Object getBean() {
5228                        return Node.this;
5229                    }
5230
5231                    @Override
5232                    public String getName() {
5233                        return "localToParentTransform";
5234                    }
5235                };
5236            }
5237
5238            return localToParentTransform;
5239        }
5240
5241        public void invalidateLocalToParentTransform() {
5242            if (localToParentTransform != null) {
5243                localToParentTransform.invalidate();
5244            }
5245        }
5246
5247        public final Transform getLocalToSceneTransform() {
5248            return localToSceneTransformProperty().get();
5249        }
5250
5251        class LocalToSceneTransformProperty extends LazyTransformProperty {
5252            // need this to track number of listeners
5253            private List localToSceneListeners;
5254            // stamps to watch for parent changes when the listeners
5255            // are not present
5256            private long stamp, parentStamp;
5257
5258            @Override
5259            protected Transform computeTransform(Transform reuse) {
5260                stamp++;
5261                updateLocalToParentTransform();
5262
5263                Node parentNode = Node.this.getParent();
5264                if (parentNode != null) {
5265                    final LocalToSceneTransformProperty parentProperty =
5266                            (LocalToSceneTransformProperty) parentNode.localToSceneTransformProperty();
5267                    final Transform parentTransform = parentProperty.getInternalValue();
5268
5269                    parentStamp = parentProperty.stamp;
5270
5271                    return TransformUtils.immutableTransform(reuse,
5272                            parentTransform,
5273                            ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
5274                } else {
5275                    return TransformUtils.immutableTransform(reuse,
5276                            ((LazyTransformProperty) localToParentTransformProperty()).getInternalValue());
5277                }
5278            }
5279
5280            @Override
5281            public Object getBean() {
5282                return Node.this;
5283            }
5284
5285            @Override
5286            public String getName() {
5287                return "localToSceneTransform";
5288            }
5289
5290            @Override
5291            protected boolean validityKnown() {
5292                return listenerReasons > 0;
5293            }
5294
5295            @Override
5296            protected int computeValidity() {
5297                if (valid != VALIDITY_UNKNOWN) {
5298                    return valid;
5299                }
5300
5301                Node n = (Node) getBean();
5302                Node parent = n.getParent();
5303
5304                if (parent != null) {
5305                    final LocalToSceneTransformProperty parentProperty =
5306                            (LocalToSceneTransformProperty) parent.localToSceneTransformProperty();
5307
5308                    if (parentStamp != parentProperty.stamp) {
5309                        valid = INVALID;
5310                        return INVALID;
5311                    }
5312
5313                    int parentValid = parentProperty.computeValidity();
5314                    if (parentValid == INVALID) {
5315                        valid = INVALID;
5316                    }
5317                    return parentValid;
5318                }
5319
5320                // Validity unknown for root means it is valid
5321                return VALID;
5322            }
5323
5324            @Override
5325            public void addListener(InvalidationListener listener) {
5326                incListenerReasons();
5327                if (localToSceneListeners == null) {
5328                    localToSceneListeners = new LinkedList<Object>();
5329                }
5330                localToSceneListeners.add(listener);
5331                super.addListener(listener);
5332            }
5333
5334            @Override
5335            public void addListener(ChangeListener<? super Transform> listener) {
5336                incListenerReasons();
5337                if (localToSceneListeners == null) {
5338                    localToSceneListeners = new LinkedList<Object>();
5339                }
5340                localToSceneListeners.add(listener);
5341                super.addListener(listener);
5342            }
5343
5344            @Override
5345            public void removeListener(InvalidationListener listener) {
5346                if (localToSceneListeners != null &&
5347                        localToSceneListeners.remove(listener)) {
5348                    decListenerReasons();
5349                }
5350                super.removeListener(listener);
5351            }
5352
5353            @Override
5354            public void removeListener(ChangeListener<? super Transform> listener) {
5355                if (localToSceneListeners != null &&
5356                        localToSceneListeners.remove(listener)) {
5357                    decListenerReasons();
5358                }
5359                super.removeListener(listener);
5360            }
5361        }
5362
5363        public final ReadOnlyObjectProperty<Transform> localToSceneTransformProperty() {
5364            if (localToSceneTransform == null) {
5365                localToSceneTransform = new LocalToSceneTransformProperty();
5366            }
5367
5368            return localToSceneTransform;
5369        }
5370
5371        public void invalidateLocalToSceneTransform() {
5372            if (localToSceneTransform != null) {
5373                localToSceneTransform.invalidate();
5374            }
5375        }
5376
5377        public double getTranslateX() {
5378            return (translateX == null) ? DEFAULT_TRANSLATE_X
5379                                        : translateX.get();
5380        }
5381
5382        public final DoubleProperty translateXProperty() {
5383            if (translateX == null) {
5384                translateX = new StyleableDoubleProperty(DEFAULT_TRANSLATE_X) {
5385                    @Override
5386                    public void invalidated() {
5387                        impl_transformsChanged();
5388                    }
5389
5390                    @Override
5391                    public CssMetaData getCssMetaData() {
5392                        return StyleableProperties.TRANSLATE_X;
5393                    }
5394
5395                    @Override
5396                    public Object getBean() {
5397                        return Node.this;
5398                    }
5399
5400                    @Override
5401                    public String getName() {
5402                        return "translateX";
5403                    }
5404                };
5405            }
5406            return translateX;
5407        }
5408
5409        public double getTranslateY() {
5410            return (translateY == null) ? DEFAULT_TRANSLATE_Y : translateY.get();
5411        }
5412
5413        public final DoubleProperty translateYProperty() {
5414            if (translateY == null) {
5415                translateY = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Y) {
5416                    @Override
5417                    public void invalidated() {
5418                        impl_transformsChanged();
5419                    }
5420
5421                    @Override
5422                    public CssMetaData getCssMetaData() {
5423                        return StyleableProperties.TRANSLATE_Y;
5424                    }
5425
5426                    @Override
5427                    public Object getBean() {
5428                        return Node.this;
5429                    }
5430
5431                    @Override
5432                    public String getName() {
5433                        return "translateY";
5434                    }
5435                };
5436            }
5437            return translateY;
5438        }
5439
5440        public double getTranslateZ() {
5441            return (translateZ == null) ? DEFAULT_TRANSLATE_Z : translateZ.get();
5442        }
5443
5444        public final DoubleProperty translateZProperty() {
5445            if (translateZ == null) {
5446                translateZ = new StyleableDoubleProperty(DEFAULT_TRANSLATE_Z) {
5447                    @Override
5448                    public void invalidated() {
5449                        impl_transformsChanged();
5450                    }
5451
5452                    @Override
5453                    public CssMetaData getCssMetaData() {
5454                        return StyleableProperties.TRANSLATE_Z;
5455                    }
5456
5457                    @Override
5458                    public Object getBean() {
5459                        return Node.this;
5460                    }
5461
5462                    @Override
5463                    public String getName() {
5464                        return "translateZ";
5465                    }
5466                };
5467            }
5468            return translateZ;
5469        }
5470
5471        public double getScaleX() {
5472            return (scaleX == null) ? DEFAULT_SCALE_X : scaleX.get();
5473        }
5474
5475        public final DoubleProperty scaleXProperty() {
5476            if (scaleX == null) {
5477                scaleX = new StyleableDoubleProperty(DEFAULT_SCALE_X) {
5478                    @Override
5479                    public void invalidated() {
5480                        impl_transformsChanged();
5481                    }
5482
5483                    @Override
5484                    public CssMetaData getCssMetaData() {
5485                        return StyleableProperties.SCALE_X;
5486                    }
5487
5488                    @Override
5489                    public Object getBean() {
5490                        return Node.this;
5491                    }
5492
5493                    @Override
5494                    public String getName() {
5495                        return "scaleX";
5496                    }
5497                };
5498            }
5499            return scaleX;
5500        }
5501
5502        public double getScaleY() {
5503            return (scaleY == null) ? DEFAULT_SCALE_Y : scaleY.get();
5504        }
5505
5506        public final DoubleProperty scaleYProperty() {
5507            if (scaleY == null) {
5508                scaleY = new StyleableDoubleProperty(DEFAULT_SCALE_Y) {
5509                    @Override
5510                    public void invalidated() {
5511                        impl_transformsChanged();
5512                    }
5513
5514                    @Override
5515                    public CssMetaData getCssMetaData() {
5516                        return StyleableProperties.SCALE_Y;
5517                    }
5518
5519                    @Override
5520                    public Object getBean() {
5521                        return Node.this;
5522                    }
5523
5524                    @Override
5525                    public String getName() {
5526                        return "scaleY";
5527                    }
5528                };
5529            }
5530            return scaleY;
5531        }
5532
5533        public double getScaleZ() {
5534            return (scaleZ == null) ? DEFAULT_SCALE_Z : scaleZ.get();
5535        }
5536
5537        public final DoubleProperty scaleZProperty() {
5538            if (scaleZ == null) {
5539                scaleZ = new StyleableDoubleProperty(DEFAULT_SCALE_Z) {
5540                    @Override
5541                    public void invalidated() {
5542                        impl_transformsChanged();
5543                    }
5544
5545                    @Override
5546                    public CssMetaData getCssMetaData() {
5547                        return StyleableProperties.SCALE_Z;
5548                    }
5549
5550                    @Override
5551                    public Object getBean() {
5552                        return Node.this;
5553                    }
5554
5555                    @Override
5556                    public String getName() {
5557                        return "scaleZ";
5558                    }
5559                };
5560            }
5561            return scaleZ;
5562        }
5563
5564        public double getRotate() {
5565            return (rotate == null) ? DEFAULT_ROTATE : rotate.get();
5566        }
5567
5568        public final DoubleProperty rotateProperty() {
5569            if (rotate == null) {
5570                rotate = new StyleableDoubleProperty(DEFAULT_ROTATE) {
5571                    @Override
5572                    public void invalidated() {
5573                        impl_transformsChanged();
5574                    }
5575
5576                    @Override
5577                    public CssMetaData getCssMetaData() {
5578                        return StyleableProperties.ROTATE;
5579                    }
5580
5581                    @Override
5582                    public Object getBean() {
5583                        return Node.this;
5584                    }
5585
5586                    @Override
5587                    public String getName() {
5588                        return "rotate";
5589                    }
5590                };
5591            }
5592            return rotate;
5593        }
5594
5595        public Point3D getRotationAxis() {
5596            return (rotationAxis == null) ? DEFAULT_ROTATION_AXIS
5597                                          : rotationAxis.get();
5598        }
5599
5600        public final ObjectProperty<Point3D> rotationAxisProperty() {
5601            if (rotationAxis == null) {
5602                rotationAxis = new ObjectPropertyBase<Point3D>(
5603                                           DEFAULT_ROTATION_AXIS) {
5604                    @Override
5605                    protected void invalidated() {
5606                        impl_transformsChanged();
5607                    }
5608
5609                    @Override
5610                    public Object getBean() {
5611                        return Node.this;
5612                    }
5613
5614                    @Override
5615                    public String getName() {
5616                        return "rotationAxis";
5617                    }
5618                };
5619            }
5620            return rotationAxis;
5621        }
5622
5623        public ObservableList<Transform> getTransforms() {
5624            if (transforms == null) {
5625                transforms = new TrackableObservableList<Transform>() {
5626                    @Override
5627                    protected void onChanged(Change<Transform> c) {
5628                        while (c.next()) {
5629                            for (Transform t : c.getRemoved()) {
5630                                t.impl_remove(Node.this);
5631                            }
5632                            for (Transform t : c.getAddedSubList()) {
5633                                t.impl_add(Node.this);
5634                            }
5635                        }
5636
5637                        impl_transformsChanged();
5638                    }
5639                };
5640            }
5641
5642            return transforms;
5643        }
5644
5645        public boolean canSetTranslateX() {
5646            return (translateX == null) || !translateX.isBound();
5647        }
5648
5649        public boolean canSetTranslateY() {
5650            return (translateY == null) || !translateY.isBound();
5651        }
5652
5653        public boolean canSetTranslateZ() {
5654            return (translateZ == null) || !translateZ.isBound();
5655        }
5656
5657        public boolean canSetScaleX() {
5658            return (scaleX == null) || !scaleX.isBound();
5659        }
5660
5661        public boolean canSetScaleY() {
5662            return (scaleY == null) || !scaleY.isBound();
5663        }
5664
5665        public boolean canSetScaleZ() {
5666            return (scaleZ == null) || !scaleZ.isBound();
5667        }
5668
5669        public boolean canSetRotate() {
5670            return (rotate == null) || !rotate.isBound();
5671        }
5672
5673        public boolean hasTransforms() {
5674            return (transforms != null && !transforms.isEmpty());
5675        }
5676
5677        public boolean hasScaleOrRotate() {
5678            if (scaleX != null && scaleX.get() != DEFAULT_SCALE_X) {
5679                return true;
5680            }
5681            if (scaleY != null && scaleY.get() != DEFAULT_SCALE_Y) {
5682                return true;
5683            }
5684            if (scaleZ != null && scaleZ.get() != DEFAULT_SCALE_Z) {
5685                return true;
5686            }
5687            if (rotate != null && rotate.get() != DEFAULT_ROTATE) {
5688                return true;
5689            }
5690            return false;
5691        }
5692
5693    }
5694
5695    ////////////////////////////
5696    //  Private Implementation
5697    ////////////////////////////
5698
5699    /***************************************************************************
5700     *                                                                         *
5701     *                        Event Handler Properties                         *
5702     *                                                                         *
5703     **************************************************************************/
5704
5705    private EventHandlerProperties eventHandlerProperties;
5706
5707    private EventHandlerProperties getEventHandlerProperties() {
5708        if (eventHandlerProperties == null) {
5709            eventHandlerProperties =
5710                    new EventHandlerProperties(
5711                        getInternalEventDispatcher().getEventHandlerManager(),
5712                        this);
5713        }
5714
5715        return eventHandlerProperties;
5716    }
5717
5718    /***************************************************************************
5719     *                                                                         *
5720     *                       Component Orientation Properties                  *
5721     *                                                                         *
5722     **************************************************************************/
5723
5724    private ObjectProperty<NodeOrientation> nodeOrientation;
5725    private EffectiveOrientationProperty effectiveNodeOrientationProperty;
5726
5727    private static final byte EFFECTIVE_ORIENTATION_LTR = 0;
5728    private static final byte EFFECTIVE_ORIENTATION_RTL = 1;
5729    private static final byte EFFECTIVE_ORIENTATION_MASK = 1;
5730    private static final byte AUTOMATIC_ORIENTATION_LTR = 0;
5731    private static final byte AUTOMATIC_ORIENTATION_RTL = 2;
5732    private static final byte AUTOMATIC_ORIENTATION_MASK = 2;
5733
5734    private byte resolvedNodeOrientation =
5735            EFFECTIVE_ORIENTATION_LTR | AUTOMATIC_ORIENTATION_LTR;
5736
5737    public final void setNodeOrientation(NodeOrientation orientation) {
5738        nodeOrientationProperty().set(orientation);
5739    }
5740
5741    public final NodeOrientation getNodeOrientation() {
5742        return nodeOrientation == null ? NodeOrientation.INHERIT : nodeOrientation.get();
5743    }
5744    /**
5745     * Property holding NodeOrientation.
5746     * <p>
5747     * Node orientation describes the flow of visual data within a node.
5748     * In the English speaking world, visual data normally flows from
5749     * left-to-right. In an Arabic or Hebrew world, visual data flows
5750     * from right-to-left.  This is consistent with the reading order
5751     * of text in both worlds.  The default value is left-to-right.
5752     * </p>
5753     *
5754     * @return NodeOrientation
5755     */
5756    public final ObjectProperty<NodeOrientation> nodeOrientationProperty() {
5757        if (nodeOrientation == null) {
5758            nodeOrientation = new StyleableObjectProperty<NodeOrientation>(NodeOrientation.INHERIT) {
5759                @Override
5760                protected void invalidated() {
5761                    nodeResolvedOrientationInvalidated();
5762                }
5763
5764                @Override
5765                public Object getBean() {
5766                    return Node.this;
5767                }
5768
5769                @Override
5770                public String getName() {
5771                    return "nodeOrientation";
5772                }
5773
5774                @Override
5775                public CssMetaData getCssMetaData() {
5776                    //TODO - not supported
5777                    throw new UnsupportedOperationException("Not supported yet.");
5778                }
5779
5780            };
5781        }
5782        return nodeOrientation;
5783    }
5784
5785    public final NodeOrientation getEffectiveNodeOrientation() {
5786        return (getEffectiveOrientation(resolvedNodeOrientation)
5787                    == EFFECTIVE_ORIENTATION_LTR)
5788                       ? NodeOrientation.LEFT_TO_RIGHT
5789                       : NodeOrientation.RIGHT_TO_LEFT;
5790    }
5791
5792    /**
5793     * The effective orientation of a node resolves the inheritance of
5794     * node orientation, returning either left-to-right or right-to-left.
5795     */
5796    public final ReadOnlyObjectProperty<NodeOrientation>
5797            effectiveNodeOrientationProperty() {
5798        if (effectiveNodeOrientationProperty == null) {
5799            effectiveNodeOrientationProperty =
5800                    new EffectiveOrientationProperty();
5801        }
5802
5803        return effectiveNodeOrientationProperty;
5804    }
5805
5806    /**
5807     * Determines whether a node should be mirrored when node orientation
5808     * is right-to-left.
5809     * <p>
5810     * When a node is mirrored, the origin is automatically moved to the
5811     * top right corner causing the node to layout children and draw from
5812     * right to left using a mirroring transformation.  Some nodes may wish
5813     * to draw from right to left without using a transformation.  These
5814     * nodes will will answer {@code false} and implement right-to-left
5815     * orientation without using the automatic transformation.
5816     * </p>
5817     */
5818    public boolean usesMirroring() {
5819        return true;
5820    }
5821
5822    final void parentResolvedOrientationInvalidated() {
5823        if (getNodeOrientation() == NodeOrientation.INHERIT) {
5824            nodeResolvedOrientationInvalidated();
5825        } else {
5826            // mirroring changed
5827            impl_transformsChanged();
5828        }
5829    }
5830
5831    final void nodeResolvedOrientationInvalidated() {
5832        final byte oldResolvedNodeOrientation =
5833                resolvedNodeOrientation;
5834
5835        resolvedNodeOrientation =
5836                (byte) (calcEffectiveNodeOrientation()
5837                            | calcAutomaticNodeOrientation());
5838
5839        if ((effectiveNodeOrientationProperty != null)
5840                && (getEffectiveOrientation(resolvedNodeOrientation)
5841                        != getEffectiveOrientation(
5842                               oldResolvedNodeOrientation))) {
5843            effectiveNodeOrientationProperty.invalidate();
5844        }
5845
5846        // mirroring changed
5847        impl_transformsChanged();
5848
5849        if (resolvedNodeOrientation != oldResolvedNodeOrientation) {
5850            nodeResolvedOrientationChanged();
5851        }
5852    }
5853
5854    void nodeResolvedOrientationChanged() {
5855        // overriden in Parent
5856    }
5857
5858    private Node getOrientationParent() {
5859        final Node parentValue = getParent();
5860        if (parentValue != null) {
5861            return parentValue;
5862        }
5863
5864        final Node subSceneValue = getSubScene();
5865        if (subSceneValue != null) {
5866            return subSceneValue;
5867        }
5868
5869        return null;
5870    }
5871
5872    private byte calcEffectiveNodeOrientation() {
5873        final NodeOrientation nodeOrientationValue = getNodeOrientation();
5874        if (nodeOrientationValue != NodeOrientation.INHERIT) {
5875            return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT)
5876                       ? EFFECTIVE_ORIENTATION_LTR
5877                       : EFFECTIVE_ORIENTATION_RTL;
5878        }
5879
5880        final Node parentValue = getOrientationParent();
5881        if (parentValue != null) {
5882            return getEffectiveOrientation(parentValue.resolvedNodeOrientation);
5883        }
5884
5885        final Scene sceneValue = getScene();
5886        if (sceneValue != null) {
5887            return (sceneValue.getEffectiveNodeOrientation()
5888                        == NodeOrientation.LEFT_TO_RIGHT)
5889                           ? EFFECTIVE_ORIENTATION_LTR
5890                           : EFFECTIVE_ORIENTATION_RTL;
5891        }
5892
5893        return EFFECTIVE_ORIENTATION_LTR;
5894    }
5895
5896    private byte calcAutomaticNodeOrientation() {
5897        if (!usesMirroring()) {
5898            return AUTOMATIC_ORIENTATION_LTR;
5899        }
5900
5901        final NodeOrientation nodeOrientationValue = getNodeOrientation();
5902        if (nodeOrientationValue != NodeOrientation.INHERIT) {
5903            return (nodeOrientationValue == NodeOrientation.LEFT_TO_RIGHT)
5904                       ? AUTOMATIC_ORIENTATION_LTR
5905                       : AUTOMATIC_ORIENTATION_RTL;
5906        }
5907
5908        final Node parentValue = getOrientationParent();
5909        if (parentValue != null) {
5910            // automatic node orientation is inherited
5911            return getAutomaticOrientation(parentValue.resolvedNodeOrientation);
5912        }
5913
5914        final Scene sceneValue = getScene();
5915        if (sceneValue != null) {
5916            return (sceneValue.getEffectiveNodeOrientation()
5917                        == NodeOrientation.LEFT_TO_RIGHT)
5918                           ? AUTOMATIC_ORIENTATION_LTR
5919                           : AUTOMATIC_ORIENTATION_RTL;
5920        }
5921
5922        return AUTOMATIC_ORIENTATION_LTR;
5923    }
5924
5925    // Return true if the node needs to be mirrored.
5926    // A node has mirroring if the orientation differs from the parent
5927    // package private for testing
5928    final boolean hasMirroring() {
5929        final Node parentValue = getOrientationParent();
5930
5931        final byte thisOrientation =
5932                getAutomaticOrientation(resolvedNodeOrientation);
5933        final byte parentOrientation =
5934                (parentValue != null)
5935                    ? getAutomaticOrientation(
5936                          parentValue.resolvedNodeOrientation)
5937                    : AUTOMATIC_ORIENTATION_LTR;
5938
5939        return thisOrientation != parentOrientation;
5940    }
5941
5942    private static byte getEffectiveOrientation(
5943            final byte resolvedNodeOrientation) {
5944        return (byte) (resolvedNodeOrientation & EFFECTIVE_ORIENTATION_MASK);
5945    }
5946
5947    private static byte getAutomaticOrientation(
5948            final byte resolvedNodeOrientation) {
5949        return (byte) (resolvedNodeOrientation & AUTOMATIC_ORIENTATION_MASK);
5950    }
5951
5952    private final class EffectiveOrientationProperty
5953            extends ReadOnlyObjectPropertyBase<NodeOrientation> {
5954        @Override
5955        public NodeOrientation get() {
5956            return getEffectiveNodeOrientation();
5957        }
5958
5959        @Override
5960        public Object getBean() {
5961            return Node.this;
5962        }
5963
5964        @Override
5965        public String getName() {
5966            return "effectiveNodeOrientation";
5967        }
5968
5969        public void invalidate() {
5970            fireValueChangedEvent();
5971        }
5972    }
5973
5974    /***************************************************************************
5975     *                                                                         *
5976     *                       Misc Seldom Used Properties                       *
5977     *                                                                         *
5978     **************************************************************************/
5979
5980    private MiscProperties miscProperties;
5981
5982    private MiscProperties getMiscProperties() {
5983        if (miscProperties == null) {
5984            miscProperties = new MiscProperties();
5985        }
5986
5987        return miscProperties;
5988    }
5989
5990    private static final boolean DEFAULT_CACHE = false;
5991    private static final CacheHint DEFAULT_CACHE_HINT = CacheHint.DEFAULT;
5992    private static final Node DEFAULT_CLIP = null;
5993    private static final Cursor DEFAULT_CURSOR = null;
5994    private static final DepthTest DEFAULT_DEPTH_TEST = DepthTest.INHERIT;
5995    private static final boolean DEFAULT_DISABLE = false;
5996    private static final Effect DEFAULT_EFFECT = null;
5997    private static final InputMethodRequests DEFAULT_INPUT_METHOD_REQUESTS =
5998            null;
5999    private static final boolean DEFAULT_MOUSE_TRANSPARENT = false;
6000
6001    private final class MiscProperties {
6002        private LazyBoundsProperty boundsInParent;
6003        private LazyBoundsProperty boundsInLocal;
6004        private BooleanProperty cache;
6005        private ObjectProperty<CacheHint> cacheHint;
6006        private ObjectProperty<Node> clip;
6007        private ObjectProperty<Cursor> cursor;
6008        private ObjectProperty<DepthTest> depthTest;
6009        private BooleanProperty disable;
6010        private ObjectProperty<Effect> effect;
6011        private ObjectProperty<InputMethodRequests> inputMethodRequests;
6012        private BooleanProperty mouseTransparent;
6013
6014        public final Bounds getBoundsInParent() {
6015            return boundsInParentProperty().get();
6016        }
6017
6018        public final ReadOnlyObjectProperty<Bounds> boundsInParentProperty() {
6019            if (boundsInParent == null) {
6020                boundsInParent = new LazyBoundsProperty() {
6021                    /**
6022                     * Computes the bounds including the clip, effects, and all
6023                     * transforms. This function is essentially how to compute
6024                     * the boundsInParent. Optimizations are made to compute as
6025                     * little as possible and create as little trash as
6026                     * possible.
6027                     */
6028                    @Override
6029                    protected Bounds computeBounds() {
6030                        BaseBounds tempBounds = TempState.getInstance().bounds;
6031                        tempBounds = getTransformedBounds(
6032                                             tempBounds,
6033                                             BaseTransform.IDENTITY_TRANSFORM);
6034                        return new BoundingBox(tempBounds.getMinX(),
6035                                               tempBounds.getMinY(),
6036                                               tempBounds.getMinZ(),
6037                                               tempBounds.getWidth(),
6038                                               tempBounds.getHeight(),
6039                                               tempBounds.getDepth());
6040                    }
6041
6042                    @Override
6043                    public Object getBean() {
6044                        return Node.this;
6045                    }
6046
6047                    @Override
6048                    public String getName() {
6049                        return "boundsInParent";
6050                    }
6051                };
6052            }
6053
6054            return boundsInParent;
6055        }
6056
6057        public void invalidateBoundsInParent() {
6058            if (boundsInParent != null) {
6059                boundsInParent.invalidate();
6060            }
6061        }
6062
6063        public final Bounds getBoundsInLocal() {
6064            return boundsInLocalProperty().get();
6065        }
6066
6067        public final ReadOnlyObjectProperty<Bounds> boundsInLocalProperty() {
6068            if (boundsInLocal == null) {
6069                boundsInLocal = new LazyBoundsProperty() {
6070                    @Override
6071                    protected Bounds computeBounds() {
6072                        BaseBounds tempBounds = TempState.getInstance().bounds;
6073                        tempBounds = getLocalBounds(
6074                                             tempBounds,
6075                                             BaseTransform.IDENTITY_TRANSFORM);
6076                        return new BoundingBox(tempBounds.getMinX(),
6077                                               tempBounds.getMinY(),
6078                                               tempBounds.getMinZ(),
6079                                               tempBounds.getWidth(),
6080                                               tempBounds.getHeight(),
6081                                               tempBounds.getDepth());
6082                    }
6083
6084                    @Override
6085                    public Object getBean() {
6086                        return Node.this;
6087                    }
6088
6089                    @Override
6090                    public String getName() {
6091                        return "boundsInLocal";
6092                    }
6093                };
6094            }
6095
6096            return boundsInLocal;
6097        }
6098
6099        public void invalidateBoundsInLocal() {
6100            if (boundsInLocal != null) {
6101                boundsInLocal.invalidate();
6102            }
6103        }
6104
6105        public final boolean isCache() {
6106            return (cache == null) ? DEFAULT_CACHE
6107                                   : cache.get();
6108        }
6109
6110        public final BooleanProperty cacheProperty() {
6111            if (cache == null) {
6112                cache = new BooleanPropertyBase(DEFAULT_CACHE) {
6113                    @Override
6114                    protected void invalidated() {
6115                        impl_markDirty(DirtyBits.NODE_CACHE);
6116                    }
6117
6118                    @Override
6119                    public Object getBean() {
6120                        return Node.this;
6121                    }
6122
6123                    @Override
6124                    public String getName() {
6125                        return "cache";
6126                    }
6127                };
6128            }
6129            return cache;
6130        }
6131
6132        public final CacheHint getCacheHint() {
6133            return (cacheHint == null) ? DEFAULT_CACHE_HINT
6134                                       : cacheHint.get();
6135        }
6136
6137        public final ObjectProperty<CacheHint> cacheHintProperty() {
6138            if (cacheHint == null) {
6139                cacheHint = new ObjectPropertyBase<CacheHint>(DEFAULT_CACHE_HINT) {
6140                    @Override
6141                    protected void invalidated() {
6142                        impl_markDirty(DirtyBits.NODE_CACHE);
6143                    }
6144
6145                    @Override
6146                    public Object getBean() {
6147                        return Node.this;
6148                    }
6149
6150                    @Override
6151                    public String getName() {
6152                        return "cacheHint";
6153                    }
6154                };
6155            }
6156            return cacheHint;
6157        }
6158
6159        public final Node getClip() {
6160            return (clip == null) ? DEFAULT_CLIP : clip.get();
6161        }
6162
6163        public final ObjectProperty<Node> clipProperty() {
6164            if (clip == null) {
6165                clip = new ObjectPropertyBase<Node>(DEFAULT_CLIP) {
6166
6167                    //temp variables used when clip was invalid to rollback to
6168                    // last value
6169                    private Node oldClip;
6170
6171                    @Override
6172                    protected void invalidated() {
6173                        final Node newClip = get();
6174                        if ((newClip != null)
6175                                && ((newClip.isConnected()
6176                                           && newClip.clipParent != Node.this)
6177                                       || wouldCreateCycle(Node.this,
6178                                                           newClip))) {
6179                            // Assigning this node to clip is illegal.
6180                            // Roll back to the previous state and throw an
6181                            // exception.
6182                            final String cause =
6183                                    newClip.isConnected()
6184                                        && (newClip.clipParent != Node.this)
6185                                            ? "node already connected"
6186                                            : "cycle detected";
6187
6188                            if (isBound()) {
6189                                unbind();
6190                                set(oldClip);
6191                                throw new IllegalArgumentException(
6192                                        "Node's clip set to incorrect value "
6193                                            + " through binding"
6194                                            + " (" + cause + ", node  = "
6195                                                   + Node.this + ", clip = "
6196                                                   + clip + ")."
6197                                            + " Binding has been removed.");
6198                            } else {
6199                                set(oldClip);
6200                                throw new IllegalArgumentException(
6201                                        "Node's clip set to incorrect value"
6202                                            + " (" + cause + ", node  = "
6203                                                   + Node.this + ", clip = "
6204                                                   + clip + ").");
6205                            }
6206                        } else {
6207                            if (oldClip != null) {
6208                                oldClip.clipParent = null;
6209                                oldClip.setScenes(null, null);
6210                            }
6211
6212                            if (newClip != null) {
6213                                newClip.clipParent = Node.this;
6214                                newClip.setScenes(getScene(), getSubScene());
6215                            }
6216
6217                            impl_markDirty(DirtyBits.NODE_CLIP);
6218
6219                            // the local bounds have (probably) changed
6220                            localBoundsChanged();
6221
6222                            oldClip = newClip;
6223                        }
6224                    }
6225
6226                    @Override
6227                    public Object getBean() {
6228                        return Node.this;
6229                    }
6230
6231                    @Override
6232                    public String getName() {
6233                        return "clip";
6234                    }
6235                };
6236            }
6237            return clip;
6238        }
6239
6240        public final Cursor getCursor() {
6241            return (cursor == null) ? DEFAULT_CURSOR : cursor.get();
6242        }
6243
6244        public final ObjectProperty<Cursor> cursorProperty() {
6245            if (cursor == null) {
6246                cursor = new StyleableObjectProperty<Cursor>(DEFAULT_CURSOR) {
6247
6248                    @Override
6249                    public CssMetaData getCssMetaData() {
6250                        return StyleableProperties.CURSOR;
6251                    }
6252
6253                    @Override
6254                    public Object getBean() {
6255                        return Node.this;
6256                    }
6257
6258                    @Override
6259                    public String getName() {
6260                        return "cursor";
6261                    }
6262
6263                };
6264            }
6265            return cursor;
6266        }
6267
6268        public final DepthTest getDepthTest() {
6269            return (depthTest == null) ? DEFAULT_DEPTH_TEST
6270                                       : depthTest.get();
6271        }
6272
6273        public final ObjectProperty<DepthTest> depthTestProperty() {
6274            if (depthTest == null) {
6275                depthTest = new ObjectPropertyBase<DepthTest>(DEFAULT_DEPTH_TEST) {
6276                    @Override protected void invalidated() {
6277                        computeDerivedDepthTest();
6278                    }
6279
6280                    @Override
6281                    public Object getBean() {
6282                        return Node.this;
6283                    }
6284
6285                    @Override
6286                    public String getName() {
6287                        return "depthTest";
6288                    }
6289                };
6290            }
6291            return depthTest;
6292        }
6293
6294        public final boolean isDisable() {
6295            return (disable == null) ? DEFAULT_DISABLE : disable.get();
6296        }
6297
6298        public final BooleanProperty disableProperty() {
6299            if (disable == null) {
6300                disable = new BooleanPropertyBase(DEFAULT_DISABLE) {
6301                    @Override
6302                    protected void invalidated() {
6303                        updateDisabled();
6304                    }
6305
6306                    @Override
6307                    public Object getBean() {
6308                        return Node.this;
6309                    }
6310
6311                    @Override
6312                    public String getName() {
6313                        return "disable";
6314                    }
6315                };
6316            }
6317            return disable;
6318        }
6319
6320        public final Effect getEffect() {
6321            return (effect == null) ? DEFAULT_EFFECT : effect.get();
6322        }
6323
6324        public final ObjectProperty<Effect> effectProperty() {
6325            if (effect == null) {
6326                effect = new StyleableObjectProperty<Effect>(DEFAULT_EFFECT) {
6327                    private Effect oldEffect = null;
6328                    private int oldBits;
6329
6330                    private final AbstractNotifyListener effectChangeListener =
6331                            new AbstractNotifyListener() {
6332
6333                        @Override
6334                        public void invalidated(Observable valueModel) {
6335                            int newBits = ((IntegerProperty) valueModel).get();
6336                            int changedBits = newBits ^ oldBits;
6337                            oldBits = newBits;
6338                            if (EffectDirtyBits.isSet(
6339                                    changedBits,
6340                                    EffectDirtyBits.EFFECT_DIRTY)
6341                                && EffectDirtyBits.isSet(
6342                                       newBits,
6343                                       EffectDirtyBits.EFFECT_DIRTY)) {
6344                                impl_markDirty(DirtyBits.EFFECT_EFFECT);
6345                            }
6346                            if (EffectDirtyBits.isSet(
6347                                    changedBits,
6348                                    EffectDirtyBits.BOUNDS_CHANGED)) {
6349                                localBoundsChanged();
6350                            }
6351                        }
6352                    };
6353
6354                    @Override
6355                    protected void invalidated() {
6356                        Effect _effect = get();
6357                        if (oldEffect != null) {
6358                            oldEffect.impl_effectDirtyProperty().removeListener(
6359                                    effectChangeListener.getWeakListener());
6360                        }
6361                        oldEffect = _effect;
6362                        if (_effect != null) {
6363                            _effect.impl_effectDirtyProperty()
6364                                   .addListener(
6365                                       effectChangeListener.getWeakListener());
6366                            if (_effect.impl_isEffectDirty()) {
6367                                impl_markDirty(DirtyBits.EFFECT_EFFECT);
6368                            }
6369                            oldBits = _effect.impl_effectDirtyProperty().get();
6370                        }
6371
6372                        impl_markDirty(DirtyBits.NODE_EFFECT);
6373                        // bounds may have changed regardeless whether
6374                        // the dirty flag on efffect is set
6375                        localBoundsChanged();
6376                    }
6377
6378                    @Override
6379                    public CssMetaData getCssMetaData() {
6380                        return StyleableProperties.EFFECT;
6381                    }
6382
6383                    @Override
6384                    public Object getBean() {
6385                        return Node.this;
6386                    }
6387
6388                    @Override
6389                    public String getName() {
6390                        return "effect";
6391                    }
6392                };
6393            }
6394            return effect;
6395        }
6396
6397        public final InputMethodRequests getInputMethodRequests() {
6398            return (inputMethodRequests == null) ? DEFAULT_INPUT_METHOD_REQUESTS
6399                                                 : inputMethodRequests.get();
6400        }
6401
6402        public ObjectProperty<InputMethodRequests>
6403                inputMethodRequestsProperty() {
6404            if (inputMethodRequests == null) {
6405                inputMethodRequests =
6406                        new SimpleObjectProperty<InputMethodRequests>(
6407                                Node.this,
6408                                "inputMethodRequests",
6409                                DEFAULT_INPUT_METHOD_REQUESTS);
6410            }
6411            return inputMethodRequests;
6412        }
6413
6414        public final boolean isMouseTransparent() {
6415            return (mouseTransparent == null) ? DEFAULT_MOUSE_TRANSPARENT
6416                                              : mouseTransparent.get();
6417        }
6418
6419        public final BooleanProperty mouseTransparentProperty() {
6420            if (mouseTransparent == null) {
6421                mouseTransparent =
6422                        new SimpleBooleanProperty(
6423                                Node.this,
6424                                "mouseTransparent",
6425                                DEFAULT_MOUSE_TRANSPARENT);
6426            }
6427            return mouseTransparent;
6428        }
6429
6430        public boolean canSetCursor() {
6431            return (cursor == null) || !cursor.isBound();
6432        }
6433
6434        public boolean canSetEffect() {
6435            return (effect == null) || !effect.isBound();
6436        }
6437    }
6438
6439    /* *************************************************************************
6440     *                                                                         *
6441     *                             Mouse Handling                              *
6442     *                                                                         *
6443     **************************************************************************/
6444
6445    public final void setMouseTransparent(boolean value) {
6446        mouseTransparentProperty().set(value);
6447    }
6448
6449    public final boolean isMouseTransparent() {
6450        return (miscProperties == null) ? DEFAULT_MOUSE_TRANSPARENT
6451                                        : miscProperties.isMouseTransparent();
6452    }
6453
6454    /**
6455     * If {@code true}, this node (together with all its children) is completely
6456     * transparent to mouse events. When choosing target for mouse event, nodes
6457     * with {@code mouseTransparent} set to {@code true} and their subtrees
6458     * won't be taken into account.
6459     */
6460    public final BooleanProperty mouseTransparentProperty() {
6461        return getMiscProperties().mouseTransparentProperty();
6462    }
6463
6464    /**
6465     * Whether or not this {@code Node} is being hovered over. Typically this is
6466     * due to the mouse being over the node, though it could be due to a pen
6467     * hovering on a graphics tablet or other form of input.
6468     *
6469     * <p>Note that current implementation of hover relies on mouse enter and
6470     * exit events to determine whether this Node is in the hover state; this
6471     * means that this feature is currently supported only on systems that
6472     * have a mouse. Future implementations may provide alternative means of
6473     * supporting hover.
6474     *
6475     * @defaultValue false
6476     */
6477    private ReadOnlyBooleanWrapper hover;
6478
6479    protected final void setHover(boolean value) {
6480        hoverPropertyImpl().set(value);
6481    }
6482
6483    public final boolean isHover() {
6484        return hover == null ? false : hover.get();
6485    }
6486
6487    public final ReadOnlyBooleanProperty hoverProperty() {
6488        return hoverPropertyImpl().getReadOnlyProperty();
6489    }
6490
6491    private ReadOnlyBooleanWrapper hoverPropertyImpl() {
6492        if (hover == null) {
6493            hover = new ReadOnlyBooleanWrapper() {
6494
6495                @Override
6496                protected void invalidated() {
6497                    PlatformLogger logger = Logging.getInputLogger();
6498                    if (logger.isLoggable(PlatformLogger.FINER)) {
6499                        logger.finer(this + " hover=" + get());
6500                    }
6501                    pseudoClassStateChanged(HOVER_PSEUDOCLASS_STATE, get());
6502                }
6503
6504                @Override
6505                public Object getBean() {
6506                    return Node.this;
6507                }
6508
6509                @Override
6510                public String getName() {
6511                    return "hover";
6512                }
6513            };
6514        }
6515        return hover;
6516    }
6517
6518    /**
6519     * Whether or not the {@code Node} is pressed. Typically this is true when
6520     * the primary mouse button is down, though subclasses may define other
6521     * mouse button state or key state to cause the node to be "pressed".
6522     *
6523     * @defaultValue false
6524     */
6525    private ReadOnlyBooleanWrapper pressed;
6526
6527    protected final void setPressed(boolean value) {
6528        pressedPropertyImpl().set(value);
6529    }
6530
6531    public final boolean isPressed() {
6532        return pressed == null ? false : pressed.get();
6533    }
6534
6535    public final ReadOnlyBooleanProperty pressedProperty() {
6536        return pressedPropertyImpl().getReadOnlyProperty();
6537    }
6538
6539    private ReadOnlyBooleanWrapper pressedPropertyImpl() {
6540        if (pressed == null) {
6541            pressed = new ReadOnlyBooleanWrapper() {
6542
6543                @Override
6544                protected void invalidated() {
6545                    PlatformLogger logger = Logging.getInputLogger();
6546                    if (logger.isLoggable(PlatformLogger.FINER)) {
6547                        logger.finer(this + " pressed=" + get());
6548                    }
6549                    pseudoClassStateChanged(PRESSED_PSEUDOCLASS_STATE, get());
6550                }
6551
6552                @Override
6553                public Object getBean() {
6554                    return Node.this;
6555                }
6556
6557                @Override
6558                public String getName() {
6559                    return "pressed";
6560                }
6561            };
6562        }
6563        return pressed;
6564    }
6565
6566    public final void setOnContextMenuRequested(
6567            EventHandler<? super ContextMenuEvent> value) {
6568        onContextMenuRequestedProperty().set(value);
6569    }
6570
6571    public final EventHandler<? super ContextMenuEvent> getOnContextMenuRequested() {
6572        return (eventHandlerProperties == null)
6573                ? null : eventHandlerProperties.onContextMenuRequested();
6574    }
6575
6576    /**
6577     * Defines a function to be called when a context menu
6578     * has been requested on this {@code Node}.
6579     */
6580    public final ObjectProperty<EventHandler<? super ContextMenuEvent>>
6581            onContextMenuRequestedProperty() {
6582        return getEventHandlerProperties().onContextMenuRequestedProperty();
6583    }
6584
6585    public final void setOnMouseClicked(
6586            EventHandler<? super MouseEvent> value) {
6587        onMouseClickedProperty().set(value);
6588    }
6589
6590    public final EventHandler<? super MouseEvent> getOnMouseClicked() {
6591        return (eventHandlerProperties == null)
6592                ? null : eventHandlerProperties.getOnMouseClicked();
6593    }
6594
6595    /**
6596     * Defines a function to be called when a mouse button has been clicked
6597     * (pressed and released) on this {@code Node}.
6598     */
6599    public final ObjectProperty<EventHandler<? super MouseEvent>>
6600            onMouseClickedProperty() {
6601        return getEventHandlerProperties().onMouseClickedProperty();
6602    }
6603
6604    public final void setOnMouseDragged(
6605            EventHandler<? super MouseEvent> value) {
6606        onMouseDraggedProperty().set(value);
6607    }
6608
6609    public final EventHandler<? super MouseEvent> getOnMouseDragged() {
6610        return (eventHandlerProperties == null)
6611                ? null : eventHandlerProperties.getOnMouseDragged();
6612    }
6613
6614    /**
6615     * Defines a function to be called when a mouse button is pressed
6616     * on this {@code Node} and then dragged.
6617     */
6618    public final ObjectProperty<EventHandler<? super MouseEvent>>
6619            onMouseDraggedProperty() {
6620        return getEventHandlerProperties().onMouseDraggedProperty();
6621    }
6622
6623    public final void setOnMouseEntered(
6624            EventHandler<? super MouseEvent> value) {
6625        onMouseEnteredProperty().set(value);
6626    }
6627
6628    public final EventHandler<? super MouseEvent> getOnMouseEntered() {
6629        return (eventHandlerProperties == null)
6630                ? null : eventHandlerProperties.getOnMouseEntered();
6631    }
6632
6633    /**
6634     * Defines a function to be called when the mouse enters this {@code Node}.
6635     */
6636    public final ObjectProperty<EventHandler<? super MouseEvent>>
6637            onMouseEnteredProperty() {
6638        return getEventHandlerProperties().onMouseEnteredProperty();
6639    }
6640
6641    public final void setOnMouseExited(
6642            EventHandler<? super MouseEvent> value) {
6643        onMouseExitedProperty().set(value);
6644    }
6645
6646    public final EventHandler<? super MouseEvent> getOnMouseExited() {
6647        return (eventHandlerProperties == null)
6648                ? null : eventHandlerProperties.getOnMouseExited();
6649    }
6650
6651    /**
6652     * Defines a function to be called when the mouse exits this {@code Node}.
6653     */
6654    public final ObjectProperty<EventHandler<? super MouseEvent>>
6655            onMouseExitedProperty() {
6656        return getEventHandlerProperties().onMouseExitedProperty();
6657    }
6658
6659    public final void setOnMouseMoved(
6660            EventHandler<? super MouseEvent> value) {
6661        onMouseMovedProperty().set(value);
6662    }
6663
6664    public final EventHandler<? super MouseEvent> getOnMouseMoved() {
6665        return (eventHandlerProperties == null)
6666                ? null : eventHandlerProperties.getOnMouseMoved();
6667    }
6668
6669    /**
6670     * Defines a function to be called when mouse cursor moves within
6671     * this {@code Node} but no buttons have been pushed.
6672     */
6673    public final ObjectProperty<EventHandler<? super MouseEvent>>
6674            onMouseMovedProperty() {
6675        return getEventHandlerProperties().onMouseMovedProperty();
6676    }
6677
6678    public final void setOnMousePressed(
6679            EventHandler<? super MouseEvent> value) {
6680        onMousePressedProperty().set(value);
6681    }
6682
6683    public final EventHandler<? super MouseEvent> getOnMousePressed() {
6684        return (eventHandlerProperties == null)
6685                ? null : eventHandlerProperties.getOnMousePressed();
6686    }
6687
6688    /**
6689     * Defines a function to be called when a mouse button
6690     * has been pressed on this {@code Node}.
6691     */
6692    public final ObjectProperty<EventHandler<? super MouseEvent>>
6693            onMousePressedProperty() {
6694        return getEventHandlerProperties().onMousePressedProperty();
6695    }
6696
6697    public final void setOnMouseReleased(
6698            EventHandler<? super MouseEvent> value) {
6699        onMouseReleasedProperty().set(value);
6700    }
6701
6702    public final EventHandler<? super MouseEvent> getOnMouseReleased() {
6703        return (eventHandlerProperties == null)
6704                ? null : eventHandlerProperties.getOnMouseReleased();
6705    }
6706
6707    /**
6708     * Defines a function to be called when a mouse button
6709     * has been released on this {@code Node}.
6710     */
6711    public final ObjectProperty<EventHandler<? super MouseEvent>>
6712            onMouseReleasedProperty() {
6713        return getEventHandlerProperties().onMouseReleasedProperty();
6714    }
6715
6716    public final void setOnDragDetected(
6717            EventHandler<? super MouseEvent> value) {
6718        onDragDetectedProperty().set(value);
6719    }
6720
6721    public final EventHandler<? super MouseEvent> getOnDragDetected() {
6722        return (eventHandlerProperties == null)
6723                ? null : eventHandlerProperties.getOnDragDetected();
6724    }
6725
6726    /**
6727     * Defines a function to be called when drag gesture has been
6728     * detected. This is the right place to start drag and drop operation.
6729     */
6730    public final ObjectProperty<EventHandler<? super MouseEvent>>
6731            onDragDetectedProperty() {
6732        return getEventHandlerProperties().onDragDetectedProperty();
6733    }
6734
6735    public final void setOnMouseDragOver(
6736            EventHandler<? super MouseDragEvent> value) {
6737        onMouseDragOverProperty().set(value);
6738    }
6739
6740    public final EventHandler<? super MouseDragEvent> getOnMouseDragOver() {
6741        return (eventHandlerProperties == null)
6742                ? null : eventHandlerProperties.getOnMouseDragOver();
6743    }
6744
6745    /**
6746     * Defines a function to be called when a full press-drag-release gesture
6747     * progresses within this {@code Node}.
6748     */
6749    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
6750            onMouseDragOverProperty() {
6751        return getEventHandlerProperties().onMouseDragOverProperty();
6752    }
6753
6754    public final void setOnMouseDragReleased(
6755            EventHandler<? super MouseDragEvent> value) {
6756        onMouseDragReleasedProperty().set(value);
6757    }
6758
6759    public final EventHandler<? super MouseDragEvent> getOnMouseDragReleased() {
6760        return (eventHandlerProperties == null)
6761                ? null : eventHandlerProperties.getOnMouseDragReleased();
6762    }
6763
6764    /**
6765     * Defines a function to be called when a full press-drag-release gesture
6766     * ends (by releasing mouse button) within this {@code Node}.
6767     */
6768    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
6769            onMouseDragReleasedProperty() {
6770        return getEventHandlerProperties().onMouseDragReleasedProperty();
6771    }
6772
6773    public final void setOnMouseDragEntered(
6774            EventHandler<? super MouseDragEvent> value) {
6775        onMouseDragEnteredProperty().set(value);
6776    }
6777
6778    public final EventHandler<? super MouseDragEvent> getOnMouseDragEntered() {
6779        return (eventHandlerProperties == null)
6780                ? null : eventHandlerProperties.getOnMouseDragEntered();
6781    }
6782
6783    /**
6784     * Defines a function to be called when a full press-drag-release gesture
6785     * enters this {@code Node}.
6786     */
6787    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
6788            onMouseDragEnteredProperty() {
6789        return getEventHandlerProperties().onMouseDragEnteredProperty();
6790    }
6791
6792    public final void setOnMouseDragExited(
6793            EventHandler<? super MouseDragEvent> value) {
6794        onMouseDragExitedProperty().set(value);
6795    }
6796
6797    public final EventHandler<? super MouseDragEvent> getOnMouseDragExited() {
6798        return (eventHandlerProperties == null)
6799                ? null : eventHandlerProperties.getOnMouseDragExited();
6800    }
6801
6802    /**
6803     * Defines a function to be called when a full press-drag-release gesture
6804     * leaves this {@code Node}.
6805     */
6806    public final ObjectProperty<EventHandler<? super MouseDragEvent>>
6807            onMouseDragExitedProperty() {
6808        return getEventHandlerProperties().onMouseDragExitedProperty();
6809    }
6810
6811
6812    /* *************************************************************************
6813     *                                                                         *
6814     *                           Gestures Handling                             *
6815     *                                                                         *
6816     **************************************************************************/
6817
6818    public final void setOnScrollStarted(
6819            EventHandler<? super ScrollEvent> value) {
6820        onScrollStartedProperty().set(value);
6821    }
6822
6823    public final EventHandler<? super ScrollEvent> getOnScrollStarted() {
6824        return (eventHandlerProperties == null)
6825                ? null : eventHandlerProperties.getOnScrollStarted();
6826    }
6827
6828    /**
6829     * Defines a function to be called when a scrolling gesture is detected.
6830     * @since 2.2
6831     */
6832    public final ObjectProperty<EventHandler<? super ScrollEvent>>
6833            onScrollStartedProperty() {
6834        return getEventHandlerProperties().onScrollStartedProperty();
6835    }
6836
6837    public final void setOnScroll(
6838            EventHandler<? super ScrollEvent> value) {
6839        onScrollProperty().set(value);
6840    }
6841
6842    public final EventHandler<? super ScrollEvent> getOnScroll() {
6843        return (eventHandlerProperties == null)
6844                ? null : eventHandlerProperties.getOnScroll();
6845    }
6846
6847    /**
6848     * Defines a function to be called when user performs a scrolling action.
6849     */
6850    public final ObjectProperty<EventHandler<? super ScrollEvent>>
6851            onScrollProperty() {
6852        return getEventHandlerProperties().onScrollProperty();
6853    }
6854
6855    public final void setOnScrollFinished(
6856            EventHandler<? super ScrollEvent> value) {
6857        onScrollFinishedProperty().set(value);
6858    }
6859
6860    public final EventHandler<? super ScrollEvent> getOnScrollFinished() {
6861        return (eventHandlerProperties == null)
6862                ? null : eventHandlerProperties.getOnScrollFinished();
6863    }
6864
6865    /**
6866     * Defines a function to be called when a scrolling gesture ends.
6867     * @since 2.2
6868     */
6869    public final ObjectProperty<EventHandler<? super ScrollEvent>>
6870            onScrollFinishedProperty() {
6871        return getEventHandlerProperties().onScrollFinishedProperty();
6872    }
6873
6874    public final void setOnRotationStarted(
6875            EventHandler<? super RotateEvent> value) {
6876        onRotationStartedProperty().set(value);
6877    }
6878
6879    public final EventHandler<? super RotateEvent> getOnRotationStarted() {
6880        return (eventHandlerProperties == null)
6881                ? null : eventHandlerProperties.getOnRotationStarted();
6882    }
6883
6884    /**
6885     * Defines a function to be called when a rotation gesture is detected.
6886     * @since 2.2
6887     */
6888    public final ObjectProperty<EventHandler<? super RotateEvent>>
6889            onRotationStartedProperty() {
6890        return getEventHandlerProperties().onRotationStartedProperty();
6891    }
6892
6893    public final void setOnRotate(
6894            EventHandler<? super RotateEvent> value) {
6895        onRotateProperty().set(value);
6896    }
6897
6898    public final EventHandler<? super RotateEvent> getOnRotate() {
6899        return (eventHandlerProperties == null)
6900                ? null : eventHandlerProperties.getOnRotate();
6901    }
6902
6903    /**
6904     * Defines a function to be called when user performs a rotation action.
6905     * @since 2.2
6906     */
6907    public final ObjectProperty<EventHandler<? super RotateEvent>>
6908            onRotateProperty() {
6909        return getEventHandlerProperties().onRotateProperty();
6910    }
6911
6912    public final void setOnRotationFinished(
6913            EventHandler<? super RotateEvent> value) {
6914        onRotationFinishedProperty().set(value);
6915    }
6916
6917    public final EventHandler<? super RotateEvent> getOnRotationFinished() {
6918        return (eventHandlerProperties == null)
6919                ? null : eventHandlerProperties.getOnRotationFinished();
6920    }
6921
6922    /**
6923     * Defines a function to be called when a rotation gesture ends.
6924     * @since 2.2
6925     */
6926    public final ObjectProperty<EventHandler<? super RotateEvent>>
6927            onRotationFinishedProperty() {
6928        return getEventHandlerProperties().onRotationFinishedProperty();
6929    }
6930
6931    public final void setOnZoomStarted(
6932            EventHandler<? super ZoomEvent> value) {
6933        onZoomStartedProperty().set(value);
6934    }
6935
6936    public final EventHandler<? super ZoomEvent> getOnZoomStarted() {
6937        return (eventHandlerProperties == null)
6938                ? null : eventHandlerProperties.getOnZoomStarted();
6939    }
6940
6941    /**
6942     * Defines a function to be called when a zooming gesture is detected.
6943     * @since 2.2
6944     */
6945    public final ObjectProperty<EventHandler<? super ZoomEvent>>
6946            onZoomStartedProperty() {
6947        return getEventHandlerProperties().onZoomStartedProperty();
6948    }
6949
6950    public final void setOnZoom(
6951            EventHandler<? super ZoomEvent> value) {
6952        onZoomProperty().set(value);
6953    }
6954
6955    public final EventHandler<? super ZoomEvent> getOnZoom() {
6956        return (eventHandlerProperties == null)
6957                ? null : eventHandlerProperties.getOnZoom();
6958    }
6959
6960    /**
6961     * Defines a function to be called when user performs a zooming action.
6962     * @since 2.2
6963     */
6964    public final ObjectProperty<EventHandler<? super ZoomEvent>>
6965            onZoomProperty() {
6966        return getEventHandlerProperties().onZoomProperty();
6967    }
6968
6969    public final void setOnZoomFinished(
6970            EventHandler<? super ZoomEvent> value) {
6971        onZoomFinishedProperty().set(value);
6972    }
6973
6974    public final EventHandler<? super ZoomEvent> getOnZoomFinished() {
6975        return (eventHandlerProperties == null)
6976                ? null : eventHandlerProperties.getOnZoomFinished();
6977    }
6978
6979    /**
6980     * Defines a function to be called when a zooming gesture ends.
6981     * @since 2.2
6982     */
6983    public final ObjectProperty<EventHandler<? super ZoomEvent>>
6984            onZoomFinishedProperty() {
6985        return getEventHandlerProperties().onZoomFinishedProperty();
6986    }
6987
6988    public final void setOnSwipeUp(
6989            EventHandler<? super SwipeEvent> value) {
6990        onSwipeUpProperty().set(value);
6991    }
6992
6993    public final EventHandler<? super SwipeEvent> getOnSwipeUp() {
6994        return (eventHandlerProperties == null)
6995                ? null : eventHandlerProperties.getOnSwipeUp();
6996    }
6997
6998    /**
6999     * Defines a function to be called when an upward swipe gesture
7000     * centered over this node happens.
7001     * @since 2.2
7002     */
7003    public final ObjectProperty<EventHandler<? super SwipeEvent>>
7004            onSwipeUpProperty() {
7005        return getEventHandlerProperties().onSwipeUpProperty();
7006    }
7007
7008    public final void setOnSwipeDown(
7009            EventHandler<? super SwipeEvent> value) {
7010        onSwipeDownProperty().set(value);
7011    }
7012
7013    public final EventHandler<? super SwipeEvent> getOnSwipeDown() {
7014        return (eventHandlerProperties == null)
7015                ? null : eventHandlerProperties.getOnSwipeDown();
7016    }
7017
7018    /**
7019     * Defines a function to be called when a downward swipe gesture
7020     * centered over this node happens.
7021     * @since 2.2
7022     */
7023    public final ObjectProperty<EventHandler<? super SwipeEvent>>
7024            onSwipeDownProperty() {
7025        return getEventHandlerProperties().onSwipeDownProperty();
7026    }
7027
7028    public final void setOnSwipeLeft(
7029            EventHandler<? super SwipeEvent> value) {
7030        onSwipeLeftProperty().set(value);
7031    }
7032
7033    public final EventHandler<? super SwipeEvent> getOnSwipeLeft() {
7034        return (eventHandlerProperties == null)
7035                ? null : eventHandlerProperties.getOnSwipeLeft();
7036    }
7037
7038    /**
7039     * Defines a function to be called when a leftward swipe gesture
7040     * centered over this node happens.
7041     * @since 2.2
7042     */
7043    public final ObjectProperty<EventHandler<? super SwipeEvent>>
7044            onSwipeLeftProperty() {
7045        return getEventHandlerProperties().onSwipeLeftProperty();
7046    }
7047
7048    public final void setOnSwipeRight(
7049            EventHandler<? super SwipeEvent> value) {
7050        onSwipeRightProperty().set(value);
7051    }
7052
7053    public final EventHandler<? super SwipeEvent> getOnSwipeRight() {
7054        return (eventHandlerProperties == null)
7055                ? null : eventHandlerProperties.getOnSwipeRight();
7056    }
7057
7058    /**
7059     * Defines a function to be called when an rightward swipe gesture
7060     * centered over this node happens.
7061     * @since 2.2
7062     */
7063    public final ObjectProperty<EventHandler<? super SwipeEvent>>
7064            onSwipeRightProperty() {
7065        return getEventHandlerProperties().onSwipeRightProperty();
7066    }
7067
7068
7069    /* *************************************************************************
7070     *                                                                         *
7071     *                             Touch Handling                              *
7072     *                                                                         *
7073     **************************************************************************/
7074
7075    public final void setOnTouchPressed(
7076            EventHandler<? super TouchEvent> value) {
7077        onTouchPressedProperty().set(value);
7078    }
7079
7080    public final EventHandler<? super TouchEvent> getOnTouchPressed() {
7081        return (eventHandlerProperties == null)
7082                ? null : eventHandlerProperties.getOnTouchPressed();
7083    }
7084
7085    /**
7086     * Defines a function to be called when a new touch point is pressed.
7087     * @since 2.2
7088     */
7089    public final ObjectProperty<EventHandler<? super TouchEvent>>
7090            onTouchPressedProperty() {
7091        return getEventHandlerProperties().onTouchPressedProperty();
7092    }
7093
7094    public final void setOnTouchMoved(
7095            EventHandler<? super TouchEvent> value) {
7096        onTouchMovedProperty().set(value);
7097    }
7098
7099    public final EventHandler<? super TouchEvent> getOnTouchMoved() {
7100        return (eventHandlerProperties == null)
7101                ? null : eventHandlerProperties.getOnTouchMoved();
7102    }
7103
7104    /**
7105     * Defines a function to be called when a touch point is moved.
7106     * @since 2.2
7107     */
7108    public final ObjectProperty<EventHandler<? super TouchEvent>>
7109            onTouchMovedProperty() {
7110        return getEventHandlerProperties().onTouchMovedProperty();
7111    }
7112
7113    public final void setOnTouchReleased(
7114            EventHandler<? super TouchEvent> value) {
7115        onTouchReleasedProperty().set(value);
7116    }
7117
7118    public final EventHandler<? super TouchEvent> getOnTouchReleased() {
7119        return (eventHandlerProperties == null)
7120                ? null : eventHandlerProperties.getOnTouchReleased();
7121    }
7122
7123    /**
7124     * Defines a function to be called when a touch point is released.
7125     * @since 2.2
7126     */
7127    public final ObjectProperty<EventHandler<? super TouchEvent>>
7128            onTouchReleasedProperty() {
7129        return getEventHandlerProperties().onTouchReleasedProperty();
7130    }
7131
7132    public final void setOnTouchStationary(
7133            EventHandler<? super TouchEvent> value) {
7134        onTouchStationaryProperty().set(value);
7135    }
7136
7137    public final EventHandler<? super TouchEvent> getOnTouchStationary() {
7138        return (eventHandlerProperties == null)
7139                ? null : eventHandlerProperties.getOnTouchStationary();
7140    }
7141
7142    /**
7143     * Defines a function to be called when a touch point stays pressed and
7144     * still.
7145     * @since 2.2
7146     */
7147    public final ObjectProperty<EventHandler<? super TouchEvent>>
7148            onTouchStationaryProperty() {
7149        return getEventHandlerProperties().onTouchStationaryProperty();
7150    }
7151
7152    /* *************************************************************************
7153     *                                                                         *
7154     *                           Keyboard Handling                             *
7155     *                                                                         *
7156     **************************************************************************/
7157
7158    public final void setOnKeyPressed(
7159            EventHandler<? super KeyEvent> value) {
7160        onKeyPressedProperty().set(value);
7161    }
7162
7163    public final EventHandler<? super KeyEvent> getOnKeyPressed() {
7164        return (eventHandlerProperties == null)
7165                ? null : eventHandlerProperties.getOnKeyPressed();
7166    }
7167
7168    /**
7169     * Defines a function to be called when this {@code Node} or its child
7170     * {@code Node} has input focus and a key has been pressed. The function
7171     * is called only if the event hasn't been already consumed during its
7172     * capturing or bubbling phase.
7173     */
7174    public final ObjectProperty<EventHandler<? super KeyEvent>>
7175            onKeyPressedProperty() {
7176        return getEventHandlerProperties().onKeyPressedProperty();
7177    }
7178
7179    public final void setOnKeyReleased(
7180            EventHandler<? super KeyEvent> value) {
7181        onKeyReleasedProperty().set(value);
7182    }
7183
7184    public final EventHandler<? super KeyEvent> getOnKeyReleased() {
7185        return (eventHandlerProperties == null)
7186                ? null : eventHandlerProperties.getOnKeyReleased();
7187    }
7188
7189    /**
7190     * Defines a function to be called when this {@code Node} or its child
7191     * {@code Node} has input focus and a key has been released. The function
7192     * is called only if the event hasn't been already consumed during its
7193     * capturing or bubbling phase.
7194     */
7195    public final ObjectProperty<EventHandler<? super KeyEvent>>
7196            onKeyReleasedProperty() {
7197        return getEventHandlerProperties().onKeyReleasedProperty();
7198    }
7199
7200    public final void setOnKeyTyped(
7201            EventHandler<? super KeyEvent> value) {
7202        onKeyTypedProperty().set(value);
7203    }
7204
7205    public final EventHandler<? super KeyEvent> getOnKeyTyped() {
7206        return (eventHandlerProperties == null)
7207                ? null : eventHandlerProperties.getOnKeyTyped();
7208    }
7209
7210    /**
7211     * Defines a function to be called when this {@code Node} or its child
7212     * {@code Node} has input focus and a key has been typed. The function
7213     * is called only if the event hasn't been already consumed during its
7214     * capturing or bubbling phase.
7215     */
7216    public final ObjectProperty<EventHandler<? super KeyEvent>>
7217            onKeyTypedProperty() {
7218        return getEventHandlerProperties().onKeyTypedProperty();
7219    }
7220
7221    /* *************************************************************************
7222     *                                                                         *
7223     *                           Input Method Handling                         *
7224     *                                                                         *
7225     **************************************************************************/
7226
7227    public final void setOnInputMethodTextChanged(
7228            EventHandler<? super InputMethodEvent> value) {
7229        onInputMethodTextChangedProperty().set(value);
7230    }
7231
7232    public final EventHandler<? super InputMethodEvent>
7233            getOnInputMethodTextChanged() {
7234        return (eventHandlerProperties == null)
7235                ? null : eventHandlerProperties.getOnInputMethodTextChanged();
7236    }
7237
7238    /**
7239     * Defines a function to be called when this {@code Node}
7240     * has input focus and the input method text has changed.  If this
7241     * function is not defined in this {@code Node}, then it
7242     * receives the result string of the input method composition as a
7243     * series of {@code onKeyTyped} function calls.
7244     * </p>
7245     * When the {@code Node} loses the input focus, the JavaFX runtime
7246     * automatically commits the existing composed text if any.
7247     */
7248    public final ObjectProperty<EventHandler<? super InputMethodEvent>>
7249            onInputMethodTextChangedProperty() {
7250        return getEventHandlerProperties().onInputMethodTextChangedProperty();
7251    }
7252
7253    public final void setInputMethodRequests(InputMethodRequests value) {
7254        inputMethodRequestsProperty().set(value);
7255    }
7256
7257    public final InputMethodRequests getInputMethodRequests() {
7258        return (miscProperties == null)
7259                       ? DEFAULT_INPUT_METHOD_REQUESTS
7260                       : miscProperties.getInputMethodRequests();
7261    }
7262
7263    /**
7264     * Property holding InputMethodRequests.
7265     *
7266     * @return InputMethodRequestsProperty
7267     */
7268    public final ObjectProperty<InputMethodRequests> inputMethodRequestsProperty() {
7269        return getMiscProperties().inputMethodRequestsProperty();
7270    }
7271
7272    /***************************************************************************
7273     *                                                                         *
7274     *                             Focus Traversal                             *
7275     *                                                                         *
7276     **************************************************************************/
7277
7278    /**
7279     * Special boolean property which allows for atomic focus change.
7280     * Focus change means defocusing the old focus owner and focusing a new
7281     * one. With a usual property, defocusing the old node fires the value
7282     * changed event and user code can react with something that breaks
7283     * focusability of the new node, or even remove the new node from the scene.
7284     * This leads to various error states. This property allows for setting
7285     * the state without firing the event. The focus change first sets both
7286     * properties and then fires both events. This makes the focus change look
7287     * like an atomic operation - when the old node is notified to loose focus,
7288     * the new node is already focused.
7289     */
7290    final class FocusedProperty extends ReadOnlyBooleanPropertyBase {
7291        private boolean value;
7292        private boolean valid = true;
7293        private boolean needsChangeEvent = false;
7294
7295        public void store(final boolean value) {
7296            if (value != this.value) {
7297                this.value = value;
7298                markInvalid();
7299            }
7300        }
7301
7302        public void notifyListeners() {
7303            if (needsChangeEvent) {
7304                fireValueChangedEvent();
7305                needsChangeEvent = false;
7306            }
7307        }
7308
7309        private void markInvalid() {
7310            if (valid) {
7311                valid = false;
7312
7313                pseudoClassStateChanged(FOCUSED_PSEUDOCLASS_STATE, get());
7314                PlatformLogger logger = Logging.getFocusLogger();
7315                if (logger.isLoggable(PlatformLogger.FINE)) {
7316                    logger.fine(this + " focused=" + get());
7317                }
7318
7319                needsChangeEvent = true;
7320            }
7321        }
7322
7323        @Override
7324        public boolean get() {
7325            valid = true;
7326            return value;
7327        }
7328
7329        @Override
7330        public Object getBean() {
7331            return Node.this;
7332        }
7333
7334        @Override
7335        public String getName() {
7336            return "focused";
7337        }
7338    }
7339
7340    /**
7341     * Indicates whether this {@code Node} currently has the input focus.
7342     * To have the input focus, a node must be the {@code Scene}'s focus
7343     * owner, and the scene must be in a {@code Stage} that is visible
7344     * and active. See {@link #requestFocus()} for more information.
7345     *
7346     * @see #requestFocus()
7347     * @defaultValue false
7348     */
7349    private FocusedProperty focused;
7350
7351    protected final void setFocused(boolean value) {
7352        FocusedProperty fp = focusedPropertyImpl();
7353        if (fp.value != value) {
7354            fp.store(value);
7355            fp.notifyListeners();
7356        }
7357    }
7358
7359    public final boolean isFocused() {
7360        return focused == null ? false : focused.get();
7361    }
7362
7363    public final ReadOnlyBooleanProperty focusedProperty() {
7364        return focusedPropertyImpl();
7365    }
7366
7367    private FocusedProperty focusedPropertyImpl() {
7368        if (focused == null) {
7369            focused = new FocusedProperty();
7370        }
7371        return focused;
7372    }
7373
7374    /**
7375     * Specifies whether this {@code Node} should be a part of focus traversal
7376     * cycle. When this property is {@code true} focus can be moved to this
7377     * {@code Node} and from this {@code Node} using regular focus traversal
7378     * keys. On a desktop such keys are usually {@code TAB} for moving focus
7379     * forward and {@code SHIFT+TAB} for moving focus backward.
7380     *
7381     * When a {@code Scene} is created, the system gives focus to a
7382     * {@code Node} whose {@code focusTraversable} variable is true
7383     * and that is eligible to receive the focus,
7384     * unless the focus had been set explicitly via a call
7385     * to {@link #requestFocus()}.
7386     *
7387     * @see #requestFocus()
7388     * @defaultValue false
7389     */
7390    private BooleanProperty focusTraversable;
7391
7392    public final void setFocusTraversable(boolean value) {
7393        focusTraversableProperty().set(value);
7394    }
7395    public final boolean isFocusTraversable() {
7396        return focusTraversable == null ? false : focusTraversable.get();
7397    }
7398
7399    public final BooleanProperty focusTraversableProperty() {
7400        if (focusTraversable == null) {
7401            focusTraversable = new StyleableBooleanProperty(false) {
7402
7403                @Override
7404                public void invalidated() {
7405                    Scene _scene = getScene();
7406                    if (_scene != null) {
7407                        if (get()) {
7408                            _scene.registerTraversable(Node.this);
7409                        } else {
7410                            _scene.unregisterTraversable(Node.this);
7411                        }
7412                        focusSetDirty(_scene);
7413                    }
7414                }
7415
7416                @Override
7417                public CssMetaData getCssMetaData() {
7418                    return StyleableProperties.FOCUS_TRAVERSABLE;
7419                }
7420
7421                @Override
7422                public Object getBean() {
7423                    return Node.this;
7424                }
7425
7426                @Override
7427                public String getName() {
7428                    return "focusTraversable";
7429                }
7430            };
7431        }
7432        return focusTraversable;
7433    }
7434
7435    /**
7436     * Called when something has changed on this node that *may* have made the
7437     * scene's focus dirty. This covers the cases where this node is the focus
7438     * owner and it may have lost eligibility, or it's traversable and it may
7439     * have gained eligibility. Note that we do not want to use disabled
7440     * or treeVisible here, as this function is called from their
7441     * "on invalidate" triggers, and using them will cause them to be
7442     * revalidated. The pulse will revalidate everything and make the final
7443     * determination.
7444     */
7445    private void focusSetDirty(Scene s) {
7446        if (s != null &&
7447            (this == s.getFocusOwner() || isFocusTraversable())) {
7448                s.setFocusDirty(true);
7449        }
7450    }
7451
7452    /**
7453     * Requests that this {@code Node} get the input focus, and that this
7454     * {@code Node}'s top-level ancestor become the focused window. To be
7455     * eligible to receive the focus, the node must be part of a scene, it and
7456     * all of its ancestors must be visible, and it must not be disabled.
7457     * If this node is eligible, this function will cause it to become this
7458     * {@code Scene}'s "focus owner". Each scene has at most one focus owner
7459     * node. The focus owner will not actually have the input focus, however,
7460     * unless the scene belongs to a {@code Stage} that is both visible
7461     * and active.
7462     */
7463    public void requestFocus() {
7464        if (getScene() != null) {
7465            getScene().requestFocus(this);
7466        }
7467    }
7468
7469    /**
7470     * Traverses from this node in the direction indicated. Note that this
7471     * node need not actually have the focus, nor need it be focusTraversable.
7472     * However, the node must be part of a scene, otherwise this request
7473     * is ignored.
7474     *
7475     * @treatAsPrivate implementation detail
7476     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
7477     */
7478    @Deprecated
7479    public final void impl_traverse(Direction dir) {
7480        if (getScene() == null) {
7481            return;
7482        }
7483        getScene().traverse(this, dir);
7484    }
7485
7486    ////////////////////////////
7487    //  Private Implementation
7488    ////////////////////////////
7489
7490     /**
7491      * Returns a string representation for the object.
7492      * @return a string representation for the object.
7493      */
7494    @Override
7495    public String toString() {
7496        String klassName = getClass().getName();
7497        String simpleName = klassName.substring(klassName.lastIndexOf('.')+1);
7498        StringBuilder sbuf = new StringBuilder(simpleName);
7499        boolean hasId = id != null && !"".equals(getId());
7500        boolean hasStyleClass = !getStyleClass().isEmpty();
7501
7502        if (!hasId) {
7503            sbuf.append('@');
7504            sbuf.append(Integer.toHexString(hashCode()));
7505        } else {
7506            sbuf.append("[id=");
7507            sbuf.append(getId());
7508            if (!hasStyleClass) sbuf.append("]");
7509        }
7510        if (hasStyleClass) {
7511            if (!hasId) sbuf.append('[');
7512            else sbuf.append(", ");
7513            sbuf.append("styleClass=");
7514            sbuf.append(getStyleClass());
7515            sbuf.append("]");
7516        }
7517        return sbuf.toString();
7518    }
7519
7520    private void preprocessMouseEvent(MouseEvent e) {
7521        final EventType<?> eventType = e.getEventType();
7522        if (eventType == MouseEvent.MOUSE_PRESSED) {
7523            for (Node n = this; n != null; n = n.getParent()) {
7524                n.setPressed(e.isPrimaryButtonDown());
7525            }
7526            return;
7527        }
7528        if (eventType == MouseEvent.MOUSE_RELEASED) {
7529            for (Node n = this; n != null; n = n.getParent()) {
7530                n.setPressed(e.isPrimaryButtonDown());
7531            }
7532            return;
7533        }
7534
7535        if (e.getTarget() == this) {
7536            // the mouse event types are translated only when the node uses
7537            // its internal event dispatcher, so both entered / exited variants
7538            // are possible here
7539
7540            if ((eventType == MouseEvent.MOUSE_ENTERED)
7541                    || (eventType == MouseEvent.MOUSE_ENTERED_TARGET)) {
7542                setHover(true);
7543                return;
7544            }
7545
7546            if ((eventType == MouseEvent.MOUSE_EXITED)
7547                    || (eventType == MouseEvent.MOUSE_EXITED_TARGET)) {
7548                setHover(false);
7549                return;
7550            }
7551        }
7552    }
7553
7554    private void updateTreeVisible() {
7555        boolean isTreeVisible = isVisible();
7556        if (isTreeVisible) {
7557            isTreeVisible = (getParent() == null) ?
7558                    (getSubScene() == null || getSubScene().impl_isTreeVisible()) :
7559                    getParent().impl_isTreeVisible();
7560        }
7561        setTreeVisible(isTreeVisible);
7562    }
7563
7564    private boolean treeVisible;
7565    private TreeVisiblePropertyReadOnly treeVisibleRO;
7566
7567    final void setTreeVisible(boolean value) {
7568        if (treeVisible != value) {
7569            treeVisible = value;
7570            updateCanReceiveFocus();
7571            focusSetDirty(getScene());
7572            if (treeVisible && !impl_isDirtyEmpty()) {
7573                // The node hasn't been synchronized while invisible, so
7574                // synchronize now
7575                addToSceneDirtyList();
7576            }
7577            ((TreeVisiblePropertyReadOnly)impl_treeVisibleProperty()).invalidate();
7578            if (Node.this instanceof SubScene) {
7579                Node subSceneRoot = ((SubScene)Node.this).getRoot();
7580                if (subSceneRoot != null) {
7581                    // SubScene.getRoot() is only null if it's constructor
7582                    // has not finished.
7583                    subSceneRoot.setTreeVisible(value && subSceneRoot.isVisible());
7584                }
7585            }
7586        }
7587    }
7588
7589    /**
7590     * @treatAsPrivate implementation detail
7591     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
7592     */
7593    @Deprecated
7594    public final boolean impl_isTreeVisible() {
7595        return impl_treeVisibleProperty().get();
7596    }
7597
7598    /**
7599     * @treatAsPrivate implementation detail
7600     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
7601     */
7602    @Deprecated
7603    protected final BooleanExpression impl_treeVisibleProperty() {
7604        if (treeVisibleRO == null) {
7605            treeVisibleRO = new TreeVisiblePropertyReadOnly();
7606        }
7607        return treeVisibleRO;
7608    }
7609
7610    class TreeVisiblePropertyReadOnly extends BooleanExpression {
7611
7612        private ExpressionHelper<Boolean> helper;
7613        private boolean valid;
7614
7615        @Override
7616        public void addListener(InvalidationListener listener) {
7617            helper = ExpressionHelper.addListener(helper, this, listener);
7618        }
7619
7620        @Override
7621        public void removeListener(InvalidationListener listener) {
7622            helper = ExpressionHelper.removeListener(helper, listener);
7623        }
7624
7625        @Override
7626        public void addListener(ChangeListener<? super Boolean> listener) {
7627            helper = ExpressionHelper.addListener(helper, this, listener);
7628        }
7629
7630        @Override
7631        public void removeListener(ChangeListener<? super Boolean> listener) {
7632            helper = ExpressionHelper.removeListener(helper, listener);
7633        }
7634
7635        protected void invalidate() {
7636            if (valid) {
7637                valid = false;
7638                ExpressionHelper.fireValueChangedEvent(helper);
7639            }
7640        }
7641
7642        @Override
7643        public boolean get() {
7644            valid = true;
7645            return Node.this.treeVisible;
7646        }
7647
7648    }
7649
7650    private boolean canReceiveFocus = false;
7651
7652    private void setCanReceiveFocus(boolean value) {
7653        canReceiveFocus = value;
7654    }
7655
7656    final boolean isCanReceiveFocus() {
7657        return canReceiveFocus;
7658    }
7659
7660    private void updateCanReceiveFocus() {
7661        setCanReceiveFocus(getScene() != null
7662          && !isDisabled()
7663          && impl_isTreeVisible());
7664    }
7665
7666    // for indenting messages based on scene-graph depth
7667    String indent() {
7668        String indent = "";
7669        Parent p = this.getParent();
7670        while (p != null) {
7671            indent += "  ";
7672            p = p.getParent();
7673        }
7674        return indent;
7675    }
7676
7677
7678
7679
7680    /**
7681     * Should we underline the mnemonic character?
7682     *
7683     * @treatAsPrivate implementation detail
7684     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
7685     */
7686    @Deprecated
7687    private BooleanProperty impl_showMnemonics;
7688
7689    /**
7690     * @treatAsPrivate implementation detail
7691     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
7692     */
7693    @Deprecated
7694    public final void impl_setShowMnemonics(boolean value) {
7695        impl_showMnemonicsProperty().set(value);
7696    }
7697
7698    /**
7699     * @treatAsPrivate implementation detail
7700     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
7701     */
7702    @Deprecated
7703    public final boolean impl_isShowMnemonics() {
7704        return impl_showMnemonics == null ? false : impl_showMnemonics.get();
7705    }
7706
7707    /**
7708     * @treatAsPrivate implementation detail
7709     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
7710     */
7711    @Deprecated
7712    public final BooleanProperty impl_showMnemonicsProperty() {
7713        if (impl_showMnemonics == null) {
7714            impl_showMnemonics = new BooleanPropertyBase(false) {
7715
7716                @Override
7717                protected void invalidated() {
7718                    pseudoClassStateChanged(SHOW_MNEMONICS_PSEUDOCLASS_STATE, get());
7719                }
7720
7721                @Override
7722                public Object getBean() {
7723                    return Node.this;
7724                }
7725
7726                @Override
7727                public String getName() {
7728                    return "showMnemonics";
7729                }
7730            };
7731        }
7732        return impl_showMnemonics;
7733    }
7734
7735
7736
7737
7738
7739    /***************************************************************************
7740     *                                                                         *
7741     *                         Event Dispatch                                  *
7742     *                                                                         *
7743     **************************************************************************/
7744
7745    // PENDING_DOC_REVIEW
7746    /**
7747     * Specifies the event dispatcher for this node. The default event
7748     * dispatcher sends the received events to the registered event handlers and
7749     * filters. When replacing the value with a new {@code EventDispatcher},
7750     * the new dispatcher should forward events to the replaced dispatcher
7751     * to maintain the node's default event handling behavior.
7752     */
7753    private ObjectProperty<EventDispatcher> eventDispatcher;
7754
7755    public final void setEventDispatcher(EventDispatcher value) {
7756        eventDispatcherProperty().set(value);
7757    }
7758
7759    public final EventDispatcher getEventDispatcher() {
7760        return eventDispatcherProperty().get();
7761    }
7762
7763    public final ObjectProperty<EventDispatcher> eventDispatcherProperty() {
7764        initializeInternalEventDispatcher();
7765        return eventDispatcher;
7766    }
7767
7768    private NodeEventDispatcher internalEventDispatcher;
7769
7770    // PENDING_DOC_REVIEW
7771    /**
7772     * Registers an event handler to this node. The handler is called when the
7773     * node receives an {@code Event} of the specified type during the bubbling
7774     * phase of event delivery.
7775     *
7776     * @param <T> the specific event class of the handler
7777     * @param eventType the type of the events to receive by the handler
7778     * @param eventHandler the handler to register
7779     * @throws NullPointerException if the event type or handler is null
7780     */
7781    public final <T extends Event> void addEventHandler(
7782            final EventType<T> eventType,
7783            final EventHandler<? super T> eventHandler) {
7784        getInternalEventDispatcher().getEventHandlerManager()
7785                                    .addEventHandler(eventType, eventHandler);
7786    }
7787
7788    // PENDING_DOC_REVIEW
7789    /**
7790     * Unregisters a previously registered event handler from this node. One
7791     * handler might have been registered for different event types, so the
7792     * caller needs to specify the particular event type from which to
7793     * unregister the handler.
7794     *
7795     * @param <T> the specific event class of the handler
7796     * @param eventType the event type from which to unregister
7797     * @param eventHandler the handler to unregister
7798     * @throws NullPointerException if the event type or handler is null
7799     */
7800    public final <T extends Event> void removeEventHandler(
7801            final EventType<T> eventType,
7802            final EventHandler<? super T> eventHandler) {
7803        getInternalEventDispatcher()
7804                .getEventHandlerManager()
7805                .removeEventHandler(eventType, eventHandler);
7806    }
7807
7808    // PENDING_DOC_REVIEW
7809    /**
7810     * Registers an event filter to this node. The filter is called when the
7811     * node receives an {@code Event} of the specified type during the capturing
7812     * phase of event delivery.
7813     *
7814     * @param <T> the specific event class of the filter
7815     * @param eventType the type of the events to receive by the filter
7816     * @param eventFilter the filter to register
7817     * @throws NullPointerException if the event type or filter is null
7818     */
7819    public final <T extends Event> void addEventFilter(
7820            final EventType<T> eventType,
7821            final EventHandler<? super T> eventFilter) {
7822        getInternalEventDispatcher().getEventHandlerManager()
7823                                    .addEventFilter(eventType, eventFilter);
7824    }
7825
7826    // PENDING_DOC_REVIEW
7827    /**
7828     * Unregisters a previously registered event filter from this node. One
7829     * filter might have been registered for different event types, so the
7830     * caller needs to specify the particular event type from which to
7831     * unregister the filter.
7832     *
7833     * @param <T> the specific event class of the filter
7834     * @param eventType the event type from which to unregister
7835     * @param eventFilter the filter to unregister
7836     * @throws NullPointerException if the event type or filter is null
7837     */
7838    public final <T extends Event> void removeEventFilter(
7839            final EventType<T> eventType,
7840            final EventHandler<? super T> eventFilter) {
7841        getInternalEventDispatcher().getEventHandlerManager()
7842                                    .removeEventFilter(eventType, eventFilter);
7843    }
7844
7845    /**
7846     * Sets the handler to use for this event type. There can only be one such handler
7847     * specified at a time. This handler is guaranteed to be called first. This is
7848     * used for registering the user-defined onFoo event handlers.
7849     *
7850     * @param <T> the specific event class of the handler
7851     * @param eventType the event type to associate with the given eventHandler
7852     * @param eventHandler the handler to register, or null to unregister
7853     * @throws NullPointerException if the event type is null
7854     */
7855    protected final <T extends Event> void setEventHandler(
7856            final EventType<T> eventType,
7857            final EventHandler<? super T> eventHandler) {
7858        getInternalEventDispatcher().getEventHandlerManager()
7859                                    .setEventHandler(eventType, eventHandler);
7860    }
7861
7862    private NodeEventDispatcher getInternalEventDispatcher() {
7863        initializeInternalEventDispatcher();
7864        return internalEventDispatcher;
7865    }
7866
7867    private void initializeInternalEventDispatcher() {
7868        if (internalEventDispatcher == null) {
7869            internalEventDispatcher = createInternalEventDispatcher();
7870            eventDispatcher = new SimpleObjectProperty<EventDispatcher>(
7871                                          Node.this,
7872                                          "eventDispatcher",
7873                                          internalEventDispatcher);
7874        }
7875    }
7876
7877    private NodeEventDispatcher createInternalEventDispatcher() {
7878        return new NodeEventDispatcher(this);
7879    }
7880
7881    /**
7882     * Event dispatcher for invoking preprocessing of mouse events
7883     */
7884    private EventDispatcher preprocessMouseEventDispatcher;
7885
7886    // PENDING_DOC_REVIEW
7887    /**
7888     * Construct an event dispatch chain for this node. The event dispatch chain
7889     * contains all event dispatchers from the stage to this node.
7890     *
7891     * @param tail the initial chain to build from
7892     * @return the resulting event dispatch chain for this node
7893     */
7894    @Override
7895    public EventDispatchChain buildEventDispatchChain(
7896            EventDispatchChain tail) {
7897
7898        if (preprocessMouseEventDispatcher == null) {
7899            preprocessMouseEventDispatcher = new EventDispatcher() {
7900                @Override
7901                public Event dispatchEvent(Event event,
7902                                           EventDispatchChain tail) {
7903                    event = tail.dispatchEvent(event);
7904                    if (event instanceof MouseEvent) {
7905                        preprocessMouseEvent((MouseEvent) event);
7906                    }
7907
7908                    return event;
7909                }
7910            };
7911        }
7912
7913        tail = tail.prepend(preprocessMouseEventDispatcher);
7914
7915        // prepend all event dispatchers from this node to the root
7916        Node curNode = this;
7917        do {
7918            if (curNode.eventDispatcher != null) {
7919                final EventDispatcher eventDispatcherValue =
7920                        curNode.eventDispatcher.get();
7921                if (eventDispatcherValue != null) {
7922                    tail = tail.prepend(eventDispatcherValue);
7923                }
7924            }
7925            final Node curParent = curNode.getParent();
7926            curNode = curParent != null ? curParent : curNode.getSubScene();
7927        } while (curNode != null);
7928
7929        if (getScene() != null) {
7930            // prepend scene's dispatch chain
7931            tail = getScene().buildEventDispatchChain(tail);
7932        }
7933
7934        return tail;
7935    }
7936
7937    // PENDING_DOC_REVIEW
7938    /**
7939     * Fires the specified event. By default the event will travel through the
7940     * hierarchy from the stage to this node. Any event filter encountered will
7941     * be notified and can consume the event. If not consumed by the filters,
7942     * the event handlers on this node are notified. If these don't consume the
7943     * event eighter, the event will travel back the same path it arrived to
7944     * this node. All event handlers encountered are called and can consume the
7945     * event.
7946     * <p>
7947     * This method must be called on the FX user thread.
7948     *
7949     * @param event the event to fire
7950     */
7951    public final void fireEvent(Event event) {
7952
7953        /* Log input events.  We do a coarse filter for at least the FINE
7954         * level and then granularize from there.
7955         */
7956        if (event instanceof InputEvent) {
7957            PlatformLogger logger = Logging.getInputLogger();
7958            if (logger.isLoggable(PlatformLogger.FINE)) {
7959                EventType eventType = event.getEventType();
7960                if (eventType == MouseEvent.MOUSE_ENTERED ||
7961                    eventType == MouseEvent.MOUSE_EXITED) {
7962                    logger.finer(event.toString());
7963                } else if (eventType == MouseEvent.MOUSE_MOVED ||
7964                           eventType == MouseEvent.MOUSE_DRAGGED) {
7965                    logger.finest(event.toString());
7966                } else {
7967                    logger.fine(event.toString());
7968                }
7969            }
7970        }
7971
7972        Event.fireEvent(this, event);
7973    }
7974
7975    /***************************************************************************
7976     *                                                                         *
7977     *                         Stylesheet Handling                             *
7978     *                                                                         *
7979     **************************************************************************/
7980
7981
7982    /**
7983     * {@inheritDoc}
7984     * @return {@code getClass().getName()} without the package name
7985     */
7986    @Override
7987    public String getTypeSelector() {
7988
7989        final Class<?> clazz = getClass();
7990        final Package pkg = clazz.getPackage();
7991
7992        // package could be null. not likely, but could be.
7993        int plen = 0;
7994        if (pkg != null) {
7995            plen = pkg.getName().length();
7996        }
7997
7998        final int clen = clazz.getName().length();
7999        final int pos = (0 < plen && plen < clen) ? plen + 1 : 0;
8000
8001        return clazz.getName().substring(pos);
8002    }
8003
8004    /**
8005     * {@inheritDoc}
8006     * @return {@code getParent()}
8007     */
8008    @Override
8009    public Styleable getStyleableParent() {
8010        return getParent();
8011    }
8012
8013
8014     /**
8015      * Not everything uses the default value of false for focusTraversable.
8016      * This method provides a way to have them return the correct initial value.
8017      * @treatAsPrivate implementation detail
8018      */
8019    @Deprecated
8020    protected /*do not make final*/ Boolean impl_cssGetFocusTraversableInitialValue() {
8021        return Boolean.FALSE;
8022    }
8023
8024     /**
8025      * Not everything uses the default value of null for cursor.
8026      * This method provides a way to have them return the correct initial value.
8027      * @treatAsPrivate implementation detail
8028      */
8029    @Deprecated
8030    protected /*do not make final*/ Cursor impl_cssGetCursorInitialValue() {
8031        return null;
8032    }
8033
8034     /**
8035      * Super-lazy instantiation pattern from Bill Pugh.
8036      * @treatAsPrivate implementation detail
8037      */
8038     private static class StyleableProperties {
8039
8040        private static final CssMetaData<Node,Cursor> CURSOR =
8041            new CssMetaData<Node,Cursor>("-fx-cursor", CursorConverter.getInstance()) {
8042
8043                @Override
8044                public boolean isSettable(Node node) {
8045                    return node.miscProperties == null || node.miscProperties.canSetCursor();
8046                }
8047
8048                @Override
8049                public StyleableProperty<Cursor> getStyleableProperty(Node node) {
8050                    return (StyleableProperty<Cursor>)node.cursorProperty();
8051                }
8052
8053                @Override
8054                public Cursor getInitialValue(Node node) {
8055                    // Most controls default focusTraversable to true.
8056                    // Give a way to have them return the correct default value.
8057                    return node.impl_cssGetCursorInitialValue();
8058                }
8059
8060            };
8061        private static final CssMetaData<Node,Effect> EFFECT =
8062            new CssMetaData<Node,Effect>("-fx-effect", EffectConverter.getInstance()) {
8063
8064                @Override
8065                public boolean isSettable(Node node) {
8066                    return node.miscProperties == null || node.miscProperties.canSetEffect();
8067                }
8068
8069                @Override
8070                public StyleableProperty<Effect> getStyleableProperty(Node node) {
8071                    return (StyleableProperty<Effect>)node.effectProperty();
8072                }
8073            };
8074        private static final CssMetaData<Node,Boolean> FOCUS_TRAVERSABLE =
8075            new CssMetaData<Node,Boolean>("-fx-focus-traversable",
8076                BooleanConverter.getInstance(), Boolean.FALSE) {
8077
8078                @Override
8079                public boolean isSettable(Node node) {
8080                    return node.focusTraversable == null || !node.focusTraversable.isBound();
8081                }
8082
8083                @Override
8084                public StyleableProperty<Boolean> getStyleableProperty(Node node) {
8085                    return (StyleableProperty<Boolean>)node.focusTraversableProperty();
8086                }
8087
8088                @Override
8089                public Boolean getInitialValue(Node node) {
8090                    // Most controls default focusTraversable to true.
8091                    // Give a way to have them return the correct default value.
8092                    return node.impl_cssGetFocusTraversableInitialValue();
8093                }
8094
8095            };
8096        private static final CssMetaData<Node,Number> OPACITY =
8097            new CssMetaData<Node,Number>("-fx-opacity",
8098                SizeConverter.getInstance(), 1.0) {
8099
8100                @Override
8101                public boolean isSettable(Node node) {
8102                    return node.opacity == null || !node.opacity.isBound();
8103                }
8104
8105                @Override
8106                public StyleableProperty<Number> getStyleableProperty(Node node) {
8107                    return (StyleableProperty<Number>)node.opacityProperty();
8108                }
8109            };
8110        private static final CssMetaData<Node,BlendMode> BLEND_MODE =
8111            new CssMetaData<Node,BlendMode>("-fx-blend-mode", new EnumConverter<BlendMode>(BlendMode.class)) {
8112
8113                @Override
8114                public boolean isSettable(Node node) {
8115                    return node.blendMode == null || !node.blendMode.isBound();
8116                }
8117
8118                @Override
8119                public StyleableProperty<BlendMode> getStyleableProperty(Node node) {
8120                    return (StyleableProperty<BlendMode>)node.blendModeProperty();
8121                }
8122            };
8123        private static final CssMetaData<Node,Number> ROTATE =
8124            new CssMetaData<Node,Number>("-fx-rotate",
8125                SizeConverter.getInstance(), 0.0) {
8126
8127                @Override
8128                public boolean isSettable(Node node) {
8129                    return node.nodeTransformation == null
8130                        || node.nodeTransformation.rotate == null
8131                        || node.nodeTransformation.canSetRotate();
8132                }
8133
8134                @Override
8135                public StyleableProperty<Number> getStyleableProperty(Node node) {
8136                    return (StyleableProperty<Number>)node.rotateProperty();
8137                }
8138            };
8139        private static final CssMetaData<Node,Number> SCALE_X =
8140            new CssMetaData<Node,Number>("-fx-scale-x",
8141                SizeConverter.getInstance(), 1.0) {
8142
8143                @Override
8144                public boolean isSettable(Node node) {
8145                    return node.nodeTransformation == null
8146                        || node.nodeTransformation.scaleX == null
8147                        || node.nodeTransformation.canSetScaleX();
8148                }
8149
8150                @Override
8151                public StyleableProperty<Number> getStyleableProperty(Node node) {
8152                    return (StyleableProperty<Number>)node.scaleXProperty();
8153                }
8154            };
8155        private static final CssMetaData<Node,Number> SCALE_Y =
8156            new CssMetaData<Node,Number>("-fx-scale-y",
8157                SizeConverter.getInstance(), 1.0) {
8158
8159                @Override
8160                public boolean isSettable(Node node) {
8161                    return node.nodeTransformation == null
8162                        || node.nodeTransformation.scaleY == null
8163                        || node.nodeTransformation.canSetScaleY();
8164                }
8165
8166                @Override
8167                public StyleableProperty<Number> getStyleableProperty(Node node) {
8168                    return (StyleableProperty<Number>)node.scaleYProperty();
8169                }
8170            };
8171        private static final CssMetaData<Node,Number> SCALE_Z =
8172            new CssMetaData<Node,Number>("-fx-scale-z",
8173                SizeConverter.getInstance(), 1.0) {
8174
8175                @Override
8176                public boolean isSettable(Node node) {
8177                    return node.nodeTransformation == null
8178                        || node.nodeTransformation.scaleZ == null
8179                        || node.nodeTransformation.canSetScaleZ();
8180                }
8181
8182                @Override
8183                public StyleableProperty<Number> getStyleableProperty(Node node) {
8184                    return (StyleableProperty<Number>)node.scaleZProperty();
8185                }
8186            };
8187        private static final CssMetaData<Node,Number> TRANSLATE_X =
8188            new CssMetaData<Node,Number>("-fx-translate-x",
8189                SizeConverter.getInstance(), 0.0) {
8190
8191                @Override
8192                public boolean isSettable(Node node) {
8193                    return node.nodeTransformation == null
8194                        || node.nodeTransformation.translateX == null
8195                        || node.nodeTransformation.canSetTranslateX();
8196                }
8197
8198                @Override
8199                public StyleableProperty<Number> getStyleableProperty(Node node) {
8200                    return (StyleableProperty<Number>)node.translateXProperty();
8201                }
8202            };
8203        private static final CssMetaData<Node,Number> TRANSLATE_Y =
8204            new CssMetaData<Node,Number>("-fx-translate-y",
8205                SizeConverter.getInstance(), 0.0) {
8206
8207                @Override
8208                public boolean isSettable(Node node) {
8209                    return node.nodeTransformation == null
8210                        || node.nodeTransformation.translateY == null
8211                        || node.nodeTransformation.canSetTranslateY();
8212                }
8213
8214                @Override
8215                public StyleableProperty<Number> getStyleableProperty(Node node) {
8216                    return (StyleableProperty<Number>)node.translateYProperty();
8217                }
8218            };
8219        private static final CssMetaData<Node,Number> TRANSLATE_Z =
8220            new CssMetaData<Node,Number>("-fx-translate-z",
8221                SizeConverter.getInstance(), 0.0) {
8222
8223                @Override
8224                public boolean isSettable(Node node) {
8225                    return node.nodeTransformation == null
8226                        || node.nodeTransformation.translateZ == null
8227                        || node.nodeTransformation.canSetTranslateZ();
8228                }
8229
8230                @Override
8231                public StyleableProperty<Number> getStyleableProperty(Node node) {
8232                    return (StyleableProperty<Number>)node.translateZProperty();
8233                }
8234            };
8235        private static final CssMetaData<Node,Boolean> VISIBILITY =
8236            new CssMetaData<Node,Boolean>("visibility",
8237                new StyleConverter<String,Boolean>() {
8238
8239                    @Override
8240                    // [ visible | hidden | collapse | inherit ]
8241                    public Boolean convert(ParsedValue<String, Boolean> value, Font font) {
8242                        final String sval = value != null ? value.getValue() : null;
8243                        return "visible".equalsIgnoreCase(sval);
8244                    }
8245
8246                },
8247                Boolean.TRUE) {
8248
8249                @Override
8250                public boolean isSettable(Node node) {
8251                    return node.visible == null || !node.visible.isBound();
8252                }
8253
8254                @Override
8255                public StyleableProperty<Boolean> getStyleableProperty(Node node) {
8256                    return (StyleableProperty<Boolean>)node.visibleProperty();
8257                }
8258            };
8259
8260         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
8261
8262         static {
8263
8264             final List<CssMetaData<? extends Styleable, ?>> styleables =
8265                     new ArrayList<CssMetaData<? extends Styleable, ?>>();
8266             styleables.add(CURSOR);
8267             styleables.add(EFFECT);
8268             styleables.add(FOCUS_TRAVERSABLE);
8269             styleables.add(OPACITY);
8270             styleables.add(BLEND_MODE);
8271             styleables.add(ROTATE);
8272             styleables.add(SCALE_X);
8273             styleables.add(SCALE_Y);
8274             styleables.add(SCALE_Z);
8275             styleables.add(TRANSLATE_X);
8276             styleables.add(TRANSLATE_Y);
8277             styleables.add(TRANSLATE_Z);
8278             styleables.add(VISIBILITY);
8279             STYLEABLES = Collections.unmodifiableList(styleables);
8280
8281         }
8282    }
8283
8284    /**
8285     * @return The CssMetaData associated with this class, which may include the
8286     * CssMetaData of its super classes.
8287     */
8288    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
8289        //
8290        // Super-lazy instantiation pattern from Bill Pugh. StyleableProperties
8291        // is referenced no earlier (and therefore loaded no earlier by the
8292        // class loader) than the moment that  getClassCssMetaData() is called.
8293        // This avoids loading the CssMetaData instances until the point at
8294        // which CSS needs the data.
8295        //
8296        return StyleableProperties.STYLEABLES;
8297    }
8298
8299    /**
8300     * This method should delegate to {@link Node#getClassCssMetaData()} so that
8301     * a Node's CssMetaData can be accessed without the need for reflection.
8302     *
8303     * @return The CssMetaData associated with this node, which may include the
8304     * CssMetaData of its super classes.
8305     */
8306
8307    @Override
8308    public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
8309        return getClassCssMetaData();
8310    }
8311
8312    /**
8313     * @return  The Styles that match this CSS property for the given Node. The
8314     * list is sorted by descending specificity.
8315     * @treatAsPrivate implementation detail
8316     * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
8317     */
8318     @Deprecated // SB-dependency: RT-21096 has been filed to track this
8319    public static List<Style> impl_getMatchingStyles(CssMetaData cssMetaData, Styleable styleable) {
8320        if (styleable != null && cssMetaData != null && styleable instanceof Node) {
8321
8322            Node node = (Node)styleable;
8323
8324            if (node.styleHelper != null) {
8325                return node.styleHelper.getMatchingStyles(node, cssMetaData);
8326            }
8327
8328        }
8329        return Collections.<Style>emptyList();
8330    }
8331
8332     /**
8333      * RT-17293
8334      * @treatAsPrivate implementation detail
8335      * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
8336      */
8337     @Deprecated // SB-dependency: RT-21096 has been filed to track this
8338     public final ObservableMap<StyleableProperty<?>, List<Style>> impl_getStyleMap() {
8339         return styleHelper != null
8340             ? styleHelper.getObservableStyleMap()
8341             : FXCollections.<StyleableProperty<?>, List<Style>>emptyObservableMap();
8342     }
8343
8344     /**
8345      * RT-17293
8346      * @treatAsPrivate implementation detail
8347      * @deprecated This is an experimental API that is not intended for general use and is subject to change in future versions
8348      */
8349     @Deprecated // SB-dependency: RT-21096 has been filed to track this
8350     public final void impl_setStyleMap(ObservableMap<StyleableProperty<?>, List<Style>> styleMap) {
8351         if (styleHelper != null) {
8352             styleHelper.setObservableStyleMap(styleMap);
8353         }
8354     }
8355
8356    /**
8357     * Flags used to indicate in which way this node is dirty (or whether it
8358     * is clean) and what must happen during the next CSS cycle on the
8359     * scenegraph.
8360     */
8361    CssFlags cssFlag = CssFlags.CLEAN;
8362
8363    /**
8364     * Needed for testing.
8365     */
8366    final CssFlags getCSSFlags() { return cssFlag; }
8367
8368    /**
8369     * Called when a CSS pseudo-class change would cause styles to be reapplied.
8370     */
8371    private void requestCssStateTransition() {
8372        // If there is no scene, then we cannot make it dirty, so we'll leave
8373        // the flag alone
8374        if (getScene() == null) return;
8375        // Don't bother doing anything if the cssFlag is not CLEAN.
8376        // If the flag indicates a DIRTY_BRANCH, the flag needs to be changed
8377        // to UPDATE to ensure that impl_processCSS is called on the node.
8378        if (cssFlag == CssFlags.CLEAN || cssFlag == CssFlags.DIRTY_BRANCH) {
8379            cssFlag = CssFlags.UPDATE;
8380            notifyParentsOfInvalidatedCSS();
8381        }
8382    }
8383
8384    /**
8385     * Used to specify that a pseudo-class of this Node has changed. If the
8386     * pseudo-class is used in a CSS selector that matches this Node, CSS will
8387     * be reapplied. Typically, this method is called from the {@code invalidated}
8388     * method of a property that is used as a pseudo-class. For example:
8389     * <code><pre>
8390     *
8391     *     private static final PseudoClass MY_PSEUDO_CLASS_STATE = PseudoClass.getPseudoClass("my-state");
8392     *
8393     *     BooleanProperty myPseudoClassState = new BooleanPropertyBase(false) {
8394     *
8395     *           {@literal @}Override public void invalidated() {
8396     *                pseudoClassStateChanged(MY_PSEUDO_CLASS_STATE, get());
8397     *           }
8398     *
8399     *           {@literal @}Override public Object getBean() {
8400     *               return MyControl.this;
8401     *           }
8402     *
8403     *           {@literal @}Override public String getName() {
8404     *               return "myPseudoClassState";
8405     *           }
8406     *       };
8407     * </pre><code>
8408     * @param pseudoClass the pseudo-class that has changed state
8409     * @param active whether or not the state is active
8410     */
8411    public final void pseudoClassStateChanged(PseudoClass pseudoClass, boolean active) {
8412
8413        final boolean modified = active
8414                ? pseudoClassStates.add(pseudoClass)
8415                : pseudoClassStates.remove(pseudoClass);
8416
8417        if (modified && styleHelper != null) {
8418            final boolean isTransition = styleHelper.pseudoClassStateChanged(pseudoClass);
8419            if (isTransition) {
8420                requestCssStateTransition();
8421            }
8422        }
8423   }
8424
8425    // package so that StyleHelper can get at it
8426    final ObservableSet<PseudoClass> pseudoClassStates = new PseudoClassState();
8427    /**
8428     * @return The active pseudo-class states of this Node, wrapped in an unmodifiable ObservableSet
8429     */
8430    public final ObservableSet<PseudoClass> getPseudoClassStates() {
8431
8432        return FXCollections.unmodifiableObservableSet(pseudoClassStates);
8433
8434    }
8435
8436    // Walks up the tree telling each parent that the pseudo class state of
8437    // this node has changed.
8438    /** @treatAsPrivate */
8439    final void notifyParentsOfInvalidatedCSS() {
8440        SubScene subScene = getSubScene();
8441        Parent root = (subScene != null) ?
8442                subScene.getRoot() : getScene().getRoot();
8443
8444        if (!root.impl_isDirty(DirtyBits.NODE_CSS)) {
8445            // Ensure that Scene.root is marked as dirty. If the scene isn't
8446            // dirty, nothing will get repainted. This bit is cleared from
8447            // Scene in doCSSPass().
8448            root.impl_markDirty(DirtyBits.NODE_CSS);
8449            if (subScene != null) {
8450                // If the node is part of a subscene, then we must ensure that
8451                // the we not only mark subScene.root dirty, but continue and
8452                // call subScene.notifyParentsOfInvalidatedCSS() until
8453                // Scene.root gets marked dirty, via the recurisve call:
8454                subScene.cssFlag = CssFlags.UPDATE;
8455                subScene.notifyParentsOfInvalidatedCSS();
8456            }
8457        }
8458        Parent _parent = getParent();
8459        while (_parent != null) {
8460            if (_parent.cssFlag == CssFlags.CLEAN) {
8461                _parent.cssFlag = CssFlags.DIRTY_BRANCH;
8462                _parent = _parent.getParent();
8463            } else {
8464                _parent = null;
8465            }
8466        }
8467    }
8468
8469    /**
8470     * @treatAsPrivate implementation detail
8471     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
8472     */
8473    @Deprecated
8474    public final void impl_reapplyCSS() {
8475        // If there is no scene, then we cannot make it dirty, so we'll leave
8476        // the flag alone
8477        if (getScene() == null) return;
8478        // If the css flag is already "REAPPLY", then do nothing
8479        if (cssFlag == CssFlags.REAPPLY) return;
8480        // Update the flag
8481        cssFlag = CssFlags.REAPPLY;
8482
8483        // One idiom employed by developers is to, during the layout pass,
8484        // add or remove nodes from the scene. For example, a ScrollPane
8485        // might add scroll bars to itself if it determines during layout
8486        // that it needs them, or a ListView might add cells to itself if
8487        // it determines that it needs to. In such situations we must
8488        // apply the CSS immediately and not add it to the scene's queue
8489        // for deferred action.
8490        if (getParent() != null && getParent().performingLayout) {
8491            impl_processCSS();
8492        } else if (getScene() != null) {
8493            notifyParentsOfInvalidatedCSS();
8494        }
8495    }
8496
8497    void processCSS() {
8498        switch (cssFlag) {
8499            case CLEAN:
8500                break;
8501            case DIRTY_BRANCH:
8502                Parent me = (Parent)this;
8503                // clear the flag first in case the flag is set to something
8504                // other than clean by downstream processing.
8505                me.cssFlag = CssFlags.CLEAN;
8506                List<Node> children = me.getChildren();
8507                for (int i=0, max=children.size(); i<max; i++) {
8508                    children.get(i).processCSS();
8509                }
8510                break;
8511            case REAPPLY:
8512            case RECALCULATE:
8513            case UPDATE:
8514            default:
8515                impl_processCSS();
8516        }
8517    }
8518
8519    /**
8520     * If invoked, will update / reapply styles from here on down. If reapply
8521     * is false, then we will only update from here on down, otherwise we will
8522     * do a full reapply.
8523     *
8524     * @treatAsPrivate implementation detail
8525     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
8526     */
8527    @Deprecated // SB-dependency: RT-21206 has been filed to track this
8528    public final void impl_processCSS(boolean reapply) {
8529
8530        assert(getScene() != null);
8531        if (getScene() == null) return;
8532
8533        //
8534        // Normally, css is processed from the root down. If this method
8535        // is called, then the code is trying to force css to be applied
8536        // in the middle of a pulse.
8537        //
8538        final boolean flag = (reapply || cssFlag == CssFlags.REAPPLY);
8539        cssFlag = flag ? CssFlags.REAPPLY : CssFlags.UPDATE;
8540
8541        //
8542        // RT-28394 - need to see if any ancestor has a flag other than clean
8543        // If so, process css from the top-most css-dirty node
8544        //
8545        Node topMost = this;
8546        Node _parent = getParent();
8547        while (_parent != null) {
8548            if (_parent.cssFlag != CssFlags.CLEAN) {
8549                topMost = _parent;
8550            }
8551            _parent = _parent.getParent();
8552        }
8553
8554        _parent = this;
8555        while (_parent != topMost) {
8556            if (_parent.cssFlag == CssFlags.CLEAN) {
8557                _parent.cssFlag = CssFlags.DIRTY_BRANCH;
8558            }
8559            _parent = _parent.getParent();
8560        }
8561        topMost.processCSS();
8562    }
8563
8564    /**
8565     * If invoked, will update / reapply styles from here on down. If reapply
8566     * is false, then we will only update from here on down, otherwise we will
8567     * do a full reapply.
8568     *
8569     * @treatAsPrivate implementation detail
8570     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
8571     */
8572    @Deprecated // SB-dependency: RT-21206 has been filed to track this
8573    protected void impl_processCSS() {
8574
8575        // Nothing to do...
8576        if (cssFlag == CssFlags.CLEAN) return;
8577
8578        final Scene scene = getScene();
8579        if (scene == null) {
8580            cssFlag = CssFlags.CLEAN;
8581            return;
8582        }
8583
8584        if (cssFlag == CssFlags.REAPPLY) {
8585
8586            // Match new styles if my own indicates I need to reapply
8587            styleHelper = CssStyleHelper.createStyleHelper(this);
8588
8589        } else if (cssFlag == CssFlags.RECALCULATE) {
8590
8591            // Recalculate means that the in-line style has changed.
8592            if (styleHelper != null) {
8593                styleHelper.inlineStyleChanged(this);
8594            } else {
8595                // If there isn't a styleHelper now, there might need to be.
8596                // Note that it is not necessary to REAPPLY css to children
8597                // since the stylesheets haven't changed. The children only
8598                // need to RECALCULATE their styles. A child that didn't
8599                // have a styleHelper before will drop into this block, but if
8600                // there are no matching style or inline styles, the child's
8601                // styleHelper will still be null.
8602                styleHelper = CssStyleHelper.createStyleHelper(this);
8603            }
8604
8605        }
8606
8607        // Clear the flag first in case the flag is set to something
8608        // other than clean by downstream processing.
8609        cssFlag = CssFlags.CLEAN;
8610
8611        // Transition to the new state and apply styles
8612        if (styleHelper != null) {
8613            styleHelper.transitionToState(this);
8614        }
8615    }
8616
8617    /**
8618     * A StyleHelper for this node.
8619     * A StyleHelper contains all the css styles for this node
8620     * and knows how to apply them when our state changes.
8621     */
8622    CssStyleHelper styleHelper;
8623
8624    private static final PseudoClass HOVER_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("hover");
8625    private static final PseudoClass PRESSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("pressed");
8626    private static final PseudoClass DISABLED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("disabled");
8627    private static final PseudoClass FOCUSED_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("focused");
8628    private static final PseudoClass SHOW_MNEMONICS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("show-mnemonics");
8629
8630    private static abstract class LazyTransformProperty
8631            extends ReadOnlyObjectProperty<Transform> {
8632
8633        protected static final int VALID = 0;
8634        protected static final int INVALID = 1;
8635        protected static final int VALIDITY_UNKNOWN = 2;
8636        protected int valid = INVALID;
8637
8638        private ExpressionHelper<Transform> helper;
8639
8640        private Transform transform;
8641        private boolean canReuse = false;
8642
8643        @Override
8644        public void addListener(InvalidationListener listener) {
8645            helper = ExpressionHelper.addListener(helper, this, listener);
8646        }
8647
8648        @Override
8649        public void removeListener(InvalidationListener listener) {
8650            helper = ExpressionHelper.removeListener(helper, listener);
8651        }
8652
8653        @Override
8654        public void addListener(ChangeListener<? super Transform> listener) {
8655            helper = ExpressionHelper.addListener(helper, this, listener);
8656        }
8657
8658        @Override
8659        public void removeListener(ChangeListener<? super Transform> listener) {
8660            helper = ExpressionHelper.removeListener(helper, listener);
8661        }
8662
8663        protected Transform getInternalValue() {
8664            if (valid == INVALID ||
8665                    (valid == VALIDITY_UNKNOWN && computeValidity() == INVALID)) {
8666                transform = computeTransform(canReuse ? transform : null);
8667                canReuse = true;
8668                valid = validityKnown() ? VALID : VALIDITY_UNKNOWN;
8669            }
8670
8671            return transform;
8672        }
8673
8674        @Override
8675        public Transform get() {
8676            transform = getInternalValue();
8677            canReuse = false;
8678            return transform;
8679        }
8680
8681        public void invalidate() {
8682            if (valid != INVALID) {
8683                valid = INVALID;
8684                ExpressionHelper.fireValueChangedEvent(helper);
8685            }
8686        }
8687
8688        protected abstract boolean validityKnown();
8689        protected abstract int computeValidity();
8690        protected abstract Transform computeTransform(Transform reuse);
8691    }
8692
8693    private static abstract class LazyBoundsProperty
8694            extends ReadOnlyObjectProperty<Bounds> {
8695        private ExpressionHelper<Bounds> helper;
8696        private boolean valid;
8697
8698        private Bounds bounds;
8699
8700        @Override
8701        public void addListener(InvalidationListener listener) {
8702            helper = ExpressionHelper.addListener(helper, this, listener);
8703        }
8704
8705        @Override
8706        public void removeListener(InvalidationListener listener) {
8707            helper = ExpressionHelper.removeListener(helper, listener);
8708        }
8709
8710        @Override
8711        public void addListener(ChangeListener<? super Bounds> listener) {
8712            helper = ExpressionHelper.addListener(helper, this, listener);
8713        }
8714
8715        @Override
8716        public void removeListener(ChangeListener<? super Bounds> listener) {
8717            helper = ExpressionHelper.removeListener(helper, listener);
8718        }
8719
8720        @Override
8721        public Bounds get() {
8722            if (!valid) {
8723                bounds = computeBounds();
8724                valid = true;
8725            }
8726
8727            return bounds;
8728        }
8729
8730        public void invalidate() {
8731            if (valid) {
8732                valid = false;
8733                ExpressionHelper.fireValueChangedEvent(helper);
8734            }
8735        }
8736
8737        protected abstract Bounds computeBounds();
8738    }
8739
8740    private static final BoundsAccessor boundsAccessor = new BoundsAccessor() {
8741        @Override
8742        public BaseBounds getGeomBounds(BaseBounds bounds, BaseTransform tx, Node node) {
8743            return node.getGeomBounds(bounds, tx);
8744        }
8745    };
8746
8747    /**
8748     * This method is used by Scene-graph JMX bean to obtain the Scene-graph structure.
8749     *
8750     * @param alg current algorithm to process this node
8751     * @param ctx current context
8752     * @return the algorithm specific result for this node
8753     * @treatAsPrivate implementation detail
8754     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
8755     */
8756    @Deprecated
8757    public abstract Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx);
8758
8759    static {
8760        // This is used by classes in different packages to get access to
8761        // private and package private methods.
8762        NodeHelper.setNodeAccessor(new NodeHelper.NodeAccessor() {
8763
8764            @Override
8765            public void layoutNodeForPrinting(Node node) {
8766                node.doCSSLayoutSyncForSnapshot();
8767            }
8768
8769            @Override
8770            public boolean isDerivedDepthTest(Node node) {
8771                return node.isDerivedDepthTest();
8772            }
8773
8774            @Override
8775            public SubScene getSubScene(Node node) {
8776                return node.getSubScene();
8777            }
8778        });
8779    }
8780}
8781