26 package javax.swing.tree;
27
28 import javax.swing.event.TreeModelEvent;
29 import java.awt.Rectangle;
30 import java.util.Enumeration;
31 import java.util.Hashtable;
32 import java.util.NoSuchElementException;
33 import java.util.Stack;
34
35 import sun.swing.SwingUtilities2;
36
37 /**
38 * NOTE: This will become more open in a future release.
39 * <p>
40 * <strong>Warning:</strong>
41 * Serialized objects of this class will not be compatible with
42 * future Swing releases. The current serialization support is
43 * appropriate for short term storage or RMI between applications running
44 * the same version of Swing. As of 1.4, support for long term storage
45 * of all JavaBeans™
46 * has been added to the <code>java.beans</code> package.
47 * Please see {@link java.beans.XMLEncoder}.
48 *
49 * @author Scott Violet
50 */
51 @SuppressWarnings("serial") // Same-version serialization only
52 public class FixedHeightLayoutCache extends AbstractLayoutCache {
53 /** Root node. */
54 private FHTreeStateNode root;
55
56 /** Number of rows currently visible. */
57 private int rowCount;
58
59 /**
60 * Used in getting sizes for nodes to avoid creating a new Rectangle
61 * every time a size is needed.
62 */
63 private Rectangle boundsBuffer;
64
65 /**
66 * Maps from TreePath to a FHTreeStateNode.
277 if(path == null)
278 return null;
279
280 FHTreeStateNode node = getNodeForPath(path, true, false);
281
282 if(node != null) {
283 return new VisibleFHTreeStateNodeEnumeration(node);
284 }
285 TreePath parentPath = path.getParentPath();
286
287 node = getNodeForPath(parentPath, true, false);
288 if(node != null && node.isExpanded()) {
289 return new VisibleFHTreeStateNodeEnumeration(node,
290 treeModel.getIndexOfChild(parentPath.getLastPathComponent(),
291 path.getLastPathComponent()));
292 }
293 return null;
294 }
295
296 /**
297 * Marks the path <code>path</code> expanded state to
298 * <code>isExpanded</code>.
299 */
300 public void setExpandedState(TreePath path, boolean isExpanded) {
301 if(isExpanded)
302 ensurePathIsExpanded(path, true);
303 else if(path != null) {
304 TreePath parentPath = path.getParentPath();
305
306 // YECK! Make the parent expanded.
307 if(parentPath != null) {
308 FHTreeStateNode parentNode = getNodeForPath(parentPath,
309 false, true);
310 if(parentNode != null)
311 parentNode.makeVisible();
312 }
313 // And collapse the child.
314 FHTreeStateNode childNode = getNodeForPath(path, true,
315 false);
316
317 if(childNode != null)
318 childNode.collapse(true);
510 changedNode = getNodeForPath(changedPath, false, true);
511 changedNode.expand();
512 }
513 if(treeSelectionModel != null && wasVisible && wasExpanded)
514 treeSelectionModel.resetRowSelection();
515 if(wasVisible)
516 this.visibleNodesChanged();
517 }
518 }
519 }
520
521
522 //
523 // Local methods
524 //
525
526 private void visibleNodesChanged() {
527 }
528
529 /**
530 * Returns the bounds for the given node. If <code>childIndex</code>
531 * is -1, the bounds of <code>parent</code> are returned, otherwise
532 * the bounds of the node at <code>childIndex</code> are returned.
533 */
534 private Rectangle getBounds(FHTreeStateNode parent, int childIndex,
535 Rectangle placeIn) {
536 boolean expanded;
537 int level;
538 int row;
539 Object value;
540
541 if(childIndex == -1) {
542 // Getting bounds for parent
543 row = parent.getRow();
544 value = parent.getUserObject();
545 expanded = parent.isExpanded();
546 level = parent.getLevel();
547 }
548 else {
549 row = parent.getRowToModelIndex(childIndex);
550 value = treeModel.getChild(parent.getUserObject(), childIndex);
551 expanded = false;
552 level = parent.getLevel() + 1;
574 */
575 private void adjustRowCountBy(int changeAmount) {
576 rowCount += changeAmount;
577 }
578
579 /**
580 * Adds a mapping for node.
581 */
582 private void addMapping(FHTreeStateNode node) {
583 treePathMapping.put(node.getTreePath(), node);
584 }
585
586 /**
587 * Removes the mapping for a previously added node.
588 */
589 private void removeMapping(FHTreeStateNode node) {
590 treePathMapping.remove(node.getTreePath());
591 }
592
593 /**
594 * Returns the node previously added for <code>path</code>. This may
595 * return null, if you to create a node use getNodeForPath.
596 */
597 private FHTreeStateNode getMapping(TreePath path) {
598 return treePathMapping.get(path);
599 }
600
601 /**
602 * Sent to completely rebuild the visible tree. All nodes are collapsed.
603 */
604 private void rebuild(boolean clearSelection) {
605 Object rootUO;
606
607 treePathMapping.clear();
608 if(treeModel != null && (rootUO = treeModel.getRoot()) != null) {
609 root = createNodeForValue(rootUO, 0);
610 root.path = new TreePath(rootUO);
611 addMapping(root);
612 if(isRootVisible()) {
613 rowCount = 1;
614 root.row = 0;
770
771 //
772 // Overriden DefaultMutableTreeNode methods
773 //
774
775 /**
776 * Messaged when this node is added somewhere, resets the path
777 * and adds a mapping from path to this node.
778 */
779 public void setParent(MutableTreeNode parent) {
780 super.setParent(parent);
781 if(parent != null) {
782 path = ((FHTreeStateNode)parent).getTreePath().
783 pathByAddingChild(getUserObject());
784 addMapping(this);
785 }
786 }
787
788 /**
789 * Messaged when this node is removed from its parent, this messages
790 * <code>removedFromMapping</code> to remove all the children.
791 */
792 public void remove(int childIndex) {
793 FHTreeStateNode node = (FHTreeStateNode)getChildAt(childIndex);
794
795 node.removeFromMapping();
796 super.remove(childIndex);
797 }
798
799 /**
800 * Messaged to set the user object. This resets the path.
801 */
802 public void setUserObject(Object o) {
803 super.setUserObject(o);
804 if(path != null) {
805 FHTreeStateNode parent = (FHTreeStateNode)getParent();
806
807 if(parent != null)
808 resetChildrenPaths(parent.getTreePath());
809 else
810 resetChildrenPaths(null);
811 }
812 }
813
814 //
815 //
816
817 /**
818 * Returns the index of the receiver in the model.
819 */
820 public int getChildIndex() {
821 return childIndex;
822 }
823
824 /**
825 * Returns the <code>TreePath</code> of the receiver.
826 */
827 public TreePath getTreePath() {
828 return path;
829 }
830
831 /**
832 * Returns the child for the passed in model index, this will
833 * return <code>null</code> if the child for <code>index</code>
834 * has not yet been created (expanded).
835 */
836 public FHTreeStateNode getChildAtModelIndex(int index) {
837 // PENDING: Make this a binary search!
838 for(int counter = getChildCount() - 1; counter >= 0; counter--)
839 if(((FHTreeStateNode)getChildAt(counter)).childIndex == index)
840 return (FHTreeStateNode)getChildAt(counter);
841 return null;
842 }
843
844 /**
845 * Returns true if this node is visible. This is determined by
846 * asking all the parents if they are expanded.
847 */
848 public boolean isVisible() {
849 FHTreeStateNode parent = (FHTreeStateNode)getParent();
850
851 if(parent == null)
852 return true;
853 return (parent.isExpanded() && parent.isVisible());
854 }
855
856 /**
857 * Returns the row of the receiver.
858 */
859 public int getRow() {
860 return row;
861 }
862
863 /**
864 * Returns the row of the child with a model index of
865 * <code>index</code>.
866 */
867 public int getRowToModelIndex(int index) {
868 FHTreeStateNode child;
869 int lastRow = getRow() + 1;
870 int retValue = lastRow;
871
872 // This too could be a binary search!
873 for(int counter = 0, maxCounter = getChildCount();
874 counter < maxCounter; counter++) {
875 child = (FHTreeStateNode)getChildAt(counter);
876 if(child.childIndex >= index) {
877 if(child.childIndex == index)
878 return child.row;
879 if(counter == 0)
880 return getRow() + 1 + index;
881 return child.row - (child.childIndex - index);
882 }
883 }
884 // YECK!
885 return getRow() + 1 + getTotalChildCount() -
948 path = parentPath.pathByAddingChild(getUserObject());
949 addMapping(this);
950 for(int counter = getChildCount() - 1; counter >= 0; counter--)
951 ((FHTreeStateNode)getChildAt(counter)).
952 resetChildrenPaths(path);
953 }
954
955 /**
956 * Removes the receiver, and all its children, from the mapping
957 * table.
958 */
959 protected void removeFromMapping() {
960 if(path != null) {
961 removeMapping(this);
962 for(int counter = getChildCount() - 1; counter >= 0; counter--)
963 ((FHTreeStateNode)getChildAt(counter)).removeFromMapping();
964 }
965 }
966
967 /**
968 * Creates a new node to represent <code>userObject</code>.
969 * This does NOT check to ensure there isn't already a child node
970 * to manage <code>userObject</code>.
971 */
972 protected FHTreeStateNode createChildFor(Object userObject) {
973 int newChildIndex = treeModel.getIndexOfChild
974 (getUserObject(), userObject);
975
976 if(newChildIndex < 0)
977 return null;
978
979 FHTreeStateNode aNode;
980 FHTreeStateNode child = createNodeForValue(userObject,
981 newChildIndex);
982 int childRow;
983
984 if(isVisible()) {
985 childRow = getRowToModelIndex(newChildIndex);
986 }
987 else {
988 childRow = -1;
989 }
990 child.row = childRow;
991 for(int counter = 0, maxCounter = getChildCount();
992 counter < maxCounter; counter++) {
993 aNode = (FHTreeStateNode)getChildAt(counter);
994 if(aNode.childIndex > newChildIndex) {
995 insert(child, counter);
996 return child;
997 }
998 }
999 add(child);
1000 return child;
1001 }
1002
1003 /**
1004 * Adjusts the receiver, and all its children rows by
1005 * <code>amount</code>.
1006 */
1007 protected void adjustRowBy(int amount) {
1008 row += amount;
1009 if(isExpanded) {
1010 for(int counter = getChildCount() - 1; counter >= 0;
1011 counter--)
1012 ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
1013 }
1014 }
1015
1016 /**
1017 * Adjusts this node, its child, and its parent starting at
1018 * an index of <code>index</code> index is the index of the child
1019 * to start adjusting from, which is not necessarily the model
1020 * index.
1021 */
1022 protected void adjustRowBy(int amount, int startIndex) {
1023 // Could check isVisible, but probably isn't worth it.
1024 if(isExpanded) {
1025 // children following startIndex.
1026 for(int counter = getChildCount() - 1; counter >= startIndex;
1027 counter--)
1028 ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
1029 }
1030 // Parent
1031 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1032
1033 if(parent != null) {
1034 parent.adjustRowBy(amount, parent.getIndex(this) + 1);
1035 }
1036 }
1037
1038 /**
1039 * Messaged when the node has expanded. This updates all of
1040 * the receivers children rows, as well as the total row count.
1041 */
1042 protected void didExpand() {
1043 int nextRow = setRowAndChildren(row);
1044 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1045 int childRowCount = nextRow - row - 1;
1046
1047 if(parent != null) {
1048 parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1);
1049 }
1050 adjustRowCountBy(childRowCount);
1051 }
1052
1053 /**
1054 * Sets the receivers row to <code>nextRow</code> and recursively
1055 * updates all the children of the receivers rows. The index the
1056 * next row is to be placed as is returned.
1057 */
1058 protected int setRowAndChildren(int nextRow) {
1059 row = nextRow;
1060
1061 if(!isExpanded())
1062 return row + 1;
1063
1064 int lastRow = row + 1;
1065 int lastModelIndex = 0;
1066 FHTreeStateNode child;
1067 int maxCounter = getChildCount();
1068
1069 for(int counter = 0; counter < maxCounter; counter++) {
1070 child = (FHTreeStateNode)getChildAt(counter);
1071 lastRow += (child.childIndex - lastModelIndex);
1072 lastModelIndex = child.childIndex + 1;
1073 if(child.isExpanded) {
1074 lastRow = child.setRowAndChildren(lastRow);
1075 }
1076 else {
1077 child.row = lastRow++;
1078 }
1079 }
1080 return lastRow + childCount - lastModelIndex;
1081 }
1082
1083 /**
1084 * Resets the receivers children's rows. Starting with the child
1085 * at <code>childIndex</code> (and <code>modelIndex</code>) to
1086 * <code>newRow</code>. This uses <code>setRowAndChildren</code>
1087 * to recursively descend children, and uses
1088 * <code>resetRowSelection</code> to ascend parents.
1089 */
1090 // This can be rather expensive, but is needed for the collapse
1091 // case this is resulting from a remove (although I could fix
1092 // that by having instances of FHTreeStateNode hold a ref to
1093 // the number of children). I prefer this though, making determing
1094 // the row of a particular node fast is very nice!
1095 protected void resetChildrenRowsFrom(int newRow, int childIndex,
1096 int modelIndex) {
1097 int lastRow = newRow;
1098 int lastModelIndex = modelIndex;
1099 FHTreeStateNode node;
1100 int maxCounter = getChildCount();
1101
1102 for(int counter = childIndex; counter < maxCounter; counter++) {
1103 node = (FHTreeStateNode)getChildAt(counter);
1104 lastRow += (node.childIndex - lastModelIndex);
1105 lastModelIndex = node.childIndex + 1;
1106 if(node.isExpanded) {
1107 lastRow = node.setRowAndChildren(lastRow);
1108 }
1109 else {
1110 node.row = lastRow++;
1111 }
1112 }
1113 lastRow += childCount - lastModelIndex;
1114 node = (FHTreeStateNode)getParent();
1115 if(node != null) {
1116 node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1,
1117 this.childIndex + 1);
1118 }
1119 else { // This is the root, reset total ROWCOUNT!
1120 rowCount = lastRow;
1121 }
1122 }
1123
1124 /**
1125 * Makes the receiver visible, but invoking
1126 * <code>expandParentAndReceiver</code> on the superclass.
1127 */
1128 protected void makeVisible() {
1129 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1130
1131 if(parent != null)
1132 parent.expandParentAndReceiver();
1133 }
1134
1135 /**
1136 * Invokes <code>expandParentAndReceiver</code> on the parent,
1137 * and expands the receiver.
1138 */
1139 protected void expandParentAndReceiver() {
1140 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1141
1142 if(parent != null)
1143 parent.expandParentAndReceiver();
1144 expand();
1145 }
1146
1147 /**
1148 * Expands the receiver.
1149 */
1150 protected void expand() {
1151 if(!isExpanded && !isLeaf()) {
1152 boolean visible = isVisible();
1153
1154 isExpanded = true;
1155 childCount = treeModel.getChildCount(getUserObject());
1156
1157 if(visible) {
1158 didExpand();
1159 }
1160
1161 // Update the selection model.
1162 if(visible && treeSelectionModel != null) {
1163 treeSelectionModel.resetRowSelection();
1164 }
1165 }
1166 }
1167
1168 /**
1169 * Collapses the receiver. If <code>adjustRows</code> is true,
1170 * the rows of nodes after the receiver are adjusted.
1171 */
1172 protected void collapse(boolean adjustRows) {
1173 if(isExpanded) {
1174 if(isVisible() && adjustRows) {
1175 int childCount = getTotalChildCount();
1176
1177 isExpanded = false;
1178 adjustRowCountBy(-childCount);
1179 // We can do this because adjustRowBy won't descend
1180 // the children.
1181 adjustRowBy(-childCount, 0);
1182 }
1183 else
1184 isExpanded = false;
1185
1186 if(adjustRows && isVisible() && treeSelectionModel != null)
1187 treeSelectionModel.resetRowSelection();
1188 }
1189 }
1203 * The location is determined from the childIndex of newChild.
1204 */
1205 protected void addNode(FHTreeStateNode newChild) {
1206 boolean added = false;
1207 int childIndex = newChild.getChildIndex();
1208
1209 for(int counter = 0, maxCounter = getChildCount();
1210 counter < maxCounter; counter++) {
1211 if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() >
1212 childIndex) {
1213 added = true;
1214 insert(newChild, counter);
1215 counter = maxCounter;
1216 }
1217 }
1218 if(!added)
1219 add(newChild);
1220 }
1221
1222 /**
1223 * Removes the child at <code>modelIndex</code>.
1224 * <code>isChildVisible</code> should be true if the receiver
1225 * is visible and expanded.
1226 */
1227 protected void removeChildAtModelIndex(int modelIndex,
1228 boolean isChildVisible) {
1229 FHTreeStateNode childNode = getChildAtModelIndex(modelIndex);
1230
1231 if(childNode != null) {
1232 int row = childNode.getRow();
1233 int index = getIndex(childNode);
1234
1235 childNode.collapse(false);
1236 remove(index);
1237 adjustChildIndexs(index, -1);
1238 childCount--;
1239 if(isChildVisible) {
1240 // Adjust the rows.
1241 resetChildrenRowsFrom(row, index, modelIndex);
1242 }
1243 }
1244 else {
1257 // above.
1258 for(; counter < maxCounter; counter++)
1259 ((FHTreeStateNode)getChildAt(counter)).
1260 childIndex--;
1261 childCount--;
1262 return;
1263 }
1264 }
1265 // No children to adjust, but it was a child, so we still need
1266 // to adjust nodes after this one.
1267 if(isChildVisible) {
1268 adjustRowBy(-1, maxCounter);
1269 adjustRowCountBy(-1);
1270 }
1271 childCount--;
1272 }
1273 }
1274
1275 /**
1276 * Adjusts the child indexs of the receivers children by
1277 * <code>amount</code>, starting at <code>index</code>.
1278 */
1279 protected void adjustChildIndexs(int index, int amount) {
1280 for(int counter = index, maxCounter = getChildCount();
1281 counter < maxCounter; counter++) {
1282 ((FHTreeStateNode)getChildAt(counter)).childIndex += amount;
1283 }
1284 }
1285
1286 /**
1287 * Messaged when a child has been inserted at index. For all the
1288 * children that have a childIndex ≥ index their index is incremented
1289 * by one.
1290 */
1291 protected void childInsertedAtModelIndex(int index,
1292 boolean isExpandedAndVisible) {
1293 FHTreeStateNode aChild;
1294 int maxCounter = getChildCount();
1295
1296 for(int counter = 0; counter < maxCounter; counter++) {
1297 aChild = (FHTreeStateNode)getChildAt(counter);
1301 adjustRowCountBy(1);
1302 }
1303 /* Since matched and children are always sorted by
1304 index, no need to continue testing with the above. */
1305 for(; counter < maxCounter; counter++)
1306 ((FHTreeStateNode)getChildAt(counter)).childIndex++;
1307 childCount++;
1308 return;
1309 }
1310 }
1311 // No children to adjust, but it was a child, so we still need
1312 // to adjust nodes after this one.
1313 if(isExpandedAndVisible) {
1314 adjustRowBy(1, maxCounter);
1315 adjustRowCountBy(1);
1316 }
1317 childCount++;
1318 }
1319
1320 /**
1321 * Returns true if there is a row for <code>row</code>.
1322 * <code>nextRow</code> gives the bounds of the receiver.
1323 * Information about the found row is returned in <code>info</code>.
1324 * This should be invoked on root with <code>nextRow</code> set
1325 * to <code>getRowCount</code>().
1326 */
1327 protected boolean getPathForRow(int row, int nextRow,
1328 SearchInfo info) {
1329 if(this.row == row) {
1330 info.node = this;
1331 info.isNodeParentNode = false;
1332 info.childIndex = childIndex;
1333 return true;
1334 }
1335
1336 FHTreeStateNode child;
1337 FHTreeStateNode lastChild = null;
1338
1339 for(int counter = 0, maxCounter = getChildCount();
1340 counter < maxCounter; counter++) {
1341 child = (FHTreeStateNode)getChildAt(counter);
1342 if(child.row > row) {
1343 if(counter == 0) {
1344 // No node exists for it, and is first.
1345 info.node = this;
1406 int retCount = stopIndex + 1;
1407
1408 for(int counter = 0, maxCounter = getChildCount();
1409 counter < maxCounter; counter++) {
1410 aChild = (FHTreeStateNode)getChildAt(counter);
1411 if(aChild.childIndex >= stopIndex)
1412 counter = maxCounter;
1413 else
1414 retCount += aChild.getTotalChildCount();
1415 }
1416 if(parent != null)
1417 return retCount + ((FHTreeStateNode)getParent())
1418 .getCountTo(childIndex);
1419 if(!isRootVisible())
1420 return (retCount - 1);
1421 return retCount;
1422 }
1423
1424 /**
1425 * Returns the number of children that are expanded to
1426 * <code>stopIndex</code>. This does not include the number
1427 * of children that the child at <code>stopIndex</code> might
1428 * have.
1429 */
1430 protected int getNumExpandedChildrenTo(int stopIndex) {
1431 FHTreeStateNode aChild;
1432 int retCount = stopIndex;
1433
1434 for(int counter = 0, maxCounter = getChildCount();
1435 counter < maxCounter; counter++) {
1436 aChild = (FHTreeStateNode)getChildAt(counter);
1437 if(aChild.childIndex >= stopIndex)
1438 return retCount;
1439 else {
1440 retCount += aChild.getTotalChildCount();
1441 }
1442 }
1443 return retCount;
1444 }
1445
1446 /**
1447 * Messaged when this node either expands or collapses.
1517
1518 TreePath retObject;
1519
1520 if(nextIndex == -1)
1521 retObject = parent.getTreePath();
1522 else {
1523 FHTreeStateNode node = parent.getChildAtModelIndex(nextIndex);
1524
1525 if(node == null)
1526 retObject = parent.getTreePath().pathByAddingChild
1527 (treeModel.getChild(parent.getUserObject(),
1528 nextIndex));
1529 else
1530 retObject = node.getTreePath();
1531 }
1532 updateNextObject();
1533 return retObject;
1534 }
1535
1536 /**
1537 * Determines the next object by invoking <code>updateNextIndex</code>
1538 * and if not succesful <code>findNextValidParent</code>.
1539 */
1540 protected void updateNextObject() {
1541 if(!updateNextIndex()) {
1542 findNextValidParent();
1543 }
1544 }
1545
1546 /**
1547 * Finds the next valid parent, this should be called when nextIndex
1548 * is beyond the number of children of the current parent.
1549 */
1550 protected boolean findNextValidParent() {
1551 if(parent == root) {
1552 // mark as invalid!
1553 parent = null;
1554 return false;
1555 }
1556 while(parent != null) {
1557 FHTreeStateNode newParent = (FHTreeStateNode)parent.
1558 getParent();
1559
1560 if(newParent != null) {
1561 nextIndex = parent.childIndex;
1562 parent = newParent;
1563 childCount = treeModel.getChildCount
1564 (parent.getUserObject());
1565 if(updateNextIndex())
1566 return true;
1567 }
1568 else
1569 parent = null;
1570 }
1571 return false;
1572 }
1573
1574 /**
1575 * Updates <code>nextIndex</code> returning false if it is beyond
1576 * the number of children of parent.
1577 */
1578 protected boolean updateNextIndex() {
1579 // nextIndex == -1 identifies receiver, make sure is expanded
1580 // before descend.
1581 if(nextIndex == -1 && !parent.isExpanded()) {
1582 return false;
1583 }
1584
1585 // Check that it can have kids
1586 if(childCount == 0) {
1587 return false;
1588 }
1589 // Make sure next index not beyond child count.
1590 else if(++nextIndex >= childCount) {
1591 return false;
1592 }
1593
1594 FHTreeStateNode child = parent.getChildAtModelIndex(nextIndex);
1595
|
26 package javax.swing.tree;
27
28 import javax.swing.event.TreeModelEvent;
29 import java.awt.Rectangle;
30 import java.util.Enumeration;
31 import java.util.Hashtable;
32 import java.util.NoSuchElementException;
33 import java.util.Stack;
34
35 import sun.swing.SwingUtilities2;
36
37 /**
38 * NOTE: This will become more open in a future release.
39 * <p>
40 * <strong>Warning:</strong>
41 * Serialized objects of this class will not be compatible with
42 * future Swing releases. The current serialization support is
43 * appropriate for short term storage or RMI between applications running
44 * the same version of Swing. As of 1.4, support for long term storage
45 * of all JavaBeans™
46 * has been added to the {@code java.beans} package.
47 * Please see {@link java.beans.XMLEncoder}.
48 *
49 * @author Scott Violet
50 */
51 @SuppressWarnings("serial") // Same-version serialization only
52 public class FixedHeightLayoutCache extends AbstractLayoutCache {
53 /** Root node. */
54 private FHTreeStateNode root;
55
56 /** Number of rows currently visible. */
57 private int rowCount;
58
59 /**
60 * Used in getting sizes for nodes to avoid creating a new Rectangle
61 * every time a size is needed.
62 */
63 private Rectangle boundsBuffer;
64
65 /**
66 * Maps from TreePath to a FHTreeStateNode.
277 if(path == null)
278 return null;
279
280 FHTreeStateNode node = getNodeForPath(path, true, false);
281
282 if(node != null) {
283 return new VisibleFHTreeStateNodeEnumeration(node);
284 }
285 TreePath parentPath = path.getParentPath();
286
287 node = getNodeForPath(parentPath, true, false);
288 if(node != null && node.isExpanded()) {
289 return new VisibleFHTreeStateNodeEnumeration(node,
290 treeModel.getIndexOfChild(parentPath.getLastPathComponent(),
291 path.getLastPathComponent()));
292 }
293 return null;
294 }
295
296 /**
297 * Marks the path {@code path} expanded state to
298 * {@code isExpanded}.
299 */
300 public void setExpandedState(TreePath path, boolean isExpanded) {
301 if(isExpanded)
302 ensurePathIsExpanded(path, true);
303 else if(path != null) {
304 TreePath parentPath = path.getParentPath();
305
306 // YECK! Make the parent expanded.
307 if(parentPath != null) {
308 FHTreeStateNode parentNode = getNodeForPath(parentPath,
309 false, true);
310 if(parentNode != null)
311 parentNode.makeVisible();
312 }
313 // And collapse the child.
314 FHTreeStateNode childNode = getNodeForPath(path, true,
315 false);
316
317 if(childNode != null)
318 childNode.collapse(true);
510 changedNode = getNodeForPath(changedPath, false, true);
511 changedNode.expand();
512 }
513 if(treeSelectionModel != null && wasVisible && wasExpanded)
514 treeSelectionModel.resetRowSelection();
515 if(wasVisible)
516 this.visibleNodesChanged();
517 }
518 }
519 }
520
521
522 //
523 // Local methods
524 //
525
526 private void visibleNodesChanged() {
527 }
528
529 /**
530 * Returns the bounds for the given node. If {@code childIndex}
531 * is -1, the bounds of {@code parent} are returned, otherwise
532 * the bounds of the node at {@code childIndex} are returned.
533 */
534 private Rectangle getBounds(FHTreeStateNode parent, int childIndex,
535 Rectangle placeIn) {
536 boolean expanded;
537 int level;
538 int row;
539 Object value;
540
541 if(childIndex == -1) {
542 // Getting bounds for parent
543 row = parent.getRow();
544 value = parent.getUserObject();
545 expanded = parent.isExpanded();
546 level = parent.getLevel();
547 }
548 else {
549 row = parent.getRowToModelIndex(childIndex);
550 value = treeModel.getChild(parent.getUserObject(), childIndex);
551 expanded = false;
552 level = parent.getLevel() + 1;
574 */
575 private void adjustRowCountBy(int changeAmount) {
576 rowCount += changeAmount;
577 }
578
579 /**
580 * Adds a mapping for node.
581 */
582 private void addMapping(FHTreeStateNode node) {
583 treePathMapping.put(node.getTreePath(), node);
584 }
585
586 /**
587 * Removes the mapping for a previously added node.
588 */
589 private void removeMapping(FHTreeStateNode node) {
590 treePathMapping.remove(node.getTreePath());
591 }
592
593 /**
594 * Returns the node previously added for {@code path}. This may
595 * return null, if you to create a node use getNodeForPath.
596 */
597 private FHTreeStateNode getMapping(TreePath path) {
598 return treePathMapping.get(path);
599 }
600
601 /**
602 * Sent to completely rebuild the visible tree. All nodes are collapsed.
603 */
604 private void rebuild(boolean clearSelection) {
605 Object rootUO;
606
607 treePathMapping.clear();
608 if(treeModel != null && (rootUO = treeModel.getRoot()) != null) {
609 root = createNodeForValue(rootUO, 0);
610 root.path = new TreePath(rootUO);
611 addMapping(root);
612 if(isRootVisible()) {
613 rowCount = 1;
614 root.row = 0;
770
771 //
772 // Overriden DefaultMutableTreeNode methods
773 //
774
775 /**
776 * Messaged when this node is added somewhere, resets the path
777 * and adds a mapping from path to this node.
778 */
779 public void setParent(MutableTreeNode parent) {
780 super.setParent(parent);
781 if(parent != null) {
782 path = ((FHTreeStateNode)parent).getTreePath().
783 pathByAddingChild(getUserObject());
784 addMapping(this);
785 }
786 }
787
788 /**
789 * Messaged when this node is removed from its parent, this messages
790 * {@code removedFromMapping} to remove all the children.
791 */
792 public void remove(int childIndex) {
793 FHTreeStateNode node = (FHTreeStateNode)getChildAt(childIndex);
794
795 node.removeFromMapping();
796 super.remove(childIndex);
797 }
798
799 /**
800 * Messaged to set the user object. This resets the path.
801 */
802 public void setUserObject(Object o) {
803 super.setUserObject(o);
804 if(path != null) {
805 FHTreeStateNode parent = (FHTreeStateNode)getParent();
806
807 if(parent != null)
808 resetChildrenPaths(parent.getTreePath());
809 else
810 resetChildrenPaths(null);
811 }
812 }
813
814 //
815 //
816
817 /**
818 * Returns the index of the receiver in the model.
819 */
820 public int getChildIndex() {
821 return childIndex;
822 }
823
824 /**
825 * Returns the {@code TreePath} of the receiver.
826 */
827 public TreePath getTreePath() {
828 return path;
829 }
830
831 /**
832 * Returns the child for the passed in model index, this will
833 * return {@code null} if the child for {@code index}
834 * has not yet been created (expanded).
835 */
836 public FHTreeStateNode getChildAtModelIndex(int index) {
837 // PENDING: Make this a binary search!
838 for(int counter = getChildCount() - 1; counter >= 0; counter--)
839 if(((FHTreeStateNode)getChildAt(counter)).childIndex == index)
840 return (FHTreeStateNode)getChildAt(counter);
841 return null;
842 }
843
844 /**
845 * Returns true if this node is visible. This is determined by
846 * asking all the parents if they are expanded.
847 */
848 public boolean isVisible() {
849 FHTreeStateNode parent = (FHTreeStateNode)getParent();
850
851 if(parent == null)
852 return true;
853 return (parent.isExpanded() && parent.isVisible());
854 }
855
856 /**
857 * Returns the row of the receiver.
858 */
859 public int getRow() {
860 return row;
861 }
862
863 /**
864 * Returns the row of the child with a model index of
865 * {@code index}.
866 */
867 public int getRowToModelIndex(int index) {
868 FHTreeStateNode child;
869 int lastRow = getRow() + 1;
870 int retValue = lastRow;
871
872 // This too could be a binary search!
873 for(int counter = 0, maxCounter = getChildCount();
874 counter < maxCounter; counter++) {
875 child = (FHTreeStateNode)getChildAt(counter);
876 if(child.childIndex >= index) {
877 if(child.childIndex == index)
878 return child.row;
879 if(counter == 0)
880 return getRow() + 1 + index;
881 return child.row - (child.childIndex - index);
882 }
883 }
884 // YECK!
885 return getRow() + 1 + getTotalChildCount() -
948 path = parentPath.pathByAddingChild(getUserObject());
949 addMapping(this);
950 for(int counter = getChildCount() - 1; counter >= 0; counter--)
951 ((FHTreeStateNode)getChildAt(counter)).
952 resetChildrenPaths(path);
953 }
954
955 /**
956 * Removes the receiver, and all its children, from the mapping
957 * table.
958 */
959 protected void removeFromMapping() {
960 if(path != null) {
961 removeMapping(this);
962 for(int counter = getChildCount() - 1; counter >= 0; counter--)
963 ((FHTreeStateNode)getChildAt(counter)).removeFromMapping();
964 }
965 }
966
967 /**
968 * Creates a new node to represent {@code userObject}.
969 * This does NOT check to ensure there isn't already a child node
970 * to manage {@code userObject}.
971 */
972 protected FHTreeStateNode createChildFor(Object userObject) {
973 int newChildIndex = treeModel.getIndexOfChild
974 (getUserObject(), userObject);
975
976 if(newChildIndex < 0)
977 return null;
978
979 FHTreeStateNode aNode;
980 FHTreeStateNode child = createNodeForValue(userObject,
981 newChildIndex);
982 int childRow;
983
984 if(isVisible()) {
985 childRow = getRowToModelIndex(newChildIndex);
986 }
987 else {
988 childRow = -1;
989 }
990 child.row = childRow;
991 for(int counter = 0, maxCounter = getChildCount();
992 counter < maxCounter; counter++) {
993 aNode = (FHTreeStateNode)getChildAt(counter);
994 if(aNode.childIndex > newChildIndex) {
995 insert(child, counter);
996 return child;
997 }
998 }
999 add(child);
1000 return child;
1001 }
1002
1003 /**
1004 * Adjusts the receiver, and all its children rows by
1005 * {@code amount}.
1006 */
1007 protected void adjustRowBy(int amount) {
1008 row += amount;
1009 if(isExpanded) {
1010 for(int counter = getChildCount() - 1; counter >= 0;
1011 counter--)
1012 ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
1013 }
1014 }
1015
1016 /**
1017 * Adjusts this node, its child, and its parent starting at
1018 * an index of {@code index} index is the index of the child
1019 * to start adjusting from, which is not necessarily the model
1020 * index.
1021 */
1022 protected void adjustRowBy(int amount, int startIndex) {
1023 // Could check isVisible, but probably isn't worth it.
1024 if(isExpanded) {
1025 // children following startIndex.
1026 for(int counter = getChildCount() - 1; counter >= startIndex;
1027 counter--)
1028 ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
1029 }
1030 // Parent
1031 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1032
1033 if(parent != null) {
1034 parent.adjustRowBy(amount, parent.getIndex(this) + 1);
1035 }
1036 }
1037
1038 /**
1039 * Messaged when the node has expanded. This updates all of
1040 * the receivers children rows, as well as the total row count.
1041 */
1042 protected void didExpand() {
1043 int nextRow = setRowAndChildren(row);
1044 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1045 int childRowCount = nextRow - row - 1;
1046
1047 if(parent != null) {
1048 parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1);
1049 }
1050 adjustRowCountBy(childRowCount);
1051 }
1052
1053 /**
1054 * Sets the receivers row to {@code nextRow} and recursively
1055 * updates all the children of the receivers rows. The index the
1056 * next row is to be placed as is returned.
1057 */
1058 protected int setRowAndChildren(int nextRow) {
1059 row = nextRow;
1060
1061 if(!isExpanded())
1062 return row + 1;
1063
1064 int lastRow = row + 1;
1065 int lastModelIndex = 0;
1066 FHTreeStateNode child;
1067 int maxCounter = getChildCount();
1068
1069 for(int counter = 0; counter < maxCounter; counter++) {
1070 child = (FHTreeStateNode)getChildAt(counter);
1071 lastRow += (child.childIndex - lastModelIndex);
1072 lastModelIndex = child.childIndex + 1;
1073 if(child.isExpanded) {
1074 lastRow = child.setRowAndChildren(lastRow);
1075 }
1076 else {
1077 child.row = lastRow++;
1078 }
1079 }
1080 return lastRow + childCount - lastModelIndex;
1081 }
1082
1083 /**
1084 * Resets the receivers children's rows. Starting with the child
1085 * at {@code childIndex} (and {@code modelIndex}) to
1086 * {@code newRow}. This uses {@code setRowAndChildren}
1087 * to recursively descend children, and uses
1088 * {@code resetRowSelection} to ascend parents.
1089 */
1090 // This can be rather expensive, but is needed for the collapse
1091 // case this is resulting from a remove (although I could fix
1092 // that by having instances of FHTreeStateNode hold a ref to
1093 // the number of children). I prefer this though, making determing
1094 // the row of a particular node fast is very nice!
1095 protected void resetChildrenRowsFrom(int newRow, int childIndex,
1096 int modelIndex) {
1097 int lastRow = newRow;
1098 int lastModelIndex = modelIndex;
1099 FHTreeStateNode node;
1100 int maxCounter = getChildCount();
1101
1102 for(int counter = childIndex; counter < maxCounter; counter++) {
1103 node = (FHTreeStateNode)getChildAt(counter);
1104 lastRow += (node.childIndex - lastModelIndex);
1105 lastModelIndex = node.childIndex + 1;
1106 if(node.isExpanded) {
1107 lastRow = node.setRowAndChildren(lastRow);
1108 }
1109 else {
1110 node.row = lastRow++;
1111 }
1112 }
1113 lastRow += childCount - lastModelIndex;
1114 node = (FHTreeStateNode)getParent();
1115 if(node != null) {
1116 node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1,
1117 this.childIndex + 1);
1118 }
1119 else { // This is the root, reset total ROWCOUNT!
1120 rowCount = lastRow;
1121 }
1122 }
1123
1124 /**
1125 * Makes the receiver visible, but invoking
1126 * {@code expandParentAndReceiver} on the superclass.
1127 */
1128 protected void makeVisible() {
1129 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1130
1131 if(parent != null)
1132 parent.expandParentAndReceiver();
1133 }
1134
1135 /**
1136 * Invokes {@code expandParentAndReceiver} on the parent,
1137 * and expands the receiver.
1138 */
1139 protected void expandParentAndReceiver() {
1140 FHTreeStateNode parent = (FHTreeStateNode)getParent();
1141
1142 if(parent != null)
1143 parent.expandParentAndReceiver();
1144 expand();
1145 }
1146
1147 /**
1148 * Expands the receiver.
1149 */
1150 protected void expand() {
1151 if(!isExpanded && !isLeaf()) {
1152 boolean visible = isVisible();
1153
1154 isExpanded = true;
1155 childCount = treeModel.getChildCount(getUserObject());
1156
1157 if(visible) {
1158 didExpand();
1159 }
1160
1161 // Update the selection model.
1162 if(visible && treeSelectionModel != null) {
1163 treeSelectionModel.resetRowSelection();
1164 }
1165 }
1166 }
1167
1168 /**
1169 * Collapses the receiver. If {@code adjustRows} is true,
1170 * the rows of nodes after the receiver are adjusted.
1171 */
1172 protected void collapse(boolean adjustRows) {
1173 if(isExpanded) {
1174 if(isVisible() && adjustRows) {
1175 int childCount = getTotalChildCount();
1176
1177 isExpanded = false;
1178 adjustRowCountBy(-childCount);
1179 // We can do this because adjustRowBy won't descend
1180 // the children.
1181 adjustRowBy(-childCount, 0);
1182 }
1183 else
1184 isExpanded = false;
1185
1186 if(adjustRows && isVisible() && treeSelectionModel != null)
1187 treeSelectionModel.resetRowSelection();
1188 }
1189 }
1203 * The location is determined from the childIndex of newChild.
1204 */
1205 protected void addNode(FHTreeStateNode newChild) {
1206 boolean added = false;
1207 int childIndex = newChild.getChildIndex();
1208
1209 for(int counter = 0, maxCounter = getChildCount();
1210 counter < maxCounter; counter++) {
1211 if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() >
1212 childIndex) {
1213 added = true;
1214 insert(newChild, counter);
1215 counter = maxCounter;
1216 }
1217 }
1218 if(!added)
1219 add(newChild);
1220 }
1221
1222 /**
1223 * Removes the child at {@code modelIndex}.
1224 * {@code isChildVisible} should be true if the receiver
1225 * is visible and expanded.
1226 */
1227 protected void removeChildAtModelIndex(int modelIndex,
1228 boolean isChildVisible) {
1229 FHTreeStateNode childNode = getChildAtModelIndex(modelIndex);
1230
1231 if(childNode != null) {
1232 int row = childNode.getRow();
1233 int index = getIndex(childNode);
1234
1235 childNode.collapse(false);
1236 remove(index);
1237 adjustChildIndexs(index, -1);
1238 childCount--;
1239 if(isChildVisible) {
1240 // Adjust the rows.
1241 resetChildrenRowsFrom(row, index, modelIndex);
1242 }
1243 }
1244 else {
1257 // above.
1258 for(; counter < maxCounter; counter++)
1259 ((FHTreeStateNode)getChildAt(counter)).
1260 childIndex--;
1261 childCount--;
1262 return;
1263 }
1264 }
1265 // No children to adjust, but it was a child, so we still need
1266 // to adjust nodes after this one.
1267 if(isChildVisible) {
1268 adjustRowBy(-1, maxCounter);
1269 adjustRowCountBy(-1);
1270 }
1271 childCount--;
1272 }
1273 }
1274
1275 /**
1276 * Adjusts the child indexs of the receivers children by
1277 * {@code amount}, starting at {@code index}.
1278 */
1279 protected void adjustChildIndexs(int index, int amount) {
1280 for(int counter = index, maxCounter = getChildCount();
1281 counter < maxCounter; counter++) {
1282 ((FHTreeStateNode)getChildAt(counter)).childIndex += amount;
1283 }
1284 }
1285
1286 /**
1287 * Messaged when a child has been inserted at index. For all the
1288 * children that have a childIndex ≥ index their index is incremented
1289 * by one.
1290 */
1291 protected void childInsertedAtModelIndex(int index,
1292 boolean isExpandedAndVisible) {
1293 FHTreeStateNode aChild;
1294 int maxCounter = getChildCount();
1295
1296 for(int counter = 0; counter < maxCounter; counter++) {
1297 aChild = (FHTreeStateNode)getChildAt(counter);
1301 adjustRowCountBy(1);
1302 }
1303 /* Since matched and children are always sorted by
1304 index, no need to continue testing with the above. */
1305 for(; counter < maxCounter; counter++)
1306 ((FHTreeStateNode)getChildAt(counter)).childIndex++;
1307 childCount++;
1308 return;
1309 }
1310 }
1311 // No children to adjust, but it was a child, so we still need
1312 // to adjust nodes after this one.
1313 if(isExpandedAndVisible) {
1314 adjustRowBy(1, maxCounter);
1315 adjustRowCountBy(1);
1316 }
1317 childCount++;
1318 }
1319
1320 /**
1321 * Returns true if there is a row for {@code row}.
1322 * {@code nextRow} gives the bounds of the receiver.
1323 * Information about the found row is returned in {@code info}.
1324 * This should be invoked on root with {@code nextRow} set
1325 * to {@code getRowCount}().
1326 */
1327 protected boolean getPathForRow(int row, int nextRow,
1328 SearchInfo info) {
1329 if(this.row == row) {
1330 info.node = this;
1331 info.isNodeParentNode = false;
1332 info.childIndex = childIndex;
1333 return true;
1334 }
1335
1336 FHTreeStateNode child;
1337 FHTreeStateNode lastChild = null;
1338
1339 for(int counter = 0, maxCounter = getChildCount();
1340 counter < maxCounter; counter++) {
1341 child = (FHTreeStateNode)getChildAt(counter);
1342 if(child.row > row) {
1343 if(counter == 0) {
1344 // No node exists for it, and is first.
1345 info.node = this;
1406 int retCount = stopIndex + 1;
1407
1408 for(int counter = 0, maxCounter = getChildCount();
1409 counter < maxCounter; counter++) {
1410 aChild = (FHTreeStateNode)getChildAt(counter);
1411 if(aChild.childIndex >= stopIndex)
1412 counter = maxCounter;
1413 else
1414 retCount += aChild.getTotalChildCount();
1415 }
1416 if(parent != null)
1417 return retCount + ((FHTreeStateNode)getParent())
1418 .getCountTo(childIndex);
1419 if(!isRootVisible())
1420 return (retCount - 1);
1421 return retCount;
1422 }
1423
1424 /**
1425 * Returns the number of children that are expanded to
1426 * {@code stopIndex}. This does not include the number
1427 * of children that the child at {@code stopIndex} might
1428 * have.
1429 */
1430 protected int getNumExpandedChildrenTo(int stopIndex) {
1431 FHTreeStateNode aChild;
1432 int retCount = stopIndex;
1433
1434 for(int counter = 0, maxCounter = getChildCount();
1435 counter < maxCounter; counter++) {
1436 aChild = (FHTreeStateNode)getChildAt(counter);
1437 if(aChild.childIndex >= stopIndex)
1438 return retCount;
1439 else {
1440 retCount += aChild.getTotalChildCount();
1441 }
1442 }
1443 return retCount;
1444 }
1445
1446 /**
1447 * Messaged when this node either expands or collapses.
1517
1518 TreePath retObject;
1519
1520 if(nextIndex == -1)
1521 retObject = parent.getTreePath();
1522 else {
1523 FHTreeStateNode node = parent.getChildAtModelIndex(nextIndex);
1524
1525 if(node == null)
1526 retObject = parent.getTreePath().pathByAddingChild
1527 (treeModel.getChild(parent.getUserObject(),
1528 nextIndex));
1529 else
1530 retObject = node.getTreePath();
1531 }
1532 updateNextObject();
1533 return retObject;
1534 }
1535
1536 /**
1537 * Determines the next object by invoking {@code updateNextIndex}
1538 * and if not succesful {@code findNextValidParent}.
1539 */
1540 protected void updateNextObject() {
1541 if(!updateNextIndex()) {
1542 findNextValidParent();
1543 }
1544 }
1545
1546 /**
1547 * Finds the next valid parent, this should be called when nextIndex
1548 * is beyond the number of children of the current parent.
1549 */
1550 protected boolean findNextValidParent() {
1551 if(parent == root) {
1552 // mark as invalid!
1553 parent = null;
1554 return false;
1555 }
1556 while(parent != null) {
1557 FHTreeStateNode newParent = (FHTreeStateNode)parent.
1558 getParent();
1559
1560 if(newParent != null) {
1561 nextIndex = parent.childIndex;
1562 parent = newParent;
1563 childCount = treeModel.getChildCount
1564 (parent.getUserObject());
1565 if(updateNextIndex())
1566 return true;
1567 }
1568 else
1569 parent = null;
1570 }
1571 return false;
1572 }
1573
1574 /**
1575 * Updates {@code nextIndex} returning false if it is beyond
1576 * the number of children of parent.
1577 */
1578 protected boolean updateNextIndex() {
1579 // nextIndex == -1 identifies receiver, make sure is expanded
1580 // before descend.
1581 if(nextIndex == -1 && !parent.isExpanded()) {
1582 return false;
1583 }
1584
1585 // Check that it can have kids
1586 if(childCount == 0) {
1587 return false;
1588 }
1589 // Make sure next index not beyond child count.
1590 else if(++nextIndex >= childCount) {
1591 return false;
1592 }
1593
1594 FHTreeStateNode child = parent.getChildAtModelIndex(nextIndex);
1595
|