1 /*
   2  * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene;
  27 
  28 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  29 import javafx.beans.property.ObjectProperty;
  30 import javafx.beans.property.ReadOnlyBooleanProperty;
  31 import javafx.beans.property.ReadOnlyBooleanWrapper;
  32 import javafx.beans.property.SimpleObjectProperty;
  33 import javafx.beans.value.WritableValue;
  34 import javafx.collections.FXCollections;
  35 import javafx.collections.ListChangeListener.Change;
  36 import javafx.collections.ObservableList;
  37 import java.util.ArrayList;
  38 import java.util.HashSet;
  39 import java.util.List;
  40 import java.util.Set;
  41 
  42 import com.sun.javafx.util.TempState;
  43 import com.sun.javafx.util.Utils;
  44 import com.sun.javafx.collections.TrackableObservableList;
  45 import com.sun.javafx.collections.VetoableListDecorator;
  46 import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
  47 import javafx.css.Selector;
  48 import com.sun.javafx.css.StyleManager;
  49 import com.sun.javafx.geom.BaseBounds;
  50 import com.sun.javafx.geom.PickRay;
  51 import com.sun.javafx.geom.Point2D;
  52 import com.sun.javafx.geom.RectBounds;
  53 import com.sun.javafx.geom.transform.BaseTransform;
  54 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  55 import com.sun.javafx.jmx.MXNodeAlgorithm;
  56 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
  57 import com.sun.javafx.scene.CssFlags;
  58 import com.sun.javafx.scene.DirtyBits;
  59 import com.sun.javafx.scene.input.PickResultChooser;
  60 import com.sun.javafx.sg.prism.NGGroup;
  61 import com.sun.javafx.sg.prism.NGNode;
  62 import com.sun.javafx.tk.Toolkit;
  63 import com.sun.javafx.scene.LayoutFlags;
  64 import javafx.stage.Window;
  65 
  66 /**
  67  * The base class for all nodes that have children in the scene graph.
  68  * <p>
  69  * This class handles all hierarchical scene graph operations, including adding/removing
  70  * child nodes, marking branches dirty for layout and rendering, picking,
  71  * bounds calculations, and executing the layout pass on each pulse.
  72  * <p>
  73  * There are two direct concrete Parent subclasses
  74  * <ul>
  75  * <li>{@link Group} effects and transforms to be applied to a collection of child nodes.</li>
  76  * <li>{@link javafx.scene.layout.Region} class for nodes that can be styled with CSS and layout children. </li>
  77  * </ul>
  78  *
  79  * @since JavaFX 2.0
  80  */
  81 public abstract class Parent extends Node {
  82     // package private for testing
  83     static final int DIRTY_CHILDREN_THRESHOLD = 10;
  84 
  85     // If set to true, generate a warning message whenever adding a node to a
  86     // parent if it is currently a child of another parent.
  87     private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn");
  88 
  89     /**
  90      * Threshold when it's worth to populate list of removed children.
  91      */
  92     private static final int REMOVED_CHILDREN_THRESHOLD = 20;
  93 
  94     /**
  95      * Do not populate list of removed children when its number exceeds threshold,
  96      * but mark whole parent dirty.
  97      */
  98     private boolean removedChildrenOptimizationDisabled = false;
  99 
 100     /**
 101      * @treatAsPrivate implementation detail
 102      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 103      */
 104     @Deprecated
 105     @Override public void impl_updatePeer() {
 106         super.impl_updatePeer();
 107         final NGGroup peer = impl_getPeer();
 108 
 109         if (Utils.assertionEnabled()) {
 110             List<NGNode> pgnodes = peer.getChildren();
 111             if (pgnodes.size() != pgChildrenSize) {
 112                 java.lang.System.err.println("*** pgnodes.size() [" + pgnodes.size() + "] != pgChildrenSize [" + pgChildrenSize + "]");
 113             }
 114         }
 115 
 116         if (impl_isDirty(DirtyBits.PARENT_CHILDREN)) {
 117             // Whether a permutation, or children having been added or
 118             // removed, we'll want to clear out the PG side starting
 119             // from startIdx. We know that everything up to but not
 120             // including startIdx is identical between the FX and PG
 121             // sides, so we only need to update the remaining portion.
 122             peer.clearFrom(startIdx);
 123             for (int idx = startIdx; idx < children.size(); idx++) {
 124                 peer.add(idx, children.get(idx).impl_getPeer());
 125             }
 126             if (removedChildrenOptimizationDisabled) {
 127                 peer.markDirty();
 128                 removedChildrenOptimizationDisabled = false;
 129             } else {
 130                 if (removed != null && !removed.isEmpty()) {
 131                     for(int i = 0; i < removed.size(); i++) {
 132                         peer.addToRemoved(removed.get(i).impl_getPeer());
 133                     }
 134                 }
 135             }
 136             if (removed != null) {
 137                 removed.clear();
 138             }
 139             pgChildrenSize = children.size();
 140             startIdx = pgChildrenSize;
 141         }
 142 
 143         if (Utils.assertionEnabled()) validatePG();
 144     }
 145 
 146 
 147     /***********************************************************************
 148      *                        Scenegraph Structure                         *
 149      *                                                                     *
 150      *  Functions and variables related to the scenegraph structure,       *
 151      *  modifying the structure, and walking the structure.                *
 152      *                                                                     *
 153      **********************************************************************/
 154 
 155     // Used to check for duplicate nodes
 156     private final Set<Node> childSet = new HashSet<Node>();
 157 
 158     // starting child index from which we need to send the children to the PGGroup
 159     private int startIdx = 0;
 160 
 161     // double of children in the PGGroup as of the last update
 162     private int pgChildrenSize = 0;
 163 
 164     void validatePG() {
 165         boolean assertionFailed = false;
 166         final NGGroup peer = impl_getPeer();
 167         List<NGNode> pgnodes = peer.getChildren();
 168         if (pgnodes.size() != children.size()) {
 169             java.lang.System.err.println("*** pgnodes.size validatePG() [" + pgnodes.size() + "] != children.size() [" + children.size() + "]");
 170             assertionFailed = true;
 171         } else {
 172             for (int idx = 0; idx < children.size(); idx++) {
 173                 Node n = children.get(idx);
 174                 if (n.getParent() != this) {
 175                     java.lang.System.err.println("*** this=" + this + " validatePG children[" + idx + "].parent= " + n.getParent());
 176                     assertionFailed = true;
 177                 }
 178                 if (n.impl_getPeer() != pgnodes.get(idx)) {
 179                     java.lang.System.err.println("*** pgnodes[" + idx + "] validatePG != children[" + idx + "]");
 180                     assertionFailed = true;
 181                 }
 182             }
 183         }
 184         if (assertionFailed) {
 185             throw new java.lang.AssertionError("validation of PGGroup children failed");
 186         }
 187 
 188     }
 189 
 190     void printSeq(String prefix, List<Node> nodes) {
 191         String str = prefix;
 192         for (Node nn : nodes) {
 193             str += nn + " ";
 194         }
 195         System.out.println(str);
 196     }
 197 
 198     // Variable used to indicate that the change to the children ObservableList is
 199     // a simple permutation as the result of a toFront or toBack operation.
 200     // We can avoid almost all of the processing of the on replace trigger in
 201     // this case.
 202     private boolean childrenTriggerPermutation = false;
 203 
 204     //accumulates all removed nodes between pulses, for dirty area calculation.
 205     private List<Node> removed;
 206 
 207     /**
 208      * A ObservableList of child {@code Node}s.
 209      * <p>
 210      * See the class documentation for {@link Node} for scene graph structure
 211      * restrictions on setting a {@link Parent}'s children ObservableList.
 212      * If these restrictions are violated by a change to the children ObservableList,
 213      * the change is ignored and the previous value of the child ObservableList is
 214      * restored.
 215      *
 216      * {@code <p>Throws AssignToBoundException} if the same node
 217      * appears in two different bound ObservableList.
 218      *
 219      * @defaultValue empty
 220      */
 221 
 222     // set to true if either childRemoved or childAdded returns
 223     // true. These functions will indicate whether the geom
 224     // bounds for the parent have changed
 225     private boolean geomChanged;
 226     private boolean childSetModified;
 227     private final ObservableList<Node> children = new VetoableListDecorator<Node>(new TrackableObservableList<Node>() {
 228 
 229 
 230         protected void onChanged(Change<Node> c) {
 231             // proceed with updating the scene graph
 232             unmodifiableManagedChildren = null;
 233             boolean relayout = false;
 234             if (childSetModified) {
 235                 while (c.next()) {
 236                     int from = c.getFrom();
 237                     int to = c.getTo();
 238                     for (int i = from; i < to; ++i) {
 239                         Node n = children.get(i);
 240                         if (n.getParent() != null && n.getParent() != Parent.this) {
 241                             if (warnOnAutoMove) {
 242                                 java.lang.System.err.println("WARNING added to a new parent without first removing it from its current");
 243                                 java.lang.System.err.println("    parent. It will be automatically removed from its current parent.");
 244                                 java.lang.System.err.println("    node=" + n + " oldparent= " + n.getParent() + " newparent=" + this);
 245                             }
 246                             n.getParent().children.remove(n);
 247                             if (warnOnAutoMove) {
 248                                 Thread.dumpStack();
 249                             }
 250                         }
 251                     }
 252 
 253                     List<Node> removed = c.getRemoved();
 254                     int removedSize = removed.size();
 255                     for (int i = 0; i < removedSize; ++i) {
 256                         final Node n = removed.get(i);
 257                         if (n.isManaged()) {
 258                             relayout = true;
 259                         }
 260                     }
 261 
 262                     // update the parent and scene for each new node
 263                     for (int i = from; i < to; ++i) {
 264                         Node node = children.get(i);
 265                         if (node.isManaged() || (node instanceof Parent && ((Parent) node).layoutFlag != LayoutFlags.CLEAN)) {
 266                             relayout = true;
 267                         }
 268                         node.setParent(Parent.this);
 269                         node.setScenes(getScene(), getSubScene(), /* reapplyCSS*/ true);
 270                         // assert !node.boundsChanged;
 271                         if (node.isVisible()) {
 272                             geomChanged = true;
 273                             childIncluded(node);
 274                         }
 275                     }
 276                 }
 277 
 278                 // check to see if the number of children exceeds
 279                 // DIRTY_CHILDREN_THRESHOLD and dirtyChildren is null.
 280                 // If so, then we need to create dirtyChildren and
 281                 // populate it.
 282                 if (dirtyChildren == null && children.size() > DIRTY_CHILDREN_THRESHOLD) {
 283                     dirtyChildren
 284                             = new ArrayList<Node>(2 * DIRTY_CHILDREN_THRESHOLD);
 285                     // only bother populating children if geom has
 286                     // changed, otherwise there is no need
 287                     if (dirtyChildrenCount > 0) {
 288                         int size = children.size();
 289                         for (int i = 0; i < size; ++i) {
 290                             Node ch = children.get(i);
 291                             if (ch.isVisible() && ch.boundsChanged) {
 292                                 dirtyChildren.add(ch);
 293                             }
 294                         }
 295                     }
 296                 }
 297             } else {
 298                 // If childSet was not modified, we still need to check whether the permutation
 299                 // did change the layout
 300                 layout_loop:while (c.next()) {
 301                     List<Node> removed = c.getRemoved();
 302                     for (int i = 0, removedSize = removed.size(); i < removedSize; ++i) {
 303                         if (removed.get(i).isManaged()) {
 304                             relayout = true;
 305                             break layout_loop;
 306                         }
 307                     }
 308 
 309                     for (int i = c.getFrom(), to = c.getTo(); i < to; ++i) {
 310                         if (children.get(i).isManaged()) {
 311                             relayout = true;
 312                             break layout_loop;
 313                         }
 314                     }
 315                 }
 316             }
 317 
 318 
 319             //
 320             // Note that the styles of a child do not affect the parent or
 321             // its siblings. Thus, it is only necessary to reapply css to
 322             // the Node just added and not to this parent and all of its
 323             // children. So the following call to impl_reapplyCSS was moved
 324             // to Node.parentProperty. The original comment and code were
 325             // purposely left here as documentation should there be any
 326             // question about how the code used to work and why the change
 327             // was made.
 328             //
 329             // if children have changed then I need to reapply
 330             // CSS from this node on down
 331 //                impl_reapplyCSS();
 332             //
 333 
 334             // request layout if a Group subclass has overridden doLayout OR
 335             // if one of the new children needs layout, in which case need to ensure
 336             // the needsLayout flag is set all the way to the root so the next layout
 337             // pass will reach the child.
 338             if (relayout) {
 339                 requestLayout();
 340             }
 341 
 342             if (geomChanged) {
 343                 impl_geomChanged();
 344             }
 345 
 346             // Note the starting index at which we need to update the
 347             // PGGroup on the next update, and mark the children dirty
 348             c.reset();
 349             c.next();
 350             if (startIdx > c.getFrom()) {
 351                 startIdx = c.getFrom();
 352             }
 353 
 354             impl_markDirty(DirtyBits.PARENT_CHILDREN);
 355             // Force synchronization to include the handling of invisible node
 356             // so that removed list will get cleanup to prevent memory leak.
 357             impl_markDirty(DirtyBits.NODE_FORCE_SYNC);
 358         }
 359 
 360     }) {
 361         @Override
 362         protected void onProposedChange(final List<Node> newNodes, int[] toBeRemoved) {
 363             final Scene scene = getScene();
 364             if (scene != null) {
 365                 Window w = scene.getWindow();
 366                 if (w != null && w.impl_getPeer() != null) {
 367                     Toolkit.getToolkit().checkFxUserThread();
 368                 }
 369             }
 370             geomChanged = false;
 371 
 372             long newLength = children.size() + newNodes.size();
 373             int removedLength = 0;
 374             for (int i = 0; i < toBeRemoved.length; i += 2) {
 375                 removedLength += toBeRemoved[i + 1] - toBeRemoved[i];
 376             }
 377             newLength -= removedLength;
 378 
 379             // If the childrenTriggerPermutation flag is set, then we know it
 380             // is a simple permutation and no further checking is needed.
 381             if (childrenTriggerPermutation) {
 382                 childSetModified = false;
 383                 return;
 384             }
 385 
 386             // If the childrenTriggerPermutation flag is not set, then we will
 387             // check to see whether any element in the ObservableList has changed,
 388             // or whether the new ObservableList is a permutation on the existing
 389             // ObservableList. Note that even if the childrenModified flag is false,
 390             // we still have to check for duplicates. If it is a simple
 391             // permutation, we can avoid checking for cycles or other parents.
 392             childSetModified = true;
 393             if (newLength == childSet.size()) {
 394                 childSetModified = false;
 395                 for (int i = newNodes.size() - 1; i >= 0; --i ) {
 396                     Node n = newNodes.get(i);
 397                     if (!childSet.contains(n)) {
 398                         childSetModified = true;
 399                         break;
 400                     }
 401                 }
 402             }
 403 
 404             // Enforce scene graph invariants, and check for structural errors.
 405             //
 406             // 1. If a child has been added to this parent more than once,
 407             // then it is an error
 408             //
 409             // 2. If a child is a target of a clip, then it is an error.
 410             //
 411             // 3. If a node would cause a cycle, then it is an error.
 412             //
 413             // 4. If a node is null
 414             //
 415             // Note that if a node is the child of another parent, we will
 416             // implicitly remove the node from its former Parent after first
 417             // checking for errors.
 418 
 419             // iterate over the nodes that were removed and remove them from
 420             // the hash set.
 421             for (int i = 0; i < toBeRemoved.length; i += 2) {
 422                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 423                     childSet.remove(children.get(j));
 424                 }
 425             }
 426 
 427             try {
 428                 if (childSetModified) {
 429                     // check individual children before duplication test
 430                     // if done in this order, the exception is more specific
 431                     for (int i = newNodes.size() - 1; i >= 0; --i ) {
 432                         Node node = newNodes.get(i);
 433                         if (node == null) {
 434                             throw new NullPointerException(
 435                                     constructExceptionMessage(
 436                                         "child node is null", null));
 437                         }
 438                         if (node.getClipParent() != null) {
 439                             throw new IllegalArgumentException(
 440                                     constructExceptionMessage(
 441                                         "node already used as a clip", node));
 442                         }
 443                         if (wouldCreateCycle(Parent.this, node)) {
 444                             throw new IllegalArgumentException(
 445                                     constructExceptionMessage(
 446                                         "cycle detected", node));
 447                         }
 448                     }
 449                 }
 450 
 451                 childSet.addAll(newNodes);
 452                 if (childSet.size() != newLength) {
 453                     throw new IllegalArgumentException(
 454                             constructExceptionMessage(
 455                                 "duplicate children added", null));
 456                 }
 457             } catch (RuntimeException e) {
 458                 //Return children to it's original state
 459                 childSet.clear();
 460                 childSet.addAll(children);
 461 
 462                 // rethrow
 463                 throw e;
 464             }
 465 
 466             // Done with error checking
 467 
 468             if (!childSetModified) {
 469                 return;
 470             }
 471 
 472             // iterate over the nodes that were removed and clear their
 473             // parent and scene. Add to them also to removed list for further
 474             // dirty regions calculation.
 475             if (removed == null) {
 476                 removed = new ArrayList<Node>();
 477             }
 478             if (removed.size() + removedLength > REMOVED_CHILDREN_THRESHOLD || !impl_isTreeVisible()) {
 479                 //do not populate too many children in removed list
 480                 removedChildrenOptimizationDisabled = true;
 481             }
 482             for (int i = 0; i < toBeRemoved.length; i += 2) {
 483                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 484                     Node old = children.get(j);
 485                     final Scene oldScene = old.getScene();
 486                     if (oldScene != null) {
 487                         oldScene.generateMouseExited(old);
 488                     }
 489                     if (dirtyChildren != null) {
 490                         dirtyChildren.remove(old);
 491                     }
 492                     if (old.isVisible()) {
 493                         geomChanged = true;
 494                         childExcluded(old);
 495                     }
 496                     if (old.getParent() == Parent.this) {
 497                         old.setParent(null);
 498                         old.setScenes(null, null, /* reapplyCSS*/ false);
 499                     }
 500                     // Do not add node with null scene to the removed list.
 501                     // It will not be processed in the list and its memory
 502                     // will not be freed.
 503                     if (scene != null && !removedChildrenOptimizationDisabled) {
 504                         removed.add(old);
 505                     }
 506                 }
 507             }
 508         }
 509 
 510         private String constructExceptionMessage(
 511                 String cause, Node offendingNode) {
 512             final StringBuilder sb = new StringBuilder("Children: ");
 513             sb.append(cause);
 514             sb.append(": parent = ").append(Parent.this);
 515             if (offendingNode != null) {
 516                 sb.append(", node = ").append(offendingNode);
 517             }
 518 
 519             return sb.toString();
 520         }
 521     };
 522 
 523     /**
 524      * A constant reference to an unmodifiable view of the children, such that every time
 525      * we ask for an unmodifiable list of children, we don't actually create a new
 526      * collection and return it. The memory overhead is pretty lightweight compared
 527      * to all the garbage we would otherwise generate.
 528      */
 529     private final ObservableList<Node> unmodifiableChildren =
 530             FXCollections.unmodifiableObservableList(children);
 531 
 532     /**
 533      * A cached reference to the unmodifiable managed children of this Parent. This is
 534      * created whenever first asked for, and thrown away whenever children are added
 535      * or removed or when their managed state changes. This could be written
 536      * differently, such that this list is essentially a filtered copy of the
 537      * main children, but that additional overhead might not be worth it.
 538      */
 539     private List<Node> unmodifiableManagedChildren = null;
 540 
 541     /**
 542      * Gets the list of children of this {@code Parent}.
 543      *
 544      * <p>
 545      * See the class documentation for {@link Node} for scene graph structure
 546      * restrictions on setting a {@link Parent}'s children list.
 547      * If these restrictions are violated by a change to the list of children,
 548      * the change is ignored and the previous value of the children list is
 549      * restored. An {@link IllegalArgumentException} is thrown in this case.
 550      *
 551      * <p>
 552      * If this {@link Parent} node is attached to a {@link Scene} attached to a {@link Window}
 553      * that is showning ({@link javafx.stage.Window#isShowing()}), then its
 554      * list of children must only be modified on the JavaFX Application Thread.
 555      * An {@link IllegalStateException} is thrown if this restriction is
 556      * violated.
 557      *
 558      * <p>
 559      * Note to subclasses: if you override this method, you must return from
 560      * your implementation the result of calling this super method. The actual
 561      * list instance returned from any getChildren() implementation must be
 562      * the list owned and managed by this Parent. The only typical purpose
 563      * for overriding this method is to promote the method to be public.
 564      *
 565      * @return the list of children of this {@code Parent}.
 566      */
 567     protected ObservableList<Node> getChildren() {
 568         return children;
 569     }
 570 
 571     /**
 572      * Gets the list of children of this {@code Parent} as a read-only
 573      * list.
 574      *
 575      * @return read-only access to this parent's children ObservableList
 576      */
 577     @ReturnsUnmodifiableCollection
 578     public ObservableList<Node> getChildrenUnmodifiable() {
 579         return unmodifiableChildren;
 580     }
 581 
 582     /**
 583      * Gets the list of all managed children of this {@code Parent}.
 584      *
 585      * @param <E> the type of the children nodes
 586      * @return list of all managed children in this parent
 587      */
 588     @ReturnsUnmodifiableCollection
 589     protected <E extends Node> List<E> getManagedChildren() {
 590         if (unmodifiableManagedChildren == null) {
 591             unmodifiableManagedChildren = new ArrayList<Node>();
 592             for (int i=0, max=children.size(); i<max; i++) {
 593                 Node e = children.get(i);
 594                 if (e.isManaged()) {
 595                     unmodifiableManagedChildren.add(e);
 596                 }
 597             }
 598         }
 599         return (List<E>)unmodifiableManagedChildren;
 600     }
 601 
 602     /**
 603      * Called by Node whenever its managed state may have changed, this
 604      * method will cause the view of managed children to be updated
 605      * such that it properly includes or excludes this child.
 606      */
 607     final void managedChildChanged() {
 608         requestLayout();
 609         unmodifiableManagedChildren = null;
 610     }
 611 
 612     // implementation of Node.toFront function
 613     final void impl_toFront(Node node) {
 614         if (Utils.assertionEnabled()) {
 615             if (!childSet.contains(node)) {
 616                 throw new java.lang.AssertionError(
 617                         "specified node is not in the list of children");
 618             }
 619         }
 620 
 621         if (children.get(children.size() - 1) != node) {
 622             childrenTriggerPermutation = true;
 623             try {
 624                 children.remove(node);
 625                 children.add(node);
 626             } finally {
 627                 childrenTriggerPermutation = false;
 628             }
 629         }
 630     }
 631 
 632     // implementation of Node.toBack function
 633     final void impl_toBack(Node node) {
 634         if (Utils.assertionEnabled()) {
 635             if (!childSet.contains(node)) {
 636                 throw new java.lang.AssertionError(
 637                         "specified node is not in the list of children");
 638             }
 639         }
 640 
 641         if (children.get(0) != node) {
 642             childrenTriggerPermutation = true;
 643             try {
 644                 children.remove(node);
 645                 children.add(0, node);
 646             } finally {
 647                 childrenTriggerPermutation = false;
 648             }
 649         }
 650     }
 651 
 652     @Override
 653     void scenesChanged(final Scene newScene, final SubScene newSubScene,
 654                        final Scene oldScene, final SubScene oldSubScene) {
 655 
 656         if (oldScene != null && newScene == null) {
 657             // RT-34863 - clean up CSS cache when Parent is removed from scene-graph
 658             StyleManager.getInstance().forget(this);
 659 
 660             // Clear removed list on parent who is no longer in a scene
 661             if (removed != null) {
 662                 removed.clear();
 663             }
 664         }
 665 
 666         for (int i=0; i<children.size(); i++) {
 667             children.get(i).setScenes(newScene, newSubScene, /* reapplyCSS*/ false);
 668         }
 669 
 670         final boolean awaitingLayout = layoutFlag != LayoutFlags.CLEAN;
 671 
 672         sceneRoot = (newSubScene != null && newSubScene.getRoot() == this) ||
 673                     (newScene != null && newScene.getRoot() == this);
 674         layoutRoot = !isManaged() || sceneRoot;
 675 
 676 
 677         if (awaitingLayout) {
 678             // If this node is dirty and the new scene or subScene is not null
 679             // then add this node to the new scene's dirty list
 680             if (newScene != null && layoutRoot) {
 681                 if (newSubScene != null) {
 682                     newSubScene.setDirtyLayout(this);
 683                 }
 684             }
 685         }
 686     }
 687 
 688     @Override
 689     void setDerivedDepthTest(boolean value) {
 690         super.setDerivedDepthTest(value);
 691 
 692         for (int i=0, max=children.size(); i<max; i++) {
 693             final Node node = children.get(i);
 694             node.computeDerivedDepthTest();
 695         }
 696     }
 697 
 698     /**
 699      * @treatAsPrivate implementation detail
 700      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 701      */
 702     @Deprecated
 703     @Override protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) {
 704 
 705         double boundsDistance = impl_intersectsBounds(pickRay);
 706 
 707         if (!Double.isNaN(boundsDistance)) {
 708             for (int i = children.size()-1; i >= 0; i--) {
 709                 children.get(i).impl_pickNode(pickRay, result);
 710                 if (result.isClosed()) {
 711                     return;
 712                 }
 713             }
 714 
 715             if (isPickOnBounds()) {
 716                 result.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance));
 717             }
 718         }
 719     }
 720 
 721     @Override boolean isConnected() {
 722         return super.isConnected() || sceneRoot;
 723     }
 724 
 725     @Override public Node lookup(String selector) {
 726         Node n = super.lookup(selector);
 727         if (n == null) {
 728             for (int i=0, max=children.size(); i<max; i++) {
 729                 final Node node = children.get(i);
 730                 n = node.lookup(selector);
 731                 if (n != null) return n;
 732             }
 733         }
 734         return n;
 735     }
 736 
 737     /**
 738      * Please Note: This method should never create the results set,
 739      * let the Node class implementation do this!
 740      */
 741     @Override List<Node> lookupAll(Selector selector, List<Node> results) {
 742         results = super.lookupAll(selector, results);
 743         for (int i=0, max=children.size(); i<max; i++) {
 744             final Node node = children.get(i);
 745             results = node.lookupAll(selector, results);
 746         }
 747         return results;
 748     }
 749 
 750     /** @treatAsPrivate implementation detail */
 751     private javafx.beans.property.ObjectProperty<ParentTraversalEngine> impl_traversalEngine;
 752 
 753     /**
 754      * @treatAsPrivate implementation detail
 755      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 756      */
 757     // SB-dependency: RT-21209 has been filed to track this
 758     @Deprecated
 759     public final void setImpl_traversalEngine(ParentTraversalEngine value) {
 760         impl_traversalEngineProperty().set(value);
 761     }
 762 
 763     /**
 764      * @treatAsPrivate implementation detail
 765      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 766      */
 767     @Deprecated
 768     public final ParentTraversalEngine getImpl_traversalEngine() {
 769         return impl_traversalEngine == null ? null : impl_traversalEngine.get();
 770     }
 771 
 772     /**
 773      * @treatAsPrivate implementation detail
 774      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 775      */
 776     @Deprecated
 777     public final ObjectProperty<ParentTraversalEngine> impl_traversalEngineProperty() {
 778         if (impl_traversalEngine == null) {
 779             impl_traversalEngine =
 780                     new SimpleObjectProperty<>(
 781                             this, "impl_traversalEngine");
 782         }
 783         return impl_traversalEngine;
 784     }
 785 
 786     /***********************************************************************
 787      *                               Layout                                *
 788      *                                                                     *
 789      *  Functions and variables related to the layout scheme used by       *
 790      *  JavaFX. Includes both public and private API.                      *
 791      *                                                                     *
 792      **********************************************************************/
 793     /**
 794      * Indicates that this Node and its subnodes requires a layout pass on
 795      * the next pulse.
 796      */
 797     private ReadOnlyBooleanWrapper needsLayout;
 798     LayoutFlags layoutFlag = LayoutFlags.CLEAN;
 799 
 800     protected final void setNeedsLayout(boolean value) {
 801         if (value) {
 802             markDirtyLayout(true, false);
 803         } else if (layoutFlag == LayoutFlags.NEEDS_LAYOUT) {
 804             boolean hasBranch = false;
 805             for (int i = 0, max = children.size(); i < max; i++) {
 806                 final Node child = children.get(i);
 807                 if (child instanceof Parent) {
 808                     if (((Parent)child).layoutFlag != LayoutFlags.CLEAN) {
 809                         hasBranch = true;
 810                         break;
 811                     }
 812 
 813                 }
 814             }
 815             setLayoutFlag(hasBranch ? LayoutFlags.DIRTY_BRANCH : LayoutFlags.CLEAN);
 816         }
 817     }
 818 
 819     public final boolean isNeedsLayout() {
 820         return layoutFlag == LayoutFlags.NEEDS_LAYOUT;
 821     }
 822 
 823     public final ReadOnlyBooleanProperty needsLayoutProperty() {
 824         if (needsLayout == null) {
 825             needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", layoutFlag == LayoutFlags.NEEDS_LAYOUT);
 826         }
 827         return needsLayout;
 828     }
 829 
 830     /**
 831      * This is used only by CCS in Node. It is set to true while
 832      * the layout() function is processing and set to false on the conclusion.
 833      * It is used by the Node to decide whether to perform CSS updates
 834      * synchronously or asynchronously.
 835      */
 836     private boolean performingLayout = false;
 837 
 838     boolean isPerformingLayout() {
 839         return performingLayout;
 840     }
 841 
 842     private boolean sizeCacheClear = true;
 843     private double prefWidthCache = -1;
 844     private double prefHeightCache = -1;
 845     private double minWidthCache = -1;
 846     private double minHeightCache = -1;
 847 
 848     void setLayoutFlag(LayoutFlags flag) {
 849         if (needsLayout != null) {
 850             needsLayout.set(flag == LayoutFlags.NEEDS_LAYOUT);
 851         }
 852         layoutFlag = flag;
 853     }
 854 
 855     private void markDirtyLayout(boolean local, boolean forceParentLayout) {
 856         setLayoutFlag(LayoutFlags.NEEDS_LAYOUT);
 857         if (local || layoutRoot) {
 858             if (sceneRoot) {
 859                 Toolkit.getToolkit().requestNextPulse();
 860                 if (getSubScene() != null) {
 861                     getSubScene().setDirtyLayout(this);
 862                 }
 863             } else {
 864                 markDirtyLayoutBranch();
 865             }
 866         } else {
 867             requestParentLayout(forceParentLayout);
 868         }
 869     }
 870 
 871     /**
 872      * Requests a layout pass to be performed before the next scene is
 873      * rendered. This is batched up asynchronously to happen once per
 874      * "pulse", or frame of animation.
 875      * <p>
 876      * If this parent is either a layout root or unmanaged, then it will be
 877      * added directly to the scene's dirty layout list, otherwise requestParentLayout
 878      * will be invoked.
 879      * @since JavaFX 8.0
 880      */
 881     public void requestLayout() {
 882         requestLayout(false);
 883     }
 884 
 885     /**
 886      * A package scope method used by Node and serves as a helper method for
 887      * requestLayout() (see above). If forceParentLayout is true it will
 888      * propagate this force layout flag to its parent.
 889      */
 890     void requestLayout(boolean forceParentLayout) {
 891         clearSizeCache();
 892         markDirtyLayout(false, forceParentLayout);
 893     }
 894 
 895     /**
 896      * Requests a layout pass of the parent to be performed before the next scene is
 897      * rendered. This is batched up asynchronously to happen once per
 898      * "pulse", or frame of animation.
 899      * <p>
 900      * This may be used when the current parent have changed it's min/max/preferred width/height,
 901      * but doesn't know yet if the change will lead to it's actual size change. This will be determined
 902      * when it's parent recomputes the layout with the new hints.
 903      */
 904     protected final void requestParentLayout() {
 905        requestParentLayout(false);
 906     }
 907 
 908     /**
 909      * A package scope method used by Node and serves as a helper method for
 910      * requestParentLayout() (see above). If forceParentLayout is true it will
 911      * force a request layout call on its parent if its parent is not null.
 912      */
 913     void requestParentLayout(boolean forceParentLayout) {
 914         if (!layoutRoot) {
 915             final Parent p = getParent();
 916             if (p != null && (!p.performingLayout || forceParentLayout)) {
 917                 p.requestLayout();
 918             }
 919         }
 920     }
 921 
 922     void clearSizeCache() {
 923         if (sizeCacheClear) {
 924             return;
 925         }
 926         sizeCacheClear = true;
 927         prefWidthCache = -1;
 928         prefHeightCache = -1;
 929         minWidthCache = -1;
 930         minHeightCache = -1;
 931     }
 932 
 933     @Override public double prefWidth(double height) {
 934         if (height == -1) {
 935             if (prefWidthCache == -1) {
 936                 prefWidthCache = computePrefWidth(-1);
 937                 if (Double.isNaN(prefWidthCache) || prefWidthCache < 0) prefWidthCache = 0;
 938                 sizeCacheClear = false;
 939             }
 940             return prefWidthCache;
 941         } else {
 942             double result = computePrefWidth(height);
 943             return Double.isNaN(result) || result < 0 ? 0 : result;
 944         }
 945     }
 946 
 947     @Override public double prefHeight(double width) {
 948         if (width == -1) {
 949             if (prefHeightCache == -1) {
 950                 prefHeightCache = computePrefHeight(-1);
 951                 if (Double.isNaN(prefHeightCache) || prefHeightCache < 0) prefHeightCache = 0;
 952                 sizeCacheClear = false;
 953             }
 954             return prefHeightCache;
 955         } else {
 956             double result = computePrefHeight(width);
 957             return Double.isNaN(result) || result < 0 ? 0 : result;
 958         }
 959     }
 960 
 961     @Override public double minWidth(double height) {
 962         if (height == -1) {
 963             if (minWidthCache == -1) {
 964                 minWidthCache = computeMinWidth(-1);
 965                 if (Double.isNaN(minWidthCache) || minWidthCache < 0) minWidthCache = 0;
 966                 sizeCacheClear = false;
 967             }
 968             return minWidthCache;
 969         } else {
 970             double result = computeMinWidth(height);
 971             return Double.isNaN(result) || result < 0 ? 0 : result;
 972         }
 973     }
 974 
 975     @Override public double minHeight(double width) {
 976         if (width == -1) {
 977             if (minHeightCache == -1) {
 978                 minHeightCache = computeMinHeight(-1);
 979                 if (Double.isNaN(minHeightCache) || minHeightCache < 0) minHeightCache = 0;
 980                 sizeCacheClear = false;
 981             }
 982             return minHeightCache;
 983         } else {
 984             double result = computeMinHeight(width);
 985             return Double.isNaN(result) || result < 0 ? 0 : result;
 986         }
 987     }
 988 
 989     // PENDING_DOC_REVIEW
 990     /**
 991      * Calculates the preferred width of this {@code Parent}. The default
 992      * implementation calculates this width as the width of the area occupied
 993      * by its managed children when they are positioned at their
 994      * current positions at their preferred widths.
 995      *
 996      * @param height the height that should be used if preferred width depends
 997      *      on it
 998      * @return the calculated preferred width
 999      */
1000     protected double computePrefWidth(double height) {
1001         double minX = 0;
1002         double maxX = 0;
1003         for (int i=0, max=children.size(); i<max; i++) {
1004             Node node = children.get(i);
1005             if (node.isManaged()) {
1006                 final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
1007                 minX = Math.min(minX, x);
1008                 maxX = Math.max(maxX, x + boundedSize(node.prefWidth(-1), node.minWidth(-1), node.maxWidth(-1)));
1009             }
1010         }
1011         return maxX - minX;
1012     }
1013 
1014     // PENDING_DOC_REVIEW
1015     /**
1016      * Calculates the preferred height of this {@code Parent}. The default
1017      * implementation calculates this height as the height of the area occupied
1018      * by its managed children when they are positioned at their current
1019      * positions at their preferred heights.
1020      *
1021      * @param width the width that should be used if preferred height depends
1022      *      on it
1023      * @return the calculated preferred height
1024      */
1025     protected double computePrefHeight(double width) {
1026         double minY = 0;
1027         double maxY = 0;
1028         for (int i=0, max=children.size(); i<max; i++) {
1029             Node node = children.get(i);
1030             if (node.isManaged()) {
1031                 final double y = node.getLayoutBounds().getMinY() + node.getLayoutY();
1032                 minY = Math.min(minY, y);
1033                 maxY = Math.max(maxY, y + boundedSize(node.prefHeight(-1), node.minHeight(-1), node.maxHeight(-1)));
1034             }
1035         }
1036         return maxY - minY;
1037     }
1038 
1039     /**
1040      * Calculates the minimum width of this {@code Parent}. The default
1041      * implementation simply returns the pref width.
1042      *
1043      * @param height the height that should be used if min width depends
1044      *      on it
1045      * @return the calculated min width
1046      * @since JavaFX 2.1
1047      */
1048     protected double computeMinWidth(double height) {
1049         return prefWidth(height);
1050     }
1051 
1052     // PENDING_DOC_REVIEW
1053     /**
1054      * Calculates the min height of this {@code Parent}. The default
1055      * implementation simply returns the pref height;
1056      *
1057      * @param width the width that should be used if min height depends
1058      *      on it
1059      * @return the calculated min height
1060      * @since JavaFX 2.1
1061      */
1062     protected double computeMinHeight(double width) {
1063         return prefHeight(width);
1064     }
1065 
1066     /**
1067      * Calculates the baseline offset based on the first managed child. If there
1068      * is no such child, returns {@link Node#getBaselineOffset()}.
1069      *
1070      * @return baseline offset
1071      */
1072     @Override public double getBaselineOffset() {
1073         for (int i=0, max=children.size(); i<max; i++) {
1074             final Node child = children.get(i);
1075             if (child.isManaged()) {
1076                 double offset = child.getBaselineOffset();
1077                 if (offset == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1078                     continue;
1079                 }
1080                 return child.getLayoutBounds().getMinY() + child.getLayoutY() + offset;
1081             }
1082         }
1083         return super.getBaselineOffset();
1084     }
1085 
1086     /***
1087      * It stores the reference to the current child being laid out by its parent.
1088      * This reference is important to differentiate whether a layout is triggered
1089      * by its parent or other events.
1090      */
1091     private Node currentLayoutChild = null;
1092 
1093     boolean isCurrentLayoutChild(Node node) {
1094         return node == currentLayoutChild;
1095     }
1096 
1097     /**
1098      * Executes a top-down layout pass on the scene graph under this parent.
1099      *
1100      * Calling this method while the Parent is doing layout is a no-op.
1101      */
1102     public final void layout() {
1103         // layoutFlag can be accessed or changed during layout processing.
1104         // Hence we need to cache and reset it before performing layout.
1105         LayoutFlags flag = layoutFlag;
1106         setLayoutFlag(LayoutFlags.CLEAN);
1107         switch(flag) {
1108             case CLEAN:
1109                 break;
1110             case NEEDS_LAYOUT:
1111                 if (performingLayout) {
1112                     /* This code is here mainly to avoid infinite loops as layout() is public and the call might be (indirectly) invoked accidentally
1113                      * while doing the layout.
1114                      * One example might be an invocation from Group layout bounds recalculation
1115                      *  (e.g. during the localToScene/localToParent calculation).
1116                      * The layout bounds will thus return layout bounds that are "old" (i.e. before the layout changes, that are just being done),
1117                      * which is likely what the code would expect.
1118                      * The changes will invalidate the layout bounds again however, so the layout bounds query after layout pass will return correct answer.
1119                      */
1120                     break;
1121                 }
1122                 performingLayout = true;
1123                 layoutChildren();
1124                 // Intended fall-through
1125             case DIRTY_BRANCH:
1126                 for (int i = 0, max = children.size(); i < max; i++) {
1127                     final Node child = children.get(i);
1128                     currentLayoutChild = child;
1129                     if (child instanceof Parent) {
1130                         ((Parent)child).layout();
1131                     } else if (child instanceof SubScene) {
1132                         ((SubScene)child).layoutPass();
1133                     }
1134                 }
1135                 currentLayoutChild = null;
1136                 performingLayout = false;
1137                 break;
1138         }
1139     }
1140 
1141     /**
1142      * Invoked during the layout pass to layout the children in this
1143      * {@code Parent}. By default it will only set the size of managed,
1144      * resizable content to their preferred sizes and does not do any node
1145      * positioning.
1146      * <p>
1147      * Subclasses should override this function to layout content as needed.
1148      */
1149     protected void layoutChildren() {
1150         for (int i=0, max=children.size(); i<max; i++) {
1151             final Node node = children.get(i);
1152             currentLayoutChild = node;
1153             if (node.isResizable() && node.isManaged()) {
1154                 node.autosize();
1155             }
1156         }
1157         currentLayoutChild = null;
1158     }
1159 
1160     /**
1161      * This field is managed by the Scene, and set on any node which is the
1162      * root of a Scene.
1163      */
1164     private boolean sceneRoot = false;
1165 
1166     /**
1167      * Keeps track of whether this node is a layout root. This is updated
1168      * whenever the sceneRoot field changes, or whenever the managed
1169      * property changes.
1170      */
1171     boolean layoutRoot = false;
1172     @Override final void notifyManagedChanged() {
1173         layoutRoot = !isManaged() || sceneRoot;
1174     }
1175 
1176     final boolean isSceneRoot() {
1177         return sceneRoot;
1178     }
1179 
1180     /***********************************************************************
1181      *                                                                     *
1182      *                         Stylesheet Handling                         *
1183      *                                                                     *
1184      **********************************************************************/
1185 
1186 
1187     /**
1188      * A ObservableList of string URLs linking to the stylesheets to use with this scene's
1189      * contents. For additional information about using CSS with the
1190      * scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1191      * Guide</a>.
1192      */
1193     private final ObservableList<String> stylesheets = new TrackableObservableList<String>() {
1194         @Override
1195         protected void onChanged(Change<String> c) {
1196             final Scene scene = getScene();
1197             if (scene != null) {
1198 
1199                 // Notify the StyleManager if stylesheets change. This Parent's
1200                 // styleManager will get recreated in impl_processCSS.
1201                 StyleManager.getInstance().stylesheetsChanged(Parent.this, c);
1202 
1203                 // RT-9784 - if stylesheet is removed, reset styled properties to
1204                 // their initial value.
1205                 c.reset();
1206                 while(c.next()) {
1207                     if (c.wasRemoved() == false) {
1208                         continue;
1209                     }
1210                     break; // no point in resetting more than once...
1211                 }
1212 
1213                 impl_reapplyCSS();
1214             }
1215         }
1216     };
1217 
1218     /**
1219      * Gets an observable list of string URLs linking to the stylesheets to use
1220      * with this Parent's contents. See {@link Scene#getStylesheets()} for details.
1221      * <p>For additional information about using CSS
1222      * with the scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1223      * Guide</a>.</p>
1224      *
1225      * @return the list of stylesheets to use with this Parent
1226      * @since JavaFX 2.1
1227      */
1228     public final ObservableList<String> getStylesheets() { return stylesheets; }
1229 
1230     /**
1231      * This method recurses up the parent chain until parent is null. As the
1232      * stack unwinds, if the Parent has stylesheets, they are added to the
1233      * list.
1234      *
1235      * It is possible to override this method to stop the recursion. This allows
1236      * a Parent to have a set of stylesheets distinct from its Parent.
1237      *
1238      * @treatAsPrivate implementation detail
1239      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1240      */
1241     @Deprecated // SB-dependency: RT-21247 has been filed to track this
1242     public /* Do not make this final! */ List<String> impl_getAllParentStylesheets() {
1243 
1244         List<String> list = null;
1245         final Parent myParent = getParent();
1246         if (myParent != null) {
1247 
1248             //
1249             // recurse so that stylesheets of Parents closest to the root are
1250             // added to the list first. The ensures that declarations for
1251             // stylesheets further down the tree (closer to the leaf) have
1252             // a higer ordinal in the cascade.
1253             //
1254             list = myParent.impl_getAllParentStylesheets();
1255         }
1256 
1257         if (stylesheets != null && stylesheets.isEmpty() == false) {
1258             if (list == null) {
1259                 list = new ArrayList<String>(stylesheets.size());
1260             }
1261             for (int n=0,nMax=stylesheets.size(); n<nMax; n++) {
1262                 list.add(stylesheets.get(n));
1263             }
1264         }
1265 
1266         return list;
1267 
1268     }
1269 
1270     /**
1271      * @treatAsPrivate implementation detail
1272      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1273      */
1274     @Deprecated
1275     @Override protected void impl_processCSS(WritableValue<Boolean> unused) {
1276 
1277         // Nothing to do...
1278         if (cssFlag == CssFlags.CLEAN) return;
1279 
1280         // RT-29254 - If DIRTY_BRANCH, pass control to Node#processCSS. This avoids calling impl_processCSS on
1281         // this node and all of its children when css doesn't need updated, recalculated, or reapplied.
1282         if (cssFlag == CssFlags.DIRTY_BRANCH) {
1283             super.processCSS();
1284             return;
1285         }
1286 
1287         // Let the super implementation handle CSS for this node
1288         super.impl_processCSS(unused);
1289 
1290         // avoid the following call to children.toArray if there are no children
1291         if (children.isEmpty()) return;
1292 
1293         //
1294         // RT-33103
1295         //
1296         // It is possible for a child to be removed from children in the middle of
1297         // the following loop. Iterating over the children may result in an IndexOutOfBoundsException.
1298         // So a copy is made and the copy is iterated over.
1299         //
1300         // Note that we don't want the fail-fast feature of an iterator, not to mention the general iterator overhead.
1301         //
1302         final Node[] childArray = children.toArray(new Node[children.size()]);
1303 
1304         // For each child, process CSS
1305         for (int i=0; i<childArray.length; i++) {
1306 
1307             final Node child = childArray[i];
1308 
1309             //  If a child no longer has this as its parent, then it is skipped.
1310             final Parent childParent = child.getParent();
1311             if (childParent == null || childParent != this) continue;
1312 
1313             // If the parent styles are being updated, recalculated or
1314             // reapplied, then make sure the children get the same treatment.
1315             // Unless the child is already more dirty than this parent (RT-29074).
1316             if(CssFlags.UPDATE.compareTo(child.cssFlag) > 0) {
1317                 child.cssFlag = CssFlags.UPDATE;
1318             }
1319             child.impl_processCSS(unused);
1320         }
1321     }
1322 
1323     /***********************************************************************
1324      *                               Misc                                  *
1325      *                                                                     *
1326      *  Initialization and other functions                                 *
1327      *                                                                     *
1328      **********************************************************************/
1329 
1330 
1331     /**
1332      * Constructs a new {@code Parent}.
1333      */
1334     protected Parent() {
1335         layoutFlag = LayoutFlags.NEEDS_LAYOUT;
1336         setAccessibleRole(AccessibleRole.PARENT);
1337     }
1338 
1339     /**
1340      * @treatAsPrivate implementation detail
1341      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1342      */
1343     @Deprecated
1344     @Override protected NGNode impl_createPeer() {
1345         return new NGGroup();
1346     }
1347 
1348     @Override
1349     void nodeResolvedOrientationChanged() {
1350         for (int i = 0, max = children.size(); i < max; ++i) {
1351             children.get(i).parentResolvedOrientationInvalidated();
1352         }
1353     }
1354 
1355     /***************************************************************************
1356      *                                                                         *
1357      *                         Bounds Computations                             *
1358      *                                                                         *
1359      *  This code originated in GroupBoundsHelper (part of javafx-sg-common)   *
1360      *  but has been ported here to the FX side since we cannot rely on the PG *
1361      *  side for computing the bounds (due to the decoupling of the two        *
1362      *  scenegraphs for threading and other purposes).                         *
1363      *                                                                         *
1364      *  Unfortunately, we cannot simply reuse GroupBoundsHelper without some  *
1365      *  major (and hacky) modification due to the fact that GroupBoundsHelper  *
1366      *  relies on PG state and we need to do similar things here that rely on  *
1367      *  core scenegraph state. Unfortunately, that means we made a port.       *
1368      *                                                                         *
1369      **************************************************************************/
1370 
1371     private BaseBounds tmp = new RectBounds();
1372 
1373     /**
1374      * The cached bounds for the Group. If the cachedBounds are invalid
1375      * then we have no history of what the bounds are, or were.
1376      */
1377     private BaseBounds cachedBounds = new RectBounds();
1378 
1379     /**
1380      * Indicates that the cachedBounds is invalid (or old) and need to be recomputed.
1381      * If cachedBoundsInvalid is true and dirtyChildrenCount is non-zero,
1382      * then when we recompute the cachedBounds we can consider the
1383      * values in cachedBounds to represent the last valid bounds for the group.
1384      * This is useful for several fast paths.
1385      */
1386     private boolean cachedBoundsInvalid;
1387 
1388     /**
1389      * The number of dirty children which bounds haven't been incorporated
1390      * into the cached bounds yet. Can be used even when dirtyChildren is null.
1391      */
1392     private int dirtyChildrenCount;
1393 
1394     /**
1395      * This set is used to track all of the children of this group which are
1396      * dirty. It is only used in cases where the number of children is > some
1397      * value (currently 10). For very wide trees, this can provide a very
1398      * important speed boost. For the sake of memory consumption, this is
1399      * null unless the number of children ever crosses the threshold where
1400      * it will be activated.
1401      */
1402     private ArrayList<Node> dirtyChildren;
1403 
1404     private Node top;
1405     private Node left;
1406     private Node bottom;
1407     private Node right;
1408     private Node near;
1409     private Node far;
1410 
1411     /**
1412      * @treatAsPrivate implementation detail
1413      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1414      */
1415     @Deprecated
1416     @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
1417         // If we have no children, our bounds are invalid
1418         if (children.isEmpty()) {
1419             return bounds.makeEmpty();
1420         }
1421 
1422         if (tx.isTranslateOrIdentity()) {
1423             // this is a transform which is only doing translations, or nothing
1424             // at all (no scales, rotates, or shears)
1425             // so in this case we can easily use the cached bounds
1426             if (cachedBoundsInvalid) {
1427                 recomputeBounds();
1428 
1429                 if (dirtyChildren != null) {
1430                     dirtyChildren.clear();
1431                 }
1432                 cachedBoundsInvalid = false;
1433                 dirtyChildrenCount = 0;
1434             }
1435             if (!tx.isIdentity()) {
1436                 bounds = bounds.deriveWithNewBounds((float)(cachedBounds.getMinX() + tx.getMxt()),
1437                                  (float)(cachedBounds.getMinY() + tx.getMyt()),
1438                                  (float)(cachedBounds.getMinZ() + tx.getMzt()),
1439                                  (float)(cachedBounds.getMaxX() + tx.getMxt()),
1440                                  (float)(cachedBounds.getMaxY() + tx.getMyt()),
1441                                  (float)(cachedBounds.getMaxZ() + tx.getMzt()));
1442             } else {
1443                 bounds = bounds.deriveWithNewBounds(cachedBounds);
1444             }
1445 
1446             return bounds;
1447         } else {
1448             // there is a scale, shear, or rotation happening, so need to
1449             // do the full transform!
1450             double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE;
1451             double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE, maxZ = Double.MIN_VALUE;
1452             boolean first = true;
1453             for (int i=0, max=children.size(); i<max; i++) {
1454                 final Node node = children.get(i);
1455                 if (node.isVisible()) {
1456                     bounds = getChildTransformedBounds(node, tx, bounds);
1457                     // if the bounds of the child are invalid, we don't want
1458                     // to use those in the remaining computations.
1459                     if (bounds.isEmpty()) continue;
1460                     if (first) {
1461                         minX = bounds.getMinX();
1462                         minY = bounds.getMinY();
1463                         minZ = bounds.getMinZ();
1464                         maxX = bounds.getMaxX();
1465                         maxY = bounds.getMaxY();
1466                         maxZ = bounds.getMaxZ();
1467                         first = false;
1468                     } else {
1469                         minX = Math.min(bounds.getMinX(), minX);
1470                         minY = Math.min(bounds.getMinY(), minY);
1471                         minZ = Math.min(bounds.getMinZ(), minZ);
1472                         maxX = Math.max(bounds.getMaxX(), maxX);
1473                         maxY = Math.max(bounds.getMaxY(), maxY);
1474                         maxZ = Math.max(bounds.getMaxZ(), maxZ);
1475                     }
1476                 }
1477             }
1478             // if "first" is still true, then we didn't have any children with
1479             // non-empty bounds and thus we must return an empty bounds,
1480             // otherwise we have non-empty bounds so go for it.
1481             if (first)
1482                 bounds.makeEmpty();
1483             else
1484                 bounds = bounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ,
1485                         (float)maxX, (float)maxY, (float)maxZ);
1486 
1487             return bounds;
1488         }
1489     }
1490 
1491     private void setChildDirty(final Node node, final boolean dirty) {
1492         if (node.boundsChanged == dirty) {
1493             return;
1494         }
1495 
1496         node.boundsChanged = dirty;
1497         if (dirty) {
1498             if (dirtyChildren != null) {
1499                 dirtyChildren.add(node);
1500             }
1501             ++dirtyChildrenCount;
1502         } else {
1503             if (dirtyChildren != null) {
1504                 dirtyChildren.remove(node);
1505             }
1506             --dirtyChildrenCount;
1507         }
1508     }
1509 
1510     private void childIncluded(final Node node) {
1511         // assert node.isVisible();
1512         cachedBoundsInvalid = true;
1513         setChildDirty(node, true);
1514     }
1515 
1516     // This is called when either the child is actually removed, OR IF IT IS
1517     // TOGGLED TO BE INVISIBLE. This is because in both cases it needs to be
1518     // cleared from the state which manages bounds.
1519     private void childExcluded(final Node node) {
1520         if (node == left) {
1521             left = null;
1522             cachedBoundsInvalid = true;
1523         }
1524         if (node == top) {
1525             top = null;
1526             cachedBoundsInvalid = true;
1527         }
1528         if (node == near) {
1529             near = null;
1530             cachedBoundsInvalid = true;
1531         }
1532         if (node == right) {
1533             right = null;
1534             cachedBoundsInvalid = true;
1535         }
1536         if (node == bottom) {
1537             bottom = null;
1538             cachedBoundsInvalid = true;
1539         }
1540         if (node == far) {
1541             far = null;
1542             cachedBoundsInvalid = true;
1543         }
1544 
1545         setChildDirty(node, false);
1546     }
1547 
1548     /**
1549      * Recomputes the bounds from scratch and saves the cached bounds.
1550      */
1551     private void recomputeBounds() {
1552         // fast path for case of no children
1553         if (children.isEmpty()) {
1554             cachedBounds.makeEmpty();
1555             return;
1556         }
1557 
1558         // fast path for case of 1 child
1559         if (children.size() == 1) {
1560             Node node = children.get(0);
1561             node.boundsChanged = false;
1562             if (node.isVisible()) {
1563                 cachedBounds = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, cachedBounds);
1564                 top = left = bottom = right = near = far = node;
1565             } else {
1566                 cachedBounds.makeEmpty();
1567                 // no need to null edge nodes here, it was done in childExcluded
1568                 // top = left = bottom = right = near = far = null;
1569             }
1570             return;
1571         }
1572 
1573         if ((dirtyChildrenCount == 0) ||
1574                 !updateCachedBounds(dirtyChildren != null
1575                                         ? dirtyChildren : children,
1576                                     dirtyChildrenCount)) {
1577             // failed to update cached bounds, recreate them
1578             createCachedBounds(children);
1579         }
1580     }
1581 
1582     private final int LEFT_INVALID = 1;
1583     private final int TOP_INVALID = 1 << 1;
1584     private final int NEAR_INVALID = 1 << 2;
1585     private final int RIGHT_INVALID = 1 << 3;
1586     private final int BOTTOM_INVALID = 1 << 4;
1587     private final int FAR_INVALID = 1 << 5;
1588 
1589     private boolean updateCachedBounds(final List<Node> dirtyNodes,
1590                                        int remainingDirtyNodes) {
1591         // fast path for untransformed bounds calculation
1592         if (cachedBounds.isEmpty()) {
1593             createCachedBounds(dirtyNodes);
1594             return true;
1595         }
1596 
1597         int invalidEdges = 0;
1598 
1599         if ((left == null) || left.boundsChanged) {
1600             invalidEdges |= LEFT_INVALID;
1601         }
1602         if ((top == null) || top.boundsChanged) {
1603             invalidEdges |= TOP_INVALID;
1604         }
1605         if ((near == null) || near.boundsChanged) {
1606             invalidEdges |= NEAR_INVALID;
1607         }
1608         if ((right == null) || right.boundsChanged) {
1609             invalidEdges |= RIGHT_INVALID;
1610         }
1611         if ((bottom == null) || bottom.boundsChanged) {
1612             invalidEdges |= BOTTOM_INVALID;
1613         }
1614         if ((far == null) || far.boundsChanged) {
1615             invalidEdges |= FAR_INVALID;
1616         }
1617 
1618         // These indicate the bounds of the Group as computed by this
1619         // function
1620         float minX = cachedBounds.getMinX();
1621         float minY = cachedBounds.getMinY();
1622         float minZ = cachedBounds.getMinZ();
1623         float maxX = cachedBounds.getMaxX();
1624         float maxY = cachedBounds.getMaxY();
1625         float maxZ = cachedBounds.getMaxZ();
1626 
1627         // this checks the newly added nodes first, so if dirtyNodes is the
1628         // whole children list, we can end early
1629         for (int i = dirtyNodes.size() - 1; remainingDirtyNodes > 0; --i) {
1630             final Node node = dirtyNodes.get(i);
1631             if (node.boundsChanged) {
1632                 // assert node.isVisible();
1633                 node.boundsChanged = false;
1634                 --remainingDirtyNodes;
1635                 tmp = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, tmp);
1636                 if (!tmp.isEmpty()) {
1637                     float tmpx = tmp.getMinX();
1638                     float tmpy = tmp.getMinY();
1639                     float tmpz = tmp.getMinZ();
1640                     float tmpx2 = tmp.getMaxX();
1641                     float tmpy2 = tmp.getMaxY();
1642                     float tmpz2 = tmp.getMaxZ();
1643 
1644                     // If this node forms an edge, then we will set it to be the
1645                     // node for this edge and update the min/max values
1646                     if (tmpx <= minX) {
1647                         minX = tmpx;
1648                         left = node;
1649                         invalidEdges &= ~LEFT_INVALID;
1650                     }
1651                     if (tmpy <= minY) {
1652                         minY = tmpy;
1653                         top = node;
1654                         invalidEdges &= ~TOP_INVALID;
1655                     }
1656                     if (tmpz <= minZ) {
1657                         minZ = tmpz;
1658                         near = node;
1659                         invalidEdges &= ~NEAR_INVALID;
1660                     }
1661                     if (tmpx2 >= maxX) {
1662                         maxX = tmpx2;
1663                         right = node;
1664                         invalidEdges &= ~RIGHT_INVALID;
1665                     }
1666                     if (tmpy2 >= maxY) {
1667                         maxY = tmpy2;
1668                         bottom = node;
1669                         invalidEdges &= ~BOTTOM_INVALID;
1670                     }
1671                     if (tmpz2 >= maxZ) {
1672                         maxZ = tmpz2;
1673                         far = node;
1674                         invalidEdges &= ~FAR_INVALID;
1675                     }
1676                 }
1677             }
1678         }
1679 
1680         if (invalidEdges != 0) {
1681             // failed to validate some edges
1682             return false;
1683         }
1684 
1685         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1686                                                         maxX, maxY, maxZ);
1687         return true;
1688     }
1689 
1690     private void createCachedBounds(final List<Node> fromNodes) {
1691         // These indicate the bounds of the Group as computed by this function
1692         float minX, minY, minZ;
1693         float maxX, maxY, maxZ;
1694 
1695         final int nodeCount = fromNodes.size();
1696         int i;
1697 
1698         // handle first visible non-empty node
1699         for (i = 0; i < nodeCount; ++i) {
1700             final Node node = fromNodes.get(i);
1701             node.boundsChanged = false;
1702             if (node.isVisible()) {
1703                 tmp = node.getTransformedBounds(
1704                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1705                 if (!tmp.isEmpty()) {
1706                     left = top = near = right = bottom = far = node;
1707                     break;
1708                 }
1709             }
1710         }
1711 
1712         if (i == nodeCount) {
1713             left = top = near = right = bottom = far = null;
1714             cachedBounds.makeEmpty();
1715             return;
1716         }
1717 
1718         minX = tmp.getMinX();
1719         minY = tmp.getMinY();
1720         minZ = tmp.getMinZ();
1721         maxX = tmp.getMaxX();
1722         maxY = tmp.getMaxY();
1723         maxZ = tmp.getMaxZ();
1724 
1725         // handle remaining visible non-empty nodes
1726         for (++i; i < nodeCount; ++i) {
1727             final Node node = fromNodes.get(i);
1728             node.boundsChanged = false;
1729             if (node.isVisible()) {
1730                 tmp = node.getTransformedBounds(
1731                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1732                 if (!tmp.isEmpty()) {
1733                     final float tmpx = tmp.getMinX();
1734                     final float tmpy = tmp.getMinY();
1735                     final float tmpz = tmp.getMinZ();
1736                     final float tmpx2 = tmp.getMaxX();
1737                     final float tmpy2 = tmp.getMaxY();
1738                     final float tmpz2 = tmp.getMaxZ();
1739 
1740                     if (tmpx < minX) { minX = tmpx; left = node; }
1741                     if (tmpy < minY) { minY = tmpy; top = node; }
1742                     if (tmpz < minZ) { minZ = tmpz; near = node; }
1743                     if (tmpx2 > maxX) { maxX = tmpx2; right = node; }
1744                     if (tmpy2 > maxY) { maxY = tmpy2; bottom = node; }
1745                     if (tmpz2 > maxZ) { maxZ = tmpz2; far = node; }
1746                 }
1747             }
1748         }
1749 
1750         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1751                                                         maxX, maxY, maxZ);
1752     }
1753 
1754     @Override protected void updateBounds() {
1755         for (int i=0, max=children.size(); i<max; i++) {
1756             children.get(i).updateBounds();
1757         }
1758         super.updateBounds();
1759     }
1760 
1761     // Note: this marks the currently processed child in terms of transformed bounds. In rare situations like
1762     // in RT-37879, it might happen that the child bounds will be marked as invalid. Due to optimizations,
1763     // the invalidation must *always* be propagated to the parent, because the parent with some transformation
1764     // calls child's getTransformedBounds non-idenitity transform and the child's transformed bounds are thus not validated.
1765     // This does not apply to the call itself however, because the call will yield the correct result even if something
1766     // was invalidated during the computation. We can safely ignore such invalidations from that Node in this case
1767     private Node currentlyProcessedChild;
1768 
1769     private BaseBounds getChildTransformedBounds(Node node, BaseTransform tx, BaseBounds bounds) {
1770         currentlyProcessedChild = node;
1771         bounds = node.getTransformedBounds(bounds, tx);
1772         currentlyProcessedChild = null;
1773         return bounds;
1774     }
1775 
1776     /**
1777      * Called by Node whenever its bounds have changed.
1778      */
1779     void childBoundsChanged(Node node) {
1780         // See comment above at "currentlyProcessedChild" field
1781         if (node == currentlyProcessedChild) {
1782             return;
1783         }
1784 
1785         cachedBoundsInvalid = true;
1786 
1787         // mark the node such that the parent knows that the child's bounds
1788         // are not in sync with this parent. In this way, when the bounds
1789         // need to be computed, we'll come back and figure out the new bounds
1790         // for all the children which have boundsChanged set to true
1791         setChildDirty(node, true);
1792 
1793         // go ahead and indicate that the geom has changed for this parent,
1794         // even though once we figure it all out it may be that the bounds
1795         // have not changed
1796         impl_geomChanged();
1797     }
1798 
1799     /**
1800      * Called by node whenever the visibility of the node changes.
1801      */
1802     void childVisibilityChanged(Node node) {
1803         if (node.isVisible()) {
1804             childIncluded(node);
1805         } else {
1806             childExcluded(node);
1807         }
1808 
1809         impl_geomChanged();
1810     }
1811 
1812     /**
1813      * @treatAsPrivate implementation detail
1814      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1815      */
1816     @Deprecated
1817     @Override
1818     protected boolean impl_computeContains(double localX, double localY) {
1819         final Point2D tempPt = TempState.getInstance().point;
1820         for (int i=0, max=children.size(); i<max; i++) {
1821             final Node node = children.get(i);
1822             tempPt.x = (float)localX;
1823             tempPt.y = (float)localY;
1824             try {
1825                 node.parentToLocal(tempPt);
1826             } catch (NoninvertibleTransformException e) {
1827                 continue;
1828             }
1829             if (node.contains(tempPt.x, tempPt.y)) {
1830                 return true;
1831             }
1832         }
1833         return false;
1834     }
1835 
1836     /**
1837      * @treatAsPrivate implementation detail
1838      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1839      */
1840     @Deprecated
1841     public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
1842         return alg.processContainerNode(this, ctx);
1843     }
1844 
1845     @Override
1846     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1847         switch (attribute) {
1848             case CHILDREN: return getChildrenUnmodifiable();
1849             default: return super.queryAccessibleAttribute(attribute, parameters);
1850         }
1851     }
1852 
1853     void releaseAccessible() {
1854         for (int i=0, max=children.size(); i<max; i++) {
1855             final Node node = children.get(i);
1856             node.releaseAccessible();
1857         }
1858         super.releaseAccessible();
1859     }
1860 
1861     /**
1862      * Note: The only user of this method is in unit test: Parent_structure_sync_Test.
1863      */
1864     List<Node> test_getRemoved() {
1865         return removed;
1866     }
1867 }