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