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