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