Print this page

    rev 1582 : RT-22121: Have StyleManager for Parent, also

Split | Close
Expand all
Collapse all

              --- old/javafx-ui-common/src/javafx/scene/Parent.java
              +++ new/javafx-ui-common/src/javafx/scene/Parent.java

       1    1  /*
       2    2   * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
       3    3   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       4    4   *
       5    5   * This code is free software; you can redistribute it and/or modify it
       6    6   * under the terms of the GNU General Public License version 2 only, as
       7    7   * published by the Free Software Foundation.  Oracle designates this
       8    8   * particular file as subject to the "Classpath" exception as provided
       9    9   * by Oracle in the LICENSE file that accompanied this code.
      10   10   *
      11   11   * This code is distributed in the hope that it will be useful, but WITHOUT
      12   12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
      13   13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
      14   14   * version 2 for more details (a copy is included in the LICENSE file that
      15   15   * accompanied this code).
      16   16   *
      17   17   * You should have received a copy of the GNU General Public License version
      18   18   * 2 along with this work; if not, write to the Free Software Foundation,
      19   19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
      20   20   *
      21   21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
      22   22   * or visit www.oracle.com if you need additional information or have any
      23   23   * questions.
      24   24   */
      25   25
      26   26  package javafx.scene;
      27   27
      28   28  import java.util.ArrayList;
      29   29  import java.util.HashSet;
      30   30  import java.util.LinkedHashSet;
      31   31  import java.util.List;
      32   32  import java.util.Set;
      33   33
      34   34  import com.sun.javafx.jmx.MXNodeAlgorithm;
      35   35  import com.sun.javafx.jmx.MXNodeAlgorithmContext;
      36   36  import javafx.beans.property.*;
      37   37  import javafx.collections.FXCollections;
      38   38  import javafx.collections.ListChangeListener.Change;
      39   39  import javafx.collections.ObservableList;
      40   40
      41   41  import com.sun.javafx.Logging;
      42   42  import com.sun.javafx.TempState;
      43   43  import com.sun.javafx.Utils;
      44   44  import com.sun.javafx.collections.TrackableObservableList;
      45   45  import com.sun.javafx.collections.VetoableObservableList;
      46   46  import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
      47   47  import com.sun.javafx.css.Selector;
      48   48  import com.sun.javafx.css.StyleManager;
      49   49  import com.sun.javafx.geom.BaseBounds;
      50   50  import com.sun.javafx.geom.PickRay;
      51   51  import com.sun.javafx.geom.Point2D;
      52   52  import com.sun.javafx.geom.RectBounds;
      53   53  import com.sun.javafx.geom.transform.BaseTransform;
      54   54  import com.sun.javafx.geom.transform.NoninvertibleTransformException;
      55   55  import com.sun.javafx.logging.PlatformLogger;
      56   56  import com.sun.javafx.scene.CSSFlags;
      57   57  import com.sun.javafx.scene.DirtyBits;
      58   58  import com.sun.javafx.scene.traversal.TraversalEngine;
      59   59  import com.sun.javafx.sg.PGGroup;
      60   60  import com.sun.javafx.sg.PGNode;
      61   61  import com.sun.javafx.tk.Toolkit;
      62   62
      63   63  /**
      64   64   * The base class for all nodes that have children in the scene graph.
      65   65   * <p>
      66   66   * This class handles all hierarchical scene graph operations, including adding/removing
      67   67   * child nodes, marking branches dirty for layout and rendering, picking,
      68   68   * bounds calculations, and executing the layout pass on each pulse.
      69   69   * <p>
      70   70   * There are two direct concrete Parent subclasses
      71   71   * <ul>
      72   72   * <li>{@link Group} effects and transforms to be applied to a collection of child nodes.</li>
      73   73   * <li>{@link javafx.scene.layout.Region} class for nodes that can be styled with CSS and layout children. </li>
      74   74   * </ul>
      75   75   *
      76   76   */
      77   77  public abstract class Parent extends Node {
      78   78
      79   79      private static final int DIRTY_CHILDREN_THRESHOLD = 10;
      80   80
      81   81      // If set to true, generate a warning message whenever adding a node to a
      82   82      // parent if it is currently a child of another parent.
      83   83      private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn");
      84   84
      85   85      /**
      86   86       * Threshold when it's worth to populate list of removed children.
      87   87       */
      88   88      private static final int REMOVED_CHILDREN_THRESHOLD = 20;
      89   89
      90   90      /**
      91   91       * Do not populate list of removed children when its number exceeds threshold,
      92   92       * but mark whole parent dirty.
      93   93       */
      94   94      private boolean removedChildrenExceedsThreshold = false;
      95   95
      96   96      /**
      97   97       * @treatAsPrivate implementation detail
      98   98       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
      99   99       */
     100  100      @Deprecated
     101  101      @Override public void impl_updatePG() {
     102  102          super.impl_updatePG();
     103  103
     104  104          if (Utils.assertionEnabled()) {
     105  105              List<PGNode> pgnodes = getPGGroup().getChildren();
     106  106              if (pgnodes.size() != pgChildrenSize) {
     107  107                  java.lang.System.err.println("*** pgnodes.size() [" + pgnodes.size() + "] != pgChildrenSize [" + pgChildrenSize + "]");
     108  108              }
     109  109          }
     110  110
     111  111          if (impl_isDirty(DirtyBits.PARENT_CHILDREN)) {
     112  112              PGGroup peer = getPGGroup();
     113  113              // Whether a permutation, or children having been added or
     114  114              // removed, we'll want to clear out the PG side starting
     115  115              // from startIdx. We know that everything up to but not
     116  116              // including startIdx is identical between the FX and PG
     117  117              // sides, so we only need to update the remaining portion.
     118  118              peer.clearFrom(startIdx);
     119  119              for (int idx = startIdx; idx < children.size(); idx++) {
     120  120                      Node n = children.get(idx);
     121  121                      if (n != null) {
     122  122                          peer.add(idx, n.impl_getPGNode());
     123  123                      }
     124  124                  }
     125  125              if (removedChildrenExceedsThreshold) {
     126  126                  peer.markDirty();
     127  127                  removedChildrenExceedsThreshold = false;
     128  128              } else {
     129  129                  if (removed != null && !removed.isEmpty()) {
     130  130                      for(int i = 0; i < removed.size(); i++) {
     131  131                          peer.addToRemoved(removed.get(i).impl_getPGNode());
     132  132                      }
     133  133                      removed.clear();
     134  134                  }
     135  135              }
     136  136              pgChildrenSize = children.size();
     137  137              startIdx = pgChildrenSize;
     138  138          }
     139  139
     140  140          if (Utils.assertionEnabled()) validatePG();
     141  141      }
     142  142
     143  143
     144  144      /***********************************************************************
     145  145       *                        Scenegraph Structure                         *
     146  146       *                                                                     *
     147  147       *  Functions and variables related to the scenegraph structure,       *
     148  148       *  modifying the structure, and walking the structure.                *
     149  149       *                                                                     *
     150  150       **********************************************************************/
     151  151
     152  152      // Used to check for duplicate nodes
     153  153      private final Set<Node> childSet = new HashSet<Node>();
     154  154
     155  155      // starting child index from which we need to send the children to the PGGroup
     156  156      private int startIdx = 0;
     157  157
     158  158      // double of children in the PGGroup as of the last update
     159  159      private int pgChildrenSize = 0;
     160  160
     161  161      void validatePG() {
     162  162          boolean assertionFailed = false;
     163  163          List<PGNode> pgnodes = getPGGroup().getChildren();
     164  164          if (pgnodes.size() != children.size()) {
     165  165              java.lang.System.err.println("*** pgnodes.size validatePG() [" + pgnodes.size() + "] != children.size() [" + children.size() + "]");
     166  166              assertionFailed = true;
     167  167          } else {
     168  168              for (int idx = 0; idx < children.size(); idx++) {
     169  169                  Node n = children.get(idx);
     170  170                  if (n.getParent() != this) {
     171  171                      java.lang.System.err.println("*** this=" + this + " validatePG children[" + idx + "].parent= " + n.getParent());
     172  172                      assertionFailed = true;
     173  173                  }
     174  174                  if (n.impl_getPGNode() != pgnodes.get(idx)) {
     175  175                      java.lang.System.err.println("*** pgnodes[" + idx + "] validatePG != children[" + idx + "]");
     176  176                      assertionFailed = true;
     177  177                  }
     178  178              }
     179  179          }
     180  180          if (assertionFailed) {
     181  181              throw new java.lang.AssertionError("validation of PGGroup children failed");
     182  182          }
     183  183
     184  184      }
     185  185
     186  186      void printSeq(String prefix, List<Node> nodes) {
     187  187          String str = prefix;
     188  188          for (Node nn : nodes) {
     189  189              str += nn + " ";
     190  190          }
     191  191          System.out.println(str);
     192  192      }
     193  193
     194  194      /**
     195  195       * Variable used to avoid executing the body of the on replace trigger on
     196  196       * children. This is specifically used when we know that changes to the
     197  197       * children is going to be valid so as to avoid all the scenegraph surgery
     198  198       * validation routines in the trigger.
     199  199       */
     200  200      private boolean ignoreChildrenTrigger = false;
     201  201
     202  202      // Variable used to indicate that the change to the children ObservableList is
     203  203      // a simple permutation as the result of a toFront or toBack operation.
     204  204      // We can avoid almost all of the processing of the on replace trigger in
     205  205      // this case.
     206  206      private boolean childrenTriggerPermutation = false;
     207  207
     208  208      //accumulates all removed nodes between pulses, for dirty area calculation.
     209  209      private List<Node> removed;
     210  210
     211  211      /**
     212  212       * A ObservableList of child {@code Node}s.
     213  213       * <p>
     214  214       * See the class documentation for {@link Node} for scene graph structure
     215  215       * restrictions on setting a {@link Parent}'s children ObservableList.
     216  216       * If these restrictions are violated by a change to the children ObservableList,
     217  217       * the change is ignored and the previous value of the child ObservableList is
     218  218       * restored.
     219  219       *
     220  220       * {@code <p>Throws AssignToBoundException} if the same node
     221  221       * appears in two different bound ObservableList.
     222  222       *
     223  223       * @defaultValue empty
     224  224       * @since JavaFX 1.3
     225  225       */
     226  226      private final ObservableList<Node> children = new VetoableObservableList<Node>() {
     227  227          // set to true if either childRemoved or childAdded returns
     228  228          // true. These functions will indicate whether the geom
     229  229          // bounds for the parent have changed
     230  230          private boolean geomChanged;
     231  231          private boolean childrenModified;
     232  232
     233  233          @Override
     234  234          protected void onProposedChange(final List<Node> newNodes, int[] toBeRemoved) {
     235  235              if (ignoreChildrenTrigger) {
     236  236                  return;
     237  237              }
     238  238              if (Parent.this.getScene() != null) {
     239  239                  // NOTE: this will throw IllegalStateException if we are on the wrong thread
     240  240                  Toolkit.getToolkit().checkFxUserThread();
     241  241              }
     242  242              geomChanged = false;
     243  243
     244  244              long newLength = children.size() + newNodes.size();
     245  245              int removedLength = 0;
     246  246              for (int i = 0; i < toBeRemoved.length; i += 2) {
     247  247                  removedLength += toBeRemoved[i + 1] - toBeRemoved[i];
     248  248              }
     249  249              newLength -= removedLength;
     250  250
     251  251              // If the childrenTriggerPermutation flag is set, then we know it
     252  252              // is a simple permutation and no further checking is needed.
     253  253              if (childrenTriggerPermutation) {
     254  254                  childrenModified = false;
     255  255                  return;
     256  256              }
     257  257
     258  258              // If the childrenTriggerPermutation flag is not set, then we will
     259  259              // check to see whether any element in the ObservableList has changed,
     260  260              // or whether the new ObservableList is a permutation on the existing
     261  261              // ObservableList. Note that even if the childrenModified flag is false,
     262  262              // we still have to check for duplicates. If it is a simple
     263  263              // permutation, we can avoid checking for cycles or other parents.
     264  264              childrenModified = true;
     265  265              if (newLength == childSet.size()) {
     266  266                  childrenModified = false;
     267  267                  for (int i = newNodes.size() - 1; i >= 0; --i ) {
     268  268                      Node n = newNodes.get(i);
     269  269                      if (!childSet.contains(n)) {
     270  270                          childrenModified = true;
     271  271                          break;
     272  272                      }
     273  273                  }
     274  274              }
     275  275
     276  276              // Enforce scene graph invariants, and check for structural errors.
     277  277              //
     278  278              // 1. If a child has been added to this parent more than once,
     279  279              // then it is an error
     280  280              //
     281  281              // 2. If a child is a target of a clip, then it is an error.
     282  282              //
     283  283              // 3. If a node would cause a cycle, then it is an error.
     284  284              //
     285  285              // 4. If a node is null
     286  286              //
     287  287              // Note that if a node is the child of another parent, we will
     288  288              // implicitly remove the node from its former Parent after first
     289  289              // checking for errors.
     290  290
     291  291              // iterate over the nodes that were removed and remove them from
     292  292              // the hash set.
     293  293              for (int i = 0; i < toBeRemoved.length; i += 2) {
     294  294                  for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
     295  295                      childSet.remove(children.get(j));
     296  296                  }
     297  297              }
     298  298
     299  299              try {
     300  300                  childSet.addAll(newNodes);
     301  301                  if (childSet.size() != newLength) {
     302  302                      throw new IllegalArgumentException(
     303  303                              constructExceptionMessage(
     304  304                                  "duplicate children added", null));
     305  305                  }
     306  306
     307  307                  if (childrenModified) {
     308  308                      for (int i = newNodes.size() - 1; i >= 0; --i ) {
     309  309                          Node node = newNodes.get(i);
     310  310                          if (node == null) {
     311  311                              throw new NullPointerException(
     312  312                                      constructExceptionMessage(
     313  313                                          "child node is null", null));
     314  314                          }
     315  315                          if (node.getClipParent() != null) {
     316  316                              throw new IllegalArgumentException(
     317  317                                      constructExceptionMessage(
     318  318                                          "node already used as a clip", node));
     319  319                          }
     320  320                          if (wouldCreateCycle(Parent.this, node)) {
     321  321                              throw new IllegalArgumentException(
     322  322                                      constructExceptionMessage(
     323  323                                          "cycle detected", node));
     324  324                          }
     325  325                      }
     326  326                  }
     327  327              } catch (RuntimeException e) {
     328  328                  //Return children to it's original state
     329  329                  childSet.clear();
     330  330                  childSet.addAll(children);
     331  331
     332  332                  // rethrow
     333  333                  throw e;
     334  334              }
     335  335
     336  336              // Done with error checking
     337  337
     338  338              if (!childrenModified) {
     339  339                  return;
     340  340              }
     341  341
     342  342              // iterate over the nodes that were removed and clear their
     343  343              // parent and scene. Add to them also to removed list for further
     344  344              // dirty regions calculation.
     345  345              if (removed == null) {
     346  346                  removed = new ArrayList<Node>();
     347  347              }
     348  348              if (removed.size() + removedLength > REMOVED_CHILDREN_THRESHOLD) {
     349  349                  //do not populate too many children in removed list
     350  350                  removedChildrenExceedsThreshold = true;
     351  351              }
     352  352              for (int i = 0; i < toBeRemoved.length; i += 2) {
     353  353                  for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
     354  354                      Node old = children.get(j);
     355  355                      if (dirtyChildren != null) {
     356  356                          dirtyChildren.remove(old);
     357  357                      }
     358  358                      if (old == null) {
     359  359                          continue;
     360  360                      }
     361  361
     362  362                      if (!geomChanged && old.isVisible()) {
     363  363                          geomChanged = childRemoved(old);
     364  364                      }
     365  365                      if (old.getParent() == Parent.this) {
     366  366                          old.setParent(null);
     367  367                          old.setScene(null);
     368  368                      }
     369  369                      if (!removedChildrenExceedsThreshold) {
     370  370                          removed.add(old);
     371  371                      }
     372  372                  }
     373  373              }
     374  374          }
     375  375
     376  376          @Override
     377  377          protected void onChanged(Change<Node> c) {
     378  378              // proceed with updating the scene graph
     379  379              if (childrenModified) {
     380  380                  unmodifiableManagedChildren = null;
     381  381                  boolean relayout = false;
     382  382
     383  383                  while (c.next()) {
     384  384                      int from = c.getFrom();
     385  385                      int to = c.getTo();
     386  386                      for (int i = from; i < to; ++i) {
     387  387                          Node n = children.get(i);
     388  388                          if (n != null && n.getParent() != null && n.getParent() != Parent.this) {
     389  389                              if (warnOnAutoMove) {
     390  390                                  java.lang.System.err.println("WARNING added to a new parent without first removing it from its current");
     391  391                                  java.lang.System.err.println("    parent. It will be automatically removed from its current parent.");
     392  392                                  java.lang.System.err.println("    node=" + n + " oldparent= " + n.getParent() + " newparent=" + this);
     393  393                              }
     394  394                              n.getParent().children.remove(n);
     395  395                              if (n.isManaged()) {
     396  396                                  relayout = true;
     397  397                              }
     398  398                              if (warnOnAutoMove) {
     399  399                                  Thread.dumpStack();
     400  400                              }
     401  401                          }
     402  402                      }
     403  403
     404  404                      List<Node> removed = c.getRemoved();
     405  405                      int removedSize = removed.size();
     406  406                      for (int i = 0; i < removedSize; ++i) {
     407  407                          Node node = removed.get(i);
     408  408                          if (node != null && node.isManaged()) {
     409  409                              relayout = true;
     410  410                          }
     411  411                      }
     412  412
     413  413                      // update the parent and scene for each new node
     414  414                      for (int i = from; i < to; ++i) {
     415  415                          Node node = children.get(i);
     416  416                          if (node != null) {
     417  417                              if (node.isManaged()) {
     418  418                                  relayout = true;
     419  419                              }
     420  420                              node.setParent(Parent.this);
     421  421                              node.setScene(getScene());
     422  422                              if (dirtyChildren != null) {
     423  423                                  dirtyChildren.add(node);
     424  424                              }
     425  425                              if (!geomChanged && node.isVisible()) {
     426  426                                  geomChanged = childAdded(node);
     427  427                              }
     428  428                          }
     429  429                      }
     430  430                  }
     431  431
     432  432                  // check to see if the number of children exceeds
     433  433                  // DIRTY_CHILDREN_THRESHOLD and dirtyChildren is null.
     434  434                  // If so, then we need to create dirtyChildren and
     435  435                  // populate it.
     436  436                  if (dirtyChildren == null && children.size() > DIRTY_CHILDREN_THRESHOLD) {
     437  437                      dirtyChildren = new LinkedHashSet<Node>(2 * children.size());
     438  438                      // only bother populating children if geom has
     439  439                      // changed, otherwise there is no need
     440  440                      if (geomChanged) {
     441  441                          int size = children.size();
     442  442                          for (int i = 0; i < size; ++i) {
     443  443                              Node ch = children.get(i);
     444  444                              if (ch.isVisible() && ch.boundsChanged) {
     445  445                                  dirtyChildren.add(ch);
     446  446                              }
     447  447                          }
     448  448                      }
     449  449                  }
     450  450
     451  451                  if (geomChanged) {
     452  452                      impl_geomChanged();
     453  453                  }
     454  454
     455  455                  //
     456  456                  // Note that the styles of a child do not affect the parent or
     457  457                  // its siblings. Thus, it is only necessary to reapply css to
     458  458                  // the Node just added and not to this parent and all of its
     459  459                  // children. So the following call to impl_reapplyCSS was moved
     460  460                  // to Node.parentProperty. The original comment and code were
     461  461                  // purposely left here as documentation should there be any
     462  462                  // question about how the code used to work and why the change
     463  463                  // was made.
     464  464                  //
     465  465                  // if children have changed then I need to reapply
     466  466                  // CSS from this node on down
     467  467  //                impl_reapplyCSS();
     468  468                  //
     469  469
     470  470                  // request layout if a Group subclass has overridden doLayout OR
     471  471                  // if one of the new children needs layout, in which case need to ensure
     472  472                  // the needsLayout flag is set all the way to the root so the next layout
     473  473                  // pass will reach the child.
     474  474                  if (relayout) {
     475  475                      requestLayout();
     476  476                  }
     477  477              }
     478  478
     479  479              // Note the starting index at which we need to update the
     480  480              // PGGroup on the next update, and mark the children dirty
     481  481              c.reset();
     482  482              c.next();
     483  483              if (startIdx > c.getFrom()) {
     484  484                  startIdx = c.getFrom();
     485  485              }
     486  486
     487  487              impl_markDirty(DirtyBits.PARENT_CHILDREN);
     488  488          }
     489  489
     490  490          private String constructExceptionMessage(
     491  491                  String cause, Node offendingNode) {
     492  492              final StringBuilder sb = new StringBuilder("Children: ");
     493  493              sb.append(cause);
     494  494              sb.append(": parent = ").append(Parent.this);
     495  495              if (offendingNode != null) {
     496  496                  sb.append(", node = ").append(offendingNode);
     497  497              }
     498  498
     499  499              return sb.toString();
     500  500          }
     501  501      };
     502  502
     503  503      /**
     504  504       * A constant reference to an unmodifiable view of the children, such that every time
     505  505       * we ask for an unmodifiable list of children, we don't actually create a new
     506  506       * collection and return it. The memory overhead is pretty lightweight compared
     507  507       * to all the garbage we would otherwise generate.
     508  508       */
     509  509      private final ObservableList<Node> unmodifiableChildren =
     510  510              FXCollections.unmodifiableObservableList(children);
     511  511
     512  512      /**
     513  513       * A cached reference to the unmodifiable managed children of this Parent. This is
     514  514       * created whenever first asked for, and thrown away whenever children are added
     515  515       * or removed or when their managed state changes. This could be written
     516  516       * differently, such that this list is essentially a filtered copy of the
     517  517       * main children, but that additional overhead might not be worth it.
     518  518       */
     519  519      private List<Node> unmodifiableManagedChildren = null;
     520  520
     521  521      /**
     522  522       * Gets the list of children of this {@code Parent}.
     523  523       *
     524  524       * <p>
     525  525       * See the class documentation for {@link Node} for scene graph structure
     526  526       * restrictions on setting a {@link Parent}'s children list.
     527  527       * If these restrictions are violated by a change to the list of children,
     528  528       * the change is ignored and the previous value of the children list is
     529  529       * restored. An {@link IllegalArgumentException} is thrown in this case.
     530  530       *
     531  531       * <p>
     532  532       * If this {@link Parent} node is attached to a {@link Scene}, then its
     533  533       * list of children must only be modified on the JavaFX Application Thread.
     534  534       * An {@link IllegalStateException} is thrown if this restriction is
     535  535       * violated.
     536  536       *
     537  537       * <p>
     538  538       * Note to subclasses: if you override this method, you must return from
     539  539       * your implementation the result of calling this super method. The actual
     540  540       * list instance returned from any getChildren() implementation must be
     541  541       * the list owned and managed by this Parent. The only typical purpose
     542  542       * for overriding this method is to promote the method to be public.
     543  543       *
     544  544       * @return the list of children of this {@code Parent}.
     545  545       */
     546  546      protected ObservableList<Node> getChildren() {
     547  547          return children;
     548  548      }
     549  549
     550  550      /**
     551  551       * Gets the list of children of this {@code Parent} as a read-only
     552  552       * list.
     553  553       *
     554  554       * @return read-only access to this parent's children ObservableList
     555  555       */
     556  556      @ReturnsUnmodifiableCollection
     557  557      public ObservableList<Node> getChildrenUnmodifiable() {
     558  558          return unmodifiableChildren;
     559  559      }
     560  560
     561  561      /**
     562  562       * Gets the list of all managed children of this {@code Parent}.
     563  563       *
     564  564       * @param <E> the type of the children nodes
     565  565       * @return list of all managed children in this parent
     566  566       */
     567  567      @ReturnsUnmodifiableCollection
     568  568      protected <E extends Node> List<E> getManagedChildren() {
     569  569          if (unmodifiableManagedChildren == null) {
     570  570              unmodifiableManagedChildren = new ArrayList<Node>();
     571  571              for (int i=0, max=children.size(); i<max; i++) {
     572  572                  Node e = children.get(i);
     573  573                  if (e != null && e.isManaged()) {
     574  574                      unmodifiableManagedChildren.add(e);
     575  575                  }
     576  576              }
     577  577          }
     578  578          return (List<E>)unmodifiableManagedChildren;
     579  579      }
     580  580
     581  581      /**
     582  582       * Called by Node whenever its managed state may have changed, this
     583  583       * method will cause the view of managed children to be updated
     584  584       * such that it properly includes or excludes this child.
     585  585       */
     586  586      final void managedChildChanged() {
     587  587          requestLayout();
     588  588          unmodifiableManagedChildren = null;
     589  589      }
     590  590
     591  591      // implementation of Node.toFront function
     592  592      final void impl_toFront(Node node) {
     593  593          if (Utils.assertionEnabled()) {
     594  594              if (!childSet.contains(node)) {
     595  595                  throw new java.lang.AssertionError(
     596  596                          "specified node is not in the list of children");
     597  597              }
     598  598          }
     599  599
     600  600          if (children.get(children.size() - 1) != node) {
     601  601              childrenTriggerPermutation = true;
     602  602              try {
     603  603                  children.remove(node);
     604  604                  children.add(node);
     605  605              } finally {
     606  606                  childrenTriggerPermutation = false;
     607  607              }
     608  608          }
     609  609      }
     610  610
     611  611      // implementation of Node.toBack function
     612  612      final void impl_toBack(Node node) {
     613  613          if (Utils.assertionEnabled()) {
     614  614              if (!childSet.contains(node)) {
     615  615                  throw new java.lang.AssertionError(
     616  616                          "specified node is not in the list of children");
     617  617              }
     618  618          }
     619  619
     620  620          if (children.get(0) != node) {
     621  621              childrenTriggerPermutation = true;
     622  622              try {
     623  623                  children.remove(node);
     624  624                  children.add(0, node);
     625  625              } finally {
     626  626                  childrenTriggerPermutation = false;
     627  627              }
     628  628          }
     629  629      }
     630  630
     631  631      @Override void sceneChanged(final Scene old) {
     632  632          // Give all of the children the new scene
     633  633          final Scene scene = getScene();
     634  634          for (int i=0, max=children.size(); i<max; i++) {
     635  635              final Node node = children.get(i);
     636  636              if (node != null) {
     637  637                  node.setScene(scene);
     638  638              }
     639  639          }
     640  640
     641  641          // If this node was in the old scene's dirty layout
     642  642          // list, then remove it from that list so that it is
     643  643          // not processed on the next pulse
     644  644          final boolean awaitingLayout = isNeedsLayout();
     645  645          if (awaitingLayout && old != null) {
     646  646              old.removeFromDirtyLayoutList(this);
     647  647          }
     648  648
     649  649          sceneRoot = scene != null && scene.getRoot() == this;
     650  650          layoutRoot = !isManaged() || sceneRoot;
     651  651
     652  652          // If this node is dirty and the new scene is not null
     653  653          // then add this node to the new scene's dirty list
     654  654          if (scene != null && awaitingLayout && layoutRoot) {
     655  655              scene.addToDirtyLayoutList(this);
     656  656          }
     657  657      }
     658  658
     659  659      @Override
     660  660      void setDerivedDepthTest(boolean value) {
     661  661          super.setDerivedDepthTest(value);
     662  662
     663  663          for (int i=0, max=children.size(); i<max; i++) {
     664  664              final Node node = children.get(i);
     665  665              node.computeDerivedDepthTest();
     666  666          }
     667  667      }
     668  668
     669  669      /**
     670  670       * @treatAsPrivate implementation detail
     671  671       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     672  672       */
     673  673      @Deprecated
     674  674      @Override protected Node impl_pickNodeLocal(double localX, double localY) {
     675  675          if (containsBounds(localX, localY)) {
     676  676              for (int i = children.size()-1; i >= 0; i--) {
     677  677                  Node picked = children.get(i).impl_pickNode(localX, localY);
     678  678                  if (picked != null) {
     679  679                      return picked;
     680  680                  }
     681  681              }
     682  682          }
     683  683          return null;
     684  684      }
     685  685
     686  686      /**
     687  687       * @treatAsPrivate implementation detail
     688  688       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     689  689       */
     690  690      @Deprecated
     691  691      @Override protected Node impl_pickNodeLocal(PickRay pickRay) {
     692  692          for (int i = children.size()-1; i >= 0; i--) {
     693  693              Node picked = children.get(i).impl_pickNode(pickRay);
     694  694
     695  695              if (picked != null) {
     696  696                  return picked;
     697  697              }
     698  698          }
     699  699          return null;
     700  700      }
     701  701
     702  702      @Override boolean isConnected() {
     703  703          return super.isConnected() || sceneRoot;
     704  704      }
     705  705
     706  706      @Override public Node lookup(String selector) {
     707  707          Node n = super.lookup(selector);
     708  708          if (n == null) {
     709  709              for (int i=0, max=children.size(); i<max; i++) {
     710  710                  final Node node = children.get(i);
     711  711                  n = node.lookup(selector);
     712  712                  if (n != null) return n;
     713  713              }
     714  714          }
     715  715          return n;
     716  716      }
     717  717
     718  718      /**
     719  719       * Please Note: This method should never create the results set,
     720  720       * let the Node class implementation do this!
     721  721       */
     722  722      @Override List<Node> lookupAll(Selector selector, List<Node> results) {
     723  723          results = super.lookupAll(selector, results);
     724  724          for (int i=0, max=children.size(); i<max; i++) {
     725  725              final Node node = children.get(i);
     726  726              results = node.lookupAll(selector, results);
     727  727          }
     728  728          return results;
     729  729      }
     730  730
     731  731      /** @treatAsPrivate implementation detail */
     732  732      private javafx.beans.property.ObjectProperty<TraversalEngine> impl_traversalEngine;
     733  733
     734  734      /**
     735  735       * @treatAsPrivate implementation detail
     736  736       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     737  737       */
     738  738      // SB-dependency: RT-21209 has been filed to track this
     739  739      @Deprecated
     740  740      public final void setImpl_traversalEngine(TraversalEngine value) {
     741  741          impl_traversalEngineProperty().set(value);
     742  742      }
     743  743
     744  744      /**
     745  745       * @treatAsPrivate implementation detail
     746  746       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     747  747       */
     748  748      @Deprecated
     749  749      public final TraversalEngine getImpl_traversalEngine() {
     750  750          return impl_traversalEngine == null ? null : impl_traversalEngine.get();
     751  751      }
     752  752
     753  753      /**
     754  754       * @treatAsPrivate implementation detail
     755  755       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
     756  756       */
     757  757      @Deprecated
     758  758      public final ObjectProperty<TraversalEngine> impl_traversalEngineProperty() {
     759  759          if (impl_traversalEngine == null) {
     760  760              impl_traversalEngine =
     761  761                      new SimpleObjectProperty<TraversalEngine>(
     762  762                              this, "impl_traversalEngine");
     763  763          }
     764  764          return impl_traversalEngine;
     765  765      }
     766  766
     767  767      /***********************************************************************
     768  768       *                               Layout                                *
     769  769       *                                                                     *
     770  770       *  Functions and variables related to the layout scheme used by       *
     771  771       *  JavaFX. Includes both public and private API.                      *
     772  772       *                                                                     *
     773  773       **********************************************************************/
     774  774      /**
     775  775       * Indicates that this Node and its subnodes requires a layout pass on
     776  776       * the next pulse.
     777  777       */
     778  778      private ReadOnlyBooleanWrapper needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", true);
     779  779
     780  780      protected final void setNeedsLayout(boolean value) {
     781  781          needsLayout.set(value);
     782  782      }
     783  783
     784  784      public final boolean isNeedsLayout() {
     785  785          return needsLayout.get();
     786  786      }
     787  787
     788  788      public final ReadOnlyBooleanProperty needsLayoutProperty() {
     789  789          return needsLayout.getReadOnlyProperty();
     790  790      }
     791  791
     792  792      /**
     793  793       * This package levelis used only by Node. It is set to true while
     794  794       * the layout() function is processing and set to false on the conclusion.
     795  795       * It is used by the Node to decide whether to perform CSS updates
     796  796       * synchronously or asynchronously.
     797  797       */
     798  798      boolean performingLayout = false;
     799  799
     800  800      private boolean sizeCacheClear = true;
     801  801      private double prefWidthCache = -1;
     802  802      private double prefHeightCache = -1;
     803  803      private double minWidthCache = -1;
     804  804      private double minHeightCache = -1;
     805  805
     806  806      /**
     807  807       * Requests a layout pass to be performed before the next scene is
     808  808       * rendered. This is batched up asynchronously to happen once per
     809  809       * "pulse", or frame of animation.
     810  810       * <p>
     811  811       * If this parent is either a layout root or unmanaged, then it will be
     812  812       * added directly to the scene's dirty layout list, otherwise requestLayout
     813  813       * will be invoked on its parent.
     814  814       */
     815  815      public void requestLayout() {
     816  816          if (!isNeedsLayout()) {
     817  817              prefWidthCache = -1;
     818  818              prefHeightCache = -1;
     819  819              minWidthCache = -1;
     820  820              minHeightCache = -1;
     821  821              PlatformLogger logger = Logging.getLayoutLogger();
     822  822              if (logger.isLoggable(PlatformLogger.FINER)) {
     823  823                  logger.finer(this.toString());
     824  824              }
     825  825
     826  826              setNeedsLayout(true);
     827  827              if (layoutRoot) {
     828  828                  final Scene scene = getScene();
     829  829                  if (scene != null) {
     830  830                      if (logger.isLoggable(PlatformLogger.FINER)) {
     831  831                          logger.finer(this.toString()+" layoutRoot added to scene dirty layout list");
     832  832                      }
     833  833                      scene.addToDirtyLayoutList(this);
     834  834                  }
     835  835              } else {
     836  836                  final Parent parent = getParent();
     837  837                  if (parent != null) {
     838  838                      parent.requestLayout();
     839  839                  }
     840  840              }
     841  841          } else {
     842  842              clearSizeCache();
     843  843          }
     844  844      }
     845  845
     846  846      void clearSizeCache() {
     847  847          if (sizeCacheClear) {
     848  848              return;
     849  849          }
     850  850          sizeCacheClear = true;
     851  851          prefWidthCache = -1;
     852  852          prefHeightCache = -1;
     853  853          minWidthCache = -1;
     854  854          minHeightCache = -1;
     855  855          if (!layoutRoot) {
     856  856              final Parent parent = getParent();
     857  857              if (parent != null) {
     858  858                  parent.clearSizeCache();
     859  859              }
     860  860          }
     861  861      }
     862  862
     863  863      @Override public double prefWidth(double height) {
     864  864          if (height == -1) {
     865  865              if (prefWidthCache == -1) {
     866  866                  prefWidthCache = computePrefWidth(-1);
     867  867                  sizeCacheClear = false;
     868  868              }
     869  869              return prefWidthCache;
     870  870          } else {
     871  871              return computePrefWidth(height);
     872  872          }
     873  873      }
     874  874
     875  875      @Override public double prefHeight(double width) {
     876  876          if (width == -1) {
     877  877              if (prefHeightCache == -1) {
     878  878                  prefHeightCache = computePrefHeight(-1);
     879  879                  sizeCacheClear = false;
     880  880              }
     881  881              return prefHeightCache;
     882  882          } else {
     883  883              return computePrefHeight(width);
     884  884          }
     885  885      }
     886  886
     887  887      @Override public double minWidth(double height) {
     888  888          if (height == -1) {
     889  889              if (minWidthCache == -1) {
     890  890                  minWidthCache = computeMinWidth(-1);
     891  891                  sizeCacheClear = false;
     892  892              }
     893  893              return minWidthCache;
     894  894          } else {
     895  895              return computeMinWidth(height);
     896  896          }
     897  897      }
     898  898
     899  899      @Override public double minHeight(double width) {
     900  900          if (width == -1) {
     901  901              if (minHeightCache == -1) {
     902  902                  minHeightCache = computeMinHeight(-1);
     903  903                  sizeCacheClear = false;
     904  904              }
     905  905              return minHeightCache;
     906  906          } else {
     907  907              return computeMinHeight(width);
     908  908          }
     909  909      }
     910  910
     911  911      // PENDING_DOC_REVIEW
     912  912      /**
     913  913       * Calculates the preferred width of this {@code Parent}. The default
     914  914       * implementation calculates this width as the width of the area occupied
     915  915       * by its managed children when they are positioned at their
     916  916       * current positions at their preferred widths.
     917  917       *
     918  918       * @param height the height that should be used if preferred width depends
     919  919       *      on it
     920  920       * @return the calculated preferred width
     921  921       */
     922  922      protected double computePrefWidth(double height) {
     923  923          double minX = 0;
     924  924          double maxX = 0;
     925  925          for (int i=0, max=children.size(); i<max; i++) {
     926  926              Node node = children.get(i);
     927  927              if (node.isManaged()) {
     928  928                  final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
     929  929                  minX = Math.min(minX, x);
     930  930                  maxX = Math.max(maxX, x + node.prefWidth(-1));
     931  931              }
     932  932          }
     933  933          return maxX - minX;
     934  934      }
     935  935
     936  936      // PENDING_DOC_REVIEW
     937  937      /**
     938  938       * Calculates the preferred height of this {@code Parent}. The default
     939  939       * implementation calculates this height as the height of the area occupied
     940  940       * by its managed children when they are positioned at their current
     941  941       * positions at their preferred heights.
     942  942       *
     943  943       * @param width the width that should be used if preferred height depends
     944  944       *      on it
     945  945       * @return the calculated preferred height
     946  946       */
     947  947      protected double computePrefHeight(double width) {
     948  948          double minY = 0;
     949  949          double maxY = 0;
     950  950          for (int i=0, max=children.size(); i<max; i++) {
     951  951              Node node = children.get(i);
     952  952              if (node.isManaged()) {
     953  953                  final double y = node.getLayoutBounds().getMinY() + node.getLayoutY();
     954  954                  minY = Math.min(minY, y);
     955  955                  maxY = Math.max(maxY, y + node.prefHeight(-1));
     956  956              }
     957  957          }
     958  958          return maxY - minY;
     959  959      }
     960  960
     961  961      /**
     962  962       * Calculates the minimum width of this {@code Parent}. The default
     963  963       * implementation simply returns the pref width.
     964  964       *
     965  965       * @param height the height that should be used if min width depends
     966  966       *      on it
     967  967       * @return the calculated min width
     968  968       */
     969  969      protected double computeMinWidth(double height) {
     970  970          return prefWidth(height);
     971  971      }
     972  972
     973  973      // PENDING_DOC_REVIEW
     974  974      /**
     975  975       * Calculates the min height of this {@code Parent}. The default
     976  976       * implementation simply returns the pref height;
     977  977       *
     978  978       * @param width the width that should be used if min height depends
     979  979       *      on it
     980  980       * @return the calculated min height
     981  981       */
     982  982      protected double computeMinHeight(double width) {
     983  983          return prefHeight(width);
     984  984      }
     985  985
     986  986      /**
     987  987       * Calculates the baseline offset based on the first managed child. If there
     988  988       * is no such child, returns {@link Node#getBaselineOffset()}.
     989  989       *
     990  990       * @return baseline offset
     991  991       */
     992  992      @Override public double getBaselineOffset() {
     993  993          for (int i=0, max=children.size(); i<max; i++) {
     994  994              final Node child = children.get(i);
     995  995              if (child.isManaged()) {
     996  996                  return child.getLayoutBounds().getMinY() + child.getLayoutY() + child.getBaselineOffset();
     997  997              }
     998  998          }
     999  999          return super.getBaselineOffset();
    1000 1000      }
    1001 1001
    1002 1002      /**
    1003 1003       * Executes a top-down layout pass on the scene graph under this parent.
    1004 1004       */
    1005 1005      public final void layout() {
    1006 1006          if (isNeedsLayout()) {
    1007 1007              performingLayout = true;
    1008 1008
    1009 1009              PlatformLogger logger = Logging.getLayoutLogger();
    1010 1010              if (logger.isLoggable(PlatformLogger.FINE)) {
    1011 1011                  logger.fine(this+" size: "+
    1012 1012                          getLayoutBounds().getWidth()+" x "+getLayoutBounds().getHeight());
    1013 1013              }
    1014 1014
    1015 1015              // layout the children in this parent.
    1016 1016              layoutChildren();
    1017 1017              // clear flag before recursing down so requestLayout calls arn't swallowed
    1018 1018              setNeedsLayout(false);
    1019 1019
    1020 1020              // Perform layout on each child, hoping it has random access performance!
    1021 1021              for (int i=0, max=children.size(); i<max; i++) {
    1022 1022                  final Node child = children.get(i);
    1023 1023                  if (child instanceof Parent) {
    1024 1024                      ((Parent) child).layout();
    1025 1025                  }
    1026 1026              }
    1027 1027              performingLayout = false;
    1028 1028          }
    1029 1029      }
    1030 1030
    1031 1031      /**
    1032 1032       * Invoked during the layout pass to layout the children in this
    1033 1033       * {@code Parent}. By default it will only set the size of managed,
    1034 1034       * resizable content to their preferred sizes and does not do any node
    1035 1035       * positioning.
    1036 1036       * <p>
    1037 1037       * Subclasses should override this function to layout content as needed.
    1038 1038       */
    1039 1039      protected void layoutChildren() {
    1040 1040          for (int i=0, max=children.size(); i<max; i++) {
    1041 1041              final Node node = children.get(i);
    1042 1042              if (node.isResizable() && node.isManaged()) {
    1043 1043                  node.autosize();
    1044 1044              }
    1045 1045          }
    1046 1046      }
    1047 1047
    1048 1048      /**
    1049 1049       * This field is managed by the Scene, and set on any node which is the
    1050 1050       * root of a Scene.
    1051 1051       */
    1052 1052      private boolean sceneRoot = false;
    1053 1053
    1054 1054      /**
    1055 1055       * Keeps track of whether this node is a layout root. This is updated
    1056 1056       * whenever the sceneRoot field changes, or whenever the managed
    1057 1057       * property changes.
    1058 1058       */
    1059 1059      private boolean layoutRoot = false;
    1060 1060      @Override final void notifyManagedChanged() {
    1061 1061          layoutRoot = !isManaged() || sceneRoot;
    1062 1062      }
    1063 1063
    1064 1064      /***********************************************************************
    1065 1065       *                                                                     *
    1066 1066       *                         Stylesheet Handling                         *
    1067 1067       *                                                                     *
    1068 1068       **********************************************************************/
    1069 1069
    1070 1070
    1071 1071      /**

↓ open down ↓ | 1071 lines elided | ↑ open up ↑

    1072 1072       * A ObservableList of string URLs linking to the stylesheets to use with this scene's
    1073 1073       * contents. For additional information about using CSS with the
    1074 1074       * scene graph, see the <a href="doc-files/cssref.html">CSS Reference
    1075 1075       * Guide</a>.
    1076 1076       */
    1077 1077      private final ObservableList<String> stylesheets = new TrackableObservableList<String>() {
    1078 1078          @Override
    1079 1079          protected void onChanged(Change<String> c) {
    1080 1080              final Scene scene = getScene();
    1081 1081              if (scene != null) {
    1082      -
    1083      -                scene.styleManager.parentStylesheetsChanged(Parent.this, c);
         1082 +
         1083 +                StyleManager sm = getStyleManager();
         1084 +                if (sm != null) sm.stylesheetsChanged(c);
         1085 +
    1084 1086                  // RT-9784 - if stylesheet is removed, reset styled properties to
    1085 1087                  // their initial value.
    1086 1088                  while(c.next()) {
    1087 1089                      if (c.wasRemoved() == false) continue;
    1088 1090                      impl_cssResetInitialValues();
    1089 1091                      break; // no point in resetting more than once...
    1090 1092                  }
    1091 1093
    1092 1094                  impl_reapplyCSS();
    1093 1095              }

    1094 1096          }
    1095 1097      };
    1096 1098

↓ open down ↓ | 3 lines elided | ↑ open up ↑

    1097 1099      /**
    1098 1100       * Gets an observable list of string URLs linking to the stylesheets to use
    1099 1101       * with this Parent's contents. For additional information about using CSS
    1100 1102       * with the scene graph, see the <a href="doc-files/cssref.html">CSS Reference
    1101 1103       * Guide</a>.
    1102 1104       *
    1103 1105       * @return the list of stylesheets to use with this Parent
    1104 1106       */
    1105 1107      public final ObservableList<String> getStylesheets() { return stylesheets; }
    1106 1108
         1109 +    private StyleManager styleManager;
         1110 +    private StyleManager getStyleManager() {
         1111 +
         1112 +        final Scene scene = getScene();
         1113 +        final boolean hasStylesheets = (getStylesheets().isEmpty() == false);
         1114 +
         1115 +        if (hasStylesheets == false || scene == null) {
         1116 +
         1117 +            styleManager = null;
         1118 +
         1119 +        } else if (styleManager == null) {
         1120 +
         1121 +            Parent parent = getParent();
         1122 +            StyleManager sm = null;
         1123 +            while (parent != null && (sm = parent.styleManager) == null) {
         1124 +                parent = parent.getParent();
         1125 +            }
         1126 +            if (sm == null) sm = scene.styleManager;
         1127 +            styleManager = StyleManager.createStyleManager(this, sm);
         1128 +        }
         1129 +
         1130 +        return styleManager;
         1131 +    }
    1107 1132      /**
    1108 1133       * This method recurses up the parent chain until parent is null. As the
    1109 1134       * stack unwinds, if the Parent has stylesheets, they are added to the
    1110 1135       * list.
    1111 1136       *
    1112 1137       * It is possible to override this method to stop the recursion. This allows
    1113 1138       * a Parent to have a set of stylesheets distinct from its Parent.
    1114 1139       *
    1115 1140       * @treatAsPrivate implementation detail
    1116 1141       * @deprecated This is an internal API that is not intended for use and will be removed in the next version

    1117 1142       */
    1118 1143      @Deprecated // SB-dependency: RT-21247 has been filed to track this
    1119 1144      public /* Do not make this final! */ List<String> impl_getAllParentStylesheets() {
    1120 1145
    1121 1146          List<String> list = null;
    1122 1147          final Parent myParent = getParent();
    1123 1148          if (myParent != null) {
    1124 1149
    1125 1150              //
    1126 1151              // recurse so that stylesheets of Parents closest to the root are
    1127 1152              // added to the list first. The ensures that declarations for
    1128 1153              // stylesheets further down the tree (closer to the leaf) have
    1129 1154              // a higer ordinal in the cascade.
    1130 1155              //
    1131 1156              list = myParent.impl_getAllParentStylesheets();
    1132 1157          }
    1133 1158
    1134 1159          if (stylesheets != null && stylesheets.isEmpty() == false) {
    1135 1160              if (list == null) list = new ArrayList<String>(stylesheets.size());
    1136 1161              for (int n=0,nMax=stylesheets.size(); n<nMax; n++)
    1137 1162                  list.add(stylesheets.get(n));
    1138 1163          }

↓ open down ↓ | 22 lines elided | ↑ open up ↑

    1139 1164
    1140 1165          return list;
    1141 1166
    1142 1167      }
    1143 1168
    1144 1169      /**
    1145 1170       * @treatAsPrivate implementation detail
    1146 1171       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    1147 1172       */
    1148 1173      @Deprecated
    1149      -    @Override public void impl_processCSS(boolean reapply) {
         1174 +    @Override public void impl_processCSS(StyleManager styleManager, boolean reapply) {
         1175 +
         1176 +        final StyleManager parentStyleManager = getStyleManager();
         1177 +        final StyleManager relevantStyleManager =
         1178 +                parentStyleManager != null ? parentStyleManager : styleManager;
         1179 +
    1150 1180          // Determine whether we will need to reapply from here on down
    1151 1181          boolean flag = reapply || cssFlag == CSSFlags.REAPPLY;
    1152 1182          // Let the super implementation handle CSS for this node
    1153      -        super.impl_processCSS(flag);
         1183 +        super.impl_processCSS(relevantStyleManager, flag);
    1154 1184          // For each child, process CSS
    1155 1185          for (int i=0, max=children.size(); i<max; i++) {
    1156 1186              final Node kid = children.get(i);
    1157 1187              if (kid != null) {
    1158      -                kid.impl_processCSS(flag);
         1188 +                kid.impl_processCSS(relevantStyleManager, flag);
    1159 1189              }
    1160 1190          }
    1161 1191      }
    1162 1192
    1163 1193      /**
    1164 1194       * @treatAsPrivate implementation detail
    1165 1195       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    1166 1196       */
    1167 1197      @Deprecated
    1168 1198      @Override public void impl_cssResetInitialValues() {

    1169 1199          // RT-9784
    1170 1200          super.impl_cssResetInitialValues();
    1171 1201
    1172 1202          for (int i=0, max=children.size(); i<max; i++) {
    1173 1203              final Node kid = children.get(i);
    1174 1204              if (kid != null) {
    1175 1205                  kid.impl_cssResetInitialValues();
    1176 1206              }
    1177 1207          }
    1178 1208      }
    1179 1209
    1180 1210
    1181 1211      /***********************************************************************

↓ open down ↓ | 13 lines elided | ↑ open up ↑

    1182 1212       *                               Misc                                  *
    1183 1213       *                                                                     *
    1184 1214       *  Initialization and other functions                                 *
    1185 1215       *                                                                     *
    1186 1216       **********************************************************************/
    1187 1217
    1188 1218
    1189 1219      /**
    1190 1220       * Constructs a new {@code Parent}.
    1191 1221       */
    1192      -    protected Parent() { }
         1222 +    protected Parent() {
         1223 +    }
    1193 1224
    1194 1225      /**
    1195 1226       * @treatAsPrivate implementation detail
    1196 1227       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    1197 1228       */
    1198 1229      @Deprecated
    1199 1230      protected @Override PGNode impl_createPGNode() {
    1200 1231          return Toolkit.getToolkit().createPGGroup();
    1201 1232      }
    1202 1233

    1203 1234      PGGroup getPGGroup() {
    1204 1235          return ((PGGroup) impl_getPGNode());
    1205 1236      }
    1206 1237
    1207 1238
    1208 1239      /***************************************************************************
    1209 1240       *                                                                         *
    1210 1241       *                         Bounds Computations                             *
    1211 1242       *                                                                         *
    1212 1243       *  This code originated in GroupBoundsHelper (part of javafx-sg-common)   *
    1213 1244       *  but has been ported here to the FX side since we cannot rely on the PG *
    1214 1245       *  side for computing the bounds (due to the decoupling of the two        *
    1215 1246       *  scenegraphs for threading and other purposes).                         *
    1216 1247       *                                                                         *
    1217 1248       *  Unfortunately, we cannot simply reuse GroupBoundsHelper without some  *
    1218 1249       *  major (and hacky) modification due to the fact that GroupBoundsHelper  *
    1219 1250       *  relies on PG state and we need to do similar things here that rely on  *
    1220 1251       *  core scenegraph state. Unfortunately, that means we made a port.       *
    1221 1252       *                                                                         *
    1222 1253       **************************************************************************/
    1223 1254
    1224 1255      private BaseBounds tmp = new RectBounds();
    1225 1256
    1226 1257      /**
    1227 1258       * The cached bounds for the Group. If the cachedBounds are invalid
    1228 1259       * then we have no history of what the bounds are, or were.
    1229 1260       */
    1230 1261      private BaseBounds cachedBounds = new RectBounds();
    1231 1262
    1232 1263      /**
    1233 1264       * Indicates that the cachedBounds is invalid (or old) and need to be recomputed.
    1234 1265       * If cachedBoundsInvalid is true, but cachedBounds is not empty,
    1235 1266       * then when we recompute the cachedBounds we can consider the
    1236 1267       * values in cachedBounds to represent the last valid bounds for the group.
    1237 1268       * This is useful for several fast paths.
    1238 1269       */
    1239 1270      private boolean cachedBoundsInvalid = true;
    1240 1271
    1241 1272      /**
    1242 1273       * This set is used to track all of the children of this group which are
    1243 1274       * dirty. It is only used in cases where the number of children is > some
    1244 1275       * value (currently 10). For very wide trees, this can provide a very
    1245 1276       * important speed boost. For the sake of memory consumption, this is
    1246 1277       * null unless the number of children ever crosses the threshold where
    1247 1278       * it will be activated.
    1248 1279       */
    1249 1280      private LinkedHashSet<Node> dirtyChildren = null;
    1250 1281
    1251 1282      private Node top;
    1252 1283      private Node left;
    1253 1284      private Node bottom;
    1254 1285      private Node right;
    1255 1286      private Node near;
    1256 1287      private Node far;
    1257 1288
    1258 1289      /**
    1259 1290       * @treatAsPrivate implementation detail
    1260 1291       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    1261 1292       */
    1262 1293      @Deprecated
    1263 1294      @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
    1264 1295          // If we have no children, our bounds are invalid
    1265 1296          if (children.isEmpty()) {
    1266 1297              return bounds.makeEmpty();
    1267 1298          }
    1268 1299
    1269 1300          if (tx.isTranslateOrIdentity()) {
    1270 1301              // this is a transform which is only doing translations, or nothing
    1271 1302              // at all (no scales, rotates, or shears)
    1272 1303              // so in this case we can easily use the cached bounds
    1273 1304              if (cachedBoundsInvalid) recomputeBounds();
    1274 1305              if (!tx.isIdentity()) {
    1275 1306                  bounds = bounds.deriveWithNewBounds((float)(cachedBounds.getMinX() + tx.getMxt()),
    1276 1307                                   (float)(cachedBounds.getMinY() + tx.getMyt()),
    1277 1308                                   (float)(cachedBounds.getMinZ() + tx.getMzt()),
    1278 1309                                   (float)(cachedBounds.getMaxX() + tx.getMxt()),
    1279 1310                                   (float)(cachedBounds.getMaxY() + tx.getMyt()),
    1280 1311                                   (float)(cachedBounds.getMaxZ() + tx.getMzt()));
    1281 1312              } else {
    1282 1313                  bounds = bounds.deriveWithNewBounds(cachedBounds);
    1283 1314              }
    1284 1315
    1285 1316              return bounds;
    1286 1317          } else {
    1287 1318              // there is a scale, shear, or rotation happening, so need to
    1288 1319              // do the full transform!
    1289 1320              double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE;
    1290 1321              double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE, maxZ = Double.MIN_VALUE;
    1291 1322              boolean first = true;
    1292 1323              for (int i=0, max=children.size(); i<max; i++) {
    1293 1324                  final Node node = children.get(i);
    1294 1325                  if (node.isVisible()) {
    1295 1326                      bounds = node.getTransformedBounds(bounds, tx);
    1296 1327                      // if the bounds of the child are invalid, we don't want
    1297 1328                      // to use those in the remaining computations.
    1298 1329                      if (bounds.isEmpty()) continue;
    1299 1330                      if (first) {
    1300 1331                          minX = bounds.getMinX();
    1301 1332                          minY = bounds.getMinY();
    1302 1333                          minZ = bounds.getMinZ();
    1303 1334                          maxX = bounds.getMaxX();
    1304 1335                          maxY = bounds.getMaxY();
    1305 1336                          maxZ = bounds.getMaxZ();
    1306 1337                          first = false;
    1307 1338                      } else {
    1308 1339                          minX = Math.min(bounds.getMinX(), minX);
    1309 1340                          minY = Math.min(bounds.getMinY(), minY);
    1310 1341                          minZ = Math.min(bounds.getMinZ(), minZ);
    1311 1342                          maxX = Math.max(bounds.getMaxX(), maxX);
    1312 1343                          maxY = Math.max(bounds.getMaxY(), maxY);
    1313 1344                          maxZ = Math.max(bounds.getMaxZ(), maxZ);
    1314 1345                      }
    1315 1346                  }
    1316 1347              }
    1317 1348              // if "first" is still true, then we didn't have any children with
    1318 1349              // non-empty bounds and thus we must return an empty bounds,
    1319 1350              // otherwise we have non-empty bounds so go for it.
    1320 1351              if (first)
    1321 1352                  bounds.makeEmpty();
    1322 1353              else
    1323 1354                  bounds = bounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ,
    1324 1355                          (float)maxX, (float)maxY, (float)maxZ);
    1325 1356
    1326 1357              return bounds;
    1327 1358          }
    1328 1359      }
    1329 1360
    1330 1361      // Returns TRUE if the bounds have changed, FALSE otherwise
    1331 1362      boolean childAdded(Node node) {
    1332 1363          // If there is no history as to what the bounds were (ie, if
    1333 1364          // cachedBounds are empty) then we punt since the next call
    1334 1365          // to get the bounds will force a full recomputation anyway.
    1335 1366
    1336 1367          if (cachedBounds.isEmpty()) {
    1337 1368              return true;
    1338 1369          }
    1339 1370
    1340 1371          // If one of the edges has been cleared by the removal of a node,
    1341 1372          // then there is no point to continuing because we will have to
    1342 1373          // completely revalidate later.
    1343 1374          if (top == null || bottom == null || left == null || right == null
    1344 1375                  || near == null || far == null) {
    1345 1376              cachedBounds.makeEmpty();
    1346 1377              cachedBoundsInvalid = true;
    1347 1378              return true;
    1348 1379          }
    1349 1380
    1350 1381          // If we got here then the top/bottom/left/right/near/far are still set
    1351 1382          // to a valid node and we know the old size of the Group. So what we are
    1352 1383          // going to do is execute a fastpath where we figure out if any of the
    1353 1384          // new Nodes "extend" the bounds, and if so, they will form the new
    1354 1385          // edges and the cachedBounds will be updated accordingly. We will
    1355 1386          // also communicate that as a result of adding these Nodes, the
    1356 1387          // bounds have changed.
    1357 1388
    1358 1389          boolean extended = false;
    1359 1390
    1360 1391          // set to indicate which edges need to be cleared so we can recompute
    1361 1392          if (node.isVisible()) {
    1362 1393              tmp = node.getTransformedBounds(tmp, BaseTransform.IDENTITY_TRANSFORM);
    1363 1394              if (!tmp.isEmpty()) {
    1364 1395                  node.boundsChanged = false;
    1365 1396                  double tmpx = tmp.getMinX();
    1366 1397                  double tmpy = tmp.getMinY();
    1367 1398                  double tmpz = tmp.getMinZ();
    1368 1399                  double tmpx2 = tmp.getMaxX();
    1369 1400                  double tmpy2 = tmp.getMaxY();
    1370 1401                  double tmpz2 = tmp.getMaxZ();
    1371 1402                  double cx = cachedBounds.getMinX();
    1372 1403                  double cy = cachedBounds.getMinY();
    1373 1404                  double cz = cachedBounds.getMinZ();
    1374 1405                  double cx2 = cachedBounds.getMaxX();
    1375 1406                  double cy2 = cachedBounds.getMaxY();
    1376 1407                  double cz2 = cachedBounds.getMaxZ();
    1377 1408
    1378 1409                  double minX = Math.min(tmpx, cx);
    1379 1410                  double minY = Math.min(tmpy, cy);
    1380 1411                  double minZ = Math.min(tmpz, cz);
    1381 1412                  double maxX = Math.max(tmpx2, cx2);
    1382 1413                  double maxY = Math.max(tmpy2, cy2);
    1383 1414                  double maxZ = Math.max(tmpz2, cz2);
    1384 1415
    1385 1416                  if (tmpy < cy) { extended = true; top = node; }
    1386 1417                  if (tmpx < cx) { extended = true; left = node; }
    1387 1418                  if (tmpz < cz) { extended = true; near = node; }
    1388 1419                  if (tmpy2 > cy2) { extended = true; bottom = node; }
    1389 1420                  if (tmpx2 > cx2) { extended = true; right = node; }
    1390 1421                  if (tmpz2 > cz2) { extended = true; far = node; }
    1391 1422
    1392 1423                  // update the cached bounds
    1393 1424                  cachedBounds = cachedBounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ,
    1394 1425                          (float)maxX, (float)maxY, (float)maxZ);
    1395 1426              }
    1396 1427          }
    1397 1428          // return whether or not the bounds have been extended (and thus, are
    1398 1429          // dirty)
    1399 1430          return extended;
    1400 1431      }
    1401 1432
    1402 1433      // This is called when either the child is actually removed, OR IF IT IS
    1403 1434      // TOGGLED TO BE INVISIBLE. This is because in both cases it needs to be
    1404 1435      // cleared from the state which manages bounds.
    1405 1436      boolean childRemoved(Node node) {
    1406 1437          // clear this flag because Group no longer knows whether the bounds
    1407 1438          // have changed or not relative to itself
    1408 1439          node.boundsChanged = false;
    1409 1440          // If there is no history as to what the bounds were (ie, if
    1410 1441          // cachedBounds are empty) then we punt since the next call
    1411 1442          // to get the bounds will force a full recomputation anyway.
    1412 1443
    1413 1444          if (cachedBounds.isEmpty()) {
    1414 1445              return true;
    1415 1446          }
    1416 1447
    1417 1448          // Since there was some prior size to the Group, all the nodes that
    1418 1449          // were removed are checked to see if they formed one of the edges. If
    1419 1450          // so, the edge variable is cleared
    1420 1451          if (node == top) top = null;
    1421 1452          if (node == left) left = null;
    1422 1453          if (node == bottom) bottom = null;
    1423 1454          if (node == right) right = null;
    1424 1455          if (node == near) near = null;
    1425 1456          if (node == far) far = null;
    1426 1457
    1427 1458          // If one of the edges has been cleared by the removal of a node,
    1428 1459          // then there is no point to continuing because we will have to
    1429 1460          // completely revalidate later.
    1430 1461          if (top == null || bottom == null || left == null || right == null
    1431 1462                  || near == null || far == null) {
    1432 1463              cachedBounds.makeEmpty();
    1433 1464              cachedBoundsInvalid = true;
    1434 1465              top = left = bottom = right = near = far = null;
    1435 1466              return true;
    1436 1467          }
    1437 1468
    1438 1469          // Return false since nothing has changed that will affect the bounds.
    1439 1470          return false;
    1440 1471      }
    1441 1472
    1442 1473      /**
    1443 1474       * Recomputes the bounds from scratch and saves the cached bounds.
    1444 1475       */
    1445 1476      void recomputeBounds() {
    1446 1477          // fast path for case of no children
    1447 1478          if (children.isEmpty()) {
    1448 1479              if (dirtyChildren != null) dirtyChildren.clear();
    1449 1480              cachedBoundsInvalid = false;
    1450 1481              cachedBounds.makeEmpty();
    1451 1482              return;
    1452 1483          }
    1453 1484
    1454 1485          // fast path for case of 1 child
    1455 1486          if (children.size() == 1) {
    1456 1487              if (dirtyChildren != null) dirtyChildren.clear();
    1457 1488              cachedBoundsInvalid = false;
    1458 1489              Node node = children.get(0);
    1459 1490              if (node == null) return;
    1460 1491
    1461 1492              if (node.isVisible()) {
    1462 1493                  cachedBounds = node.getTransformedBounds(cachedBounds, BaseTransform.IDENTITY_TRANSFORM);
    1463 1494              } else {
    1464 1495                  cachedBounds.makeEmpty();
    1465 1496              }
    1466 1497              node.boundsChanged = false;
    1467 1498              return;
    1468 1499          }
    1469 1500
    1470 1501          // We will attempt to use a fastpath through this function. We can use
    1471 1502          // the fastpath as long as we have an old size of the Group recorded
    1472 1503          // (cachedBounds is invalid but not empty), and each of the edge
    1473 1504          // variables have a value.
    1474 1505          if (cachedBounds != null
    1475 1506              && !cachedBounds.isEmpty()
    1476 1507              && top != null
    1477 1508              && left != null
    1478 1509              && bottom != null
    1479 1510              && right != null
    1480 1511              && near != null
    1481 1512              && far != null)
    1482 1513          {
    1483 1514              // The fastpath will simply iterate over each of the Nodes in the
    1484 1515              // group. If any node has "dirty" bounds, then I know that I must
    1485 1516              // recompute its bounds. When I do so, I will take special care
    1486 1517              // if it was once one of my edges, and also special care if it has
    1487 1518              // moved to become a new edge.
    1488 1519
    1489 1520              // First, look at the nodes which form the top, left, bottom, right,
    1490 1521              // near and far edges. If any of them are now invisible (visible = false)
    1491 1522              // then they no longer contribute to the bounds of the group and
    1492 1523              // therefore, this fast path cannot be used
    1493 1524              if (top.isVisible() && left.isVisible() && bottom.isVisible() && right.isVisible()
    1494 1525                      && near.isVisible() && far.isVisible()) {
    1495 1526                  // This fastpath becomes degenerate if I get to the end only to
    1496 1527                  // discover that the very last node has moved inward, in which
    1497 1528                  // case I have to throw away my work and simply sum up all the
    1498 1529                  // nodes in the group.
    1499 1530                  boolean quit = false;
    1500 1531
    1501 1532                  // These are local references to the cached bounds vars
    1502 1533                  double cx = cachedBounds.getMinX();
    1503 1534                  double cy = cachedBounds.getMinY();
    1504 1535                  double cz = cachedBounds.getMinZ();
    1505 1536                  double cx2 = cachedBounds.getMaxX();
    1506 1537                  double cy2 = cachedBounds.getMaxY();
    1507 1538                  double cz2 = cachedBounds.getMaxZ();
    1508 1539
    1509 1540                  // These indicate the bounds of the Group as computed by this
    1510 1541                  // function (had to add the 'b' suffix to suppress a spurious
    1511 1542                  // compiler error message)
    1512 1543                  double minXb=cx;
    1513 1544                  double minYb=cy;
    1514 1545                  double minZb=cz;
    1515 1546                  double maxXb=cx2;
    1516 1547                  double maxYb=cy2;
    1517 1548                  double maxZb=cz2;
    1518 1549
    1519 1550                  // iterate over all the dirty nodes
    1520 1551                  if (dirtyChildren != null) {
    1521 1552                      for (Node node : dirtyChildren) {
    1522 1553                          if (node.isVisible() && node.boundsChanged) {
    1523 1554                              tmp = node.getTransformedBounds(tmp, BaseTransform.IDENTITY_TRANSFORM);
    1524 1555                              if (tmp.isEmpty()) {
    1525 1556                                  // If this node formed one of the edges, and if it has
    1526 1557                                  // moved off that edge (inward), then we break out of this
    1527 1558                                  // loop and quit the fast path
    1528 1559                                  if (node == top) { quit = true; break; }
    1529 1560                                  if (node == left) { quit = true; break; }
    1530 1561                                  if (node == bottom) { quit = true; break; }
    1531 1562                                  if (node == right) { quit = true; break; }
    1532 1563                                  if (node == near) { quit = true; break; }
    1533 1564                                  if (node == far) { quit = true; break; }
    1534 1565                              } else {
    1535 1566                                  double tmpx = tmp.getMinX();
    1536 1567                                  double tmpy = tmp.getMinY();
    1537 1568                                  double tmpz = tmp.getMinZ();
    1538 1569                                  double tmpx2 = tmp.getMaxX();
    1539 1570                                  double tmpy2 = tmp.getMaxY();
    1540 1571                                  double tmpz2 = tmp.getMaxZ();
    1541 1572
    1542 1573                                  // If this node formed one of the edges, and if it has
    1543 1574                                  // moved off that edge (inward), then we break out of this
    1544 1575                                  // loop and quit the fast path
    1545 1576                                  if (node == top && tmpy > cy) { quit = true; break; }
    1546 1577                                  if (node == left && tmpx > cx) { quit = true; break; }
    1547 1578                                  if (node == near && tmpz > cz) { quit = true; break; }
    1548 1579                                  if (node == bottom && tmpy2 < cy2) { quit = true; break; }
    1549 1580                                  if (node == right && tmpx2 < cx2) { quit = true; break; }
    1550 1581                                  if (node == far && tmpz2 < cz2) { quit = true; break; }
    1551 1582
    1552 1583                                  // If this node forms an edge, then we will set it to be the
    1553 1584                                  // node for this edge and update the min/max values
    1554 1585                                  if (tmpy < minYb) { minYb = tmpy; top = node; }
    1555 1586                                  if (tmpx < minXb) { minXb = tmpx; left = node; }
    1556 1587                                  if (tmpz < minZb) { minZb = tmpz; near = node; }
    1557 1588                                  if (tmpy2 > maxYb) { maxYb = tmpy2; bottom = node; }
    1558 1589                                  if (tmpx2 > maxXb) { maxXb = tmpx2; right = node; }
    1559 1590                                  if (tmpz2 > maxZb) { maxZb = tmpz2; far = node; }
    1560 1591                              }
    1561 1592                          }
    1562 1593                          node.boundsChanged = false;
    1563 1594                      }
    1564 1595                  } else {
    1565 1596                      for (int i=0, max=children.size(); i<max; i++) {
    1566 1597                          final Node node = children.get(i);
    1567 1598                          if (node == null)
    1568 1599                              continue;
    1569 1600                          if (node.isVisible() && node.boundsChanged) {
    1570 1601                              tmp = node.getTransformedBounds(tmp, BaseTransform.IDENTITY_TRANSFORM);
    1571 1602                              if (tmp.isEmpty()) {
    1572 1603                                  // If this node formed one of the edges, and if it has
    1573 1604                                  // moved off that edge (inward), then we break out of this
    1574 1605                                  // loop and quit the fast path
    1575 1606                                  if (node == top) { quit = true; break; }
    1576 1607                                  if (node == left) { quit = true; break; }
    1577 1608                                  if (node == bottom) { quit = true; break; }
    1578 1609                                  if (node == right) { quit = true; break; }
    1579 1610                                  if (node == near) { quit = true; break; }
    1580 1611                                  if (node == far) { quit = true; break; }
    1581 1612                              } else {
    1582 1613                                  double tmpx = tmp.getMinX();
    1583 1614                                  double tmpy = tmp.getMinY();
    1584 1615                                  double tmpz = tmp.getMinZ();
    1585 1616                                  double tmpx2 = tmp.getMaxX();
    1586 1617                                  double tmpy2 = tmp.getMaxY();
    1587 1618                                  double tmpz2 = tmp.getMaxZ();
    1588 1619
    1589 1620                                  // If this node formed one of the edges, and if it has
    1590 1621                                  // moved off that edge (inward), then we break out of this
    1591 1622                                  // loop and quit the fast path
    1592 1623                                  if (node == top && tmpy > cy) { quit = true; break; }
    1593 1624                                  if (node == left && tmpx > cx) { quit = true; break; }
    1594 1625                                  if (node == near && tmpz > cz) { quit = true; break; }
    1595 1626                                  if (node == bottom && tmpy2 < cy2) { quit = true; break; }
    1596 1627                                  if (node == right && tmpx2 < cx2) { quit = true; break; }
    1597 1628                                  if (node == far && tmpz2 < cz2) { quit = true; break; }
    1598 1629
    1599 1630                                  // If this node forms an edge, then we will set it to be the
    1600 1631                                  // node for this edge and update the min/max values
    1601 1632                                  if (tmpy < minYb) { minYb = tmpy; top = node; }
    1602 1633                                  if (tmpx < minXb) { minXb = tmpx; left = node; }
    1603 1634                                  if (tmpz < minZb) { minZb = tmpz; near = node; }
    1604 1635                                  if (tmpy2 > maxYb) { maxYb = tmpy2; bottom = node; }
    1605 1636                                  if (tmpx2 > maxXb) { maxXb = tmpx2; right = node; }
    1606 1637                                  if (tmpz2 > maxZb) { maxZb = tmpz2; far = node; }
    1607 1638                              }
    1608 1639                          }
    1609 1640                          node.boundsChanged = false;
    1610 1641                      }
    1611 1642                  }
    1612 1643
    1613 1644                  if (dirtyChildren != null) dirtyChildren.clear();
    1614 1645
    1615 1646                  if (!quit) {
    1616 1647                      // The fastpath succeeded, so we can simply update the
    1617 1648                      // cachedBounds, clear the cachedBoundsInvalid flag and return
    1618 1649                      cachedBoundsInvalid = false;
    1619 1650                      cachedBounds = cachedBounds.deriveWithNewBounds((float)minXb, (float)minYb, (float)minZb,
    1620 1651                              (float)maxXb, (float)maxYb, (float)maxZb);
    1621 1652                      return;
    1622 1653                  }
    1623 1654              }
    1624 1655              // fall through to the long case
    1625 1656          }
    1626 1657
    1627 1658          // These indicate the bounds of the Group as computed by this function
    1628 1659          double minX = 0;
    1629 1660          double minY = 0;
    1630 1661          double minZ = 0;
    1631 1662          double maxX = 0;
    1632 1663          double maxY = 0;
    1633 1664          double maxZ = 0;
    1634 1665
    1635 1666          // The long case is pretty simple -- visit each and every node, get
    1636 1667          // the bounds of each node, figure out the edge nodes, get the min/max
    1637 1668          // for the x/y/x2/y2 and then update cachedBounds and cachedBoundsInvalid
    1638 1669          boolean first = true;
    1639 1670          for (int i=0, max=children.size(); i<max; i++) {
    1640 1671              final Node node = children.get(i);
    1641 1672              if (node == null)
    1642 1673                  continue;
    1643 1674
    1644 1675              node.boundsChanged = false;
    1645 1676              if (node.isVisible()) {
    1646 1677                  tmp = node.getTransformedBounds(tmp, BaseTransform.IDENTITY_TRANSFORM);
    1647 1678                  if (!tmp.isEmpty()) {
    1648 1679                      double tmpx = tmp.getMinX();
    1649 1680                      double tmpy = tmp.getMinY();
    1650 1681                      double tmpz = tmp.getMinZ();
    1651 1682                      double tmpx2 = tmp.getMaxX();
    1652 1683                      double tmpy2 = tmp.getMaxY();
    1653 1684                      double tmpz2 = tmp.getMaxZ();
    1654 1685
    1655 1686                      if (first) {
    1656 1687                          top = left = bottom = right = near = far = node;
    1657 1688                          minX = tmpx;
    1658 1689                          minY = tmpy;
    1659 1690                          minZ = tmpz;
    1660 1691                          maxX = tmpx2;
    1661 1692                          maxY = tmpy2;
    1662 1693                          maxZ = tmpz2;
    1663 1694                          first = false;
    1664 1695                      } else {
    1665 1696                          if (tmpy < minY) { minY = tmpy; top = node; }
    1666 1697                          if (tmpx < minX) { minX = tmpx; left = node; }
    1667 1698                          if (tmpz < minZ) { minZ = tmpz; near = node; }
    1668 1699                          if (tmpy2 > maxY) { maxY = tmpy2; bottom = node; }
    1669 1700                          if (tmpx2 > maxX) { maxX = tmpx2; right = node; }
    1670 1701                          if (tmpz2 > maxZ) { maxZ = tmpz2; far = node; }
    1671 1702                      }
    1672 1703                  }
    1673 1704              }
    1674 1705          }
    1675 1706
    1676 1707          if (dirtyChildren != null) dirtyChildren.clear();
    1677 1708          cachedBoundsInvalid = false;
    1678 1709          if (first) {
    1679 1710              top = left = bottom = right = near = far = null;
    1680 1711              cachedBounds.makeEmpty();
    1681 1712          } else {
    1682 1713              cachedBounds = cachedBounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ,
    1683 1714                      (float)maxX, (float)maxY, (float)maxZ);
    1684 1715          }
    1685 1716      }
    1686 1717
    1687 1718      /**
    1688 1719       * Overridden to make sure boundsInvalid gets set to true
    1689 1720       *
    1690 1721       * @treatAsPrivate implementation detail
    1691 1722       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    1692 1723       */
    1693 1724      @Deprecated
    1694 1725      @Override protected void impl_geomChanged() {
    1695 1726          cachedBoundsInvalid = true;
    1696 1727          super.impl_geomChanged();
    1697 1728      }
    1698 1729
    1699 1730      /**
    1700 1731       * Called by Node whenever its bounds have changed.
    1701 1732       */
    1702 1733      void childBoundsChanged(Node node) {
    1703 1734          if (!node.isVisible()) {
    1704 1735              // the change in bounds of this child node has no bearing on
    1705 1736              // the bounds of the group, so forget it
    1706 1737              return;
    1707 1738          }
    1708 1739
    1709 1740          // mark the node such that the parent knows that the child's bounds
    1710 1741          // are not in sync with this parent. In this way, when the bounds
    1711 1742          // need to be computed, we'll come back and figure out the new bounds
    1712 1743          // for all the children which have boundsChanged set to true
    1713 1744          if (!node.boundsChanged) {
    1714 1745              node.boundsChanged = true;
    1715 1746              if (dirtyChildren != null) dirtyChildren.add(node);
    1716 1747          }
    1717 1748          // go ahead and indicate that the geom has changed for this parent,
    1718 1749          // even though once we figure it all out it may be that the bounds
    1719 1750          // have not changed
    1720 1751          impl_geomChanged();
    1721 1752      }
    1722 1753
    1723 1754      /**
    1724 1755       * Called by node whenever the visibility of the node changes.
    1725 1756       */
    1726 1757      void childVisibilityChanged(Node node) {
    1727 1758          // cachedBoundsInvalid will be set to true in impl_geomChanged();
    1728 1759          cachedBounds.makeEmpty();
    1729 1760          impl_geomChanged();
    1730 1761      }
    1731 1762
    1732 1763      /**
    1733 1764       * @treatAsPrivate implementation detail
    1734 1765       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    1735 1766       */
    1736 1767      @Deprecated
    1737 1768      @Override
    1738 1769      protected boolean impl_computeContains(double localX, double localY) {
    1739 1770          final Point2D tempPt = TempState.getInstance().point;
    1740 1771          for (int i=0, max=children.size(); i<max; i++) {
    1741 1772              final Node node = children.get(i);
    1742 1773              tempPt.x = (float)localX;
    1743 1774              tempPt.y = (float)localY;
    1744 1775              try {
    1745 1776                  node.parentToLocal(tempPt);
    1746 1777              } catch (NoninvertibleTransformException e) {
    1747 1778                  continue;
    1748 1779              }
    1749 1780              if (node.contains(tempPt.x, tempPt.y)) {
    1750 1781                  return true;
    1751 1782              }
    1752 1783          }
    1753 1784          return false;
    1754 1785      }
    1755 1786
    1756 1787      /**
    1757 1788       * @treatAsPrivate implementation detail
    1758 1789       * @deprecated This is an internal API that is not intended for use and will be removed in the next version
    1759 1790       */
    1760 1791      @Deprecated
    1761 1792      public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
    1762 1793          return alg.processContainerNode(this, ctx);
    1763 1794      }
    1764 1795  }

↓ open down ↓ | 562 lines elided | ↑ open up ↑

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX