/**
   * Invoked after the tree has drastically changed structure from a given node down. If the path
   * returned by e.getPath() is of length one and the first element does not identify the current
   * root node the first element should become the new root of the tree.
   *
   * <p>e.path() holds the path to the node.
   *
   * <p>e.childIndices() returns null.
   */
  public void treeStructureChanged(TreeModelEvent e) {
    if (e != null) {
      TreePath changedPath = SwingUtilities2.getTreePath(e, getModel());
      FHTreeStateNode changedNode = getNodeForPath(changedPath, false, false);

      // Check if root has changed, either to a null root, or
      // to an entirely new root.
      if (changedNode == root
          || (changedNode == null
              && ((changedPath == null && treeModel != null && treeModel.getRoot() == null)
                  || (changedPath != null && changedPath.getPathCount() <= 1)))) {
        rebuild(true);
      } else if (changedNode != null) {
        boolean wasExpanded, wasVisible;
        FHTreeStateNode parent = (FHTreeStateNode) changedNode.getParent();

        wasExpanded = changedNode.isExpanded();
        wasVisible = changedNode.isVisible();

        int index = parent.getIndex(changedNode);
        changedNode.collapse(false);
        parent.remove(index);

        if (wasVisible && wasExpanded) {
          int row = changedNode.getRow();
          parent.resetChildrenRowsFrom(row, index, changedNode.getChildIndex());
          changedNode = getNodeForPath(changedPath, false, true);
          changedNode.expand();
        }
        if (treeSelectionModel != null && wasVisible && wasExpanded)
          treeSelectionModel.resetRowSelection();
        if (wasVisible) this.visibleNodesChanged();
      }
    }
  }
  /**
   * Invoked after nodes have been removed from the tree. Note that if a subtree is removed from the
   * tree, this method may only be invoked once for the root of the removed subtree, not once for
   * each individual set of siblings removed.
   *
   * <p>e.path() returns the former parent of the deleted nodes.
   *
   * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending
   * order.
   */
  public void treeNodesRemoved(TreeModelEvent e) {
    if (e != null) {
      int changedIndexs[];
      int maxCounter;
      TreePath parentPath = SwingUtilities2.getTreePath(e, getModel());
      FHTreeStateNode changedParentNode = getNodeForPath(parentPath, false, false);

      changedIndexs = e.getChildIndices();
      // PENDING(scott): make sure that changedIndexs are sorted in
      // ascending order.
      if (changedParentNode != null
          && changedIndexs != null
          && (maxCounter = changedIndexs.length) > 0) {
        Object[] children = e.getChildren();
        boolean isVisible = (changedParentNode.isVisible() && changedParentNode.isExpanded());

        for (int counter = maxCounter - 1; counter >= 0; counter--) {
          changedParentNode.removeChildAtModelIndex(changedIndexs[counter], isVisible);
        }
        if (isVisible) {
          if (treeSelectionModel != null) treeSelectionModel.resetRowSelection();
          if (treeModel.getChildCount(changedParentNode.getUserObject()) == 0
              && changedParentNode.isLeaf()) {
            // Node has become a leaf, collapse it.
            changedParentNode.collapse(false);
          }
          visibleNodesChanged();
        } else if (changedParentNode.isVisible()) visibleNodesChanged();
      }
    }
  }
  /**
   * Invoked after a node (or a set of siblings) has changed in some way. The node(s) have not
   * changed locations in the tree or altered their children arrays, but other attributes have
   * changed and may affect presentation. Example: the name of a file has changed, but it is in the
   * same location in the file system.
   *
   * <p>e.path() returns the path the parent of the changed node(s).
   *
   * <p>e.childIndices() returns the index(es) of the changed node(s).
   */
  public void treeNodesChanged(TreeModelEvent e) {
    if (e != null) {
      int changedIndexs[];
      FHTreeStateNode changedParent =
          getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
      int maxCounter;

      changedIndexs = e.getChildIndices();
      /* Only need to update the children if the node has been
      expanded once. */
      // PENDING(scott): make sure childIndexs is sorted!
      if (changedParent != null) {
        if (changedIndexs != null && (maxCounter = changedIndexs.length) > 0) {
          Object parentValue = changedParent.getUserObject();

          for (int counter = 0; counter < maxCounter; counter++) {
            FHTreeStateNode child = changedParent.getChildAtModelIndex(changedIndexs[counter]);

            if (child != null) {
              child.setUserObject(treeModel.getChild(parentValue, changedIndexs[counter]));
            }
          }
          if (changedParent.isVisible() && changedParent.isExpanded()) visibleNodesChanged();
        }
        // Null for root indicates it changed.
        else if (changedParent == root && changedParent.isVisible() && changedParent.isExpanded()) {
          visibleNodesChanged();
        }
      }
    }
  }
  /**
   * Invoked after nodes have been inserted into the tree.
   *
   * <p>e.path() returns the parent of the new nodes
   *
   * <p>e.childIndices() returns the indices of the new nodes in ascending order.
   */
  public void treeNodesInserted(TreeModelEvent e) {
    if (e != null) {
      int changedIndexs[];
      FHTreeStateNode changedParent =
          getNodeForPath(SwingUtilities2.getTreePath(e, getModel()), false, false);
      int maxCounter;

      changedIndexs = e.getChildIndices();
      /* Only need to update the children if the node has been
      expanded once. */
      // PENDING(scott): make sure childIndexs is sorted!
      if (changedParent != null
          && changedIndexs != null
          && (maxCounter = changedIndexs.length) > 0) {
        boolean isVisible = (changedParent.isVisible() && changedParent.isExpanded());

        for (int counter = 0; counter < maxCounter; counter++) {
          changedParent.childInsertedAtModelIndex(changedIndexs[counter], isVisible);
        }
        if (isVisible && treeSelectionModel != null) treeSelectionModel.resetRowSelection();
        if (changedParent.isVisible()) this.visibleNodesChanged();
      }
    }
  }