GNU Classpath (0.91) | |
Frames | No Frames |
1: /* VariableHeightLayoutCache.java -- 2: Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package javax.swing.tree; 39: 40: import gnu.javax.swing.tree.GnuPath; 41: 42: import java.awt.Rectangle; 43: import java.util.Enumeration; 44: import java.util.HashSet; 45: import java.util.Hashtable; 46: import java.util.LinkedList; 47: import java.util.Set; 48: import java.util.Vector; 49: 50: import javax.swing.event.TreeModelEvent; 51: 52: /** 53: * The fixed height tree layout. This class requires the NodeDimensions to be 54: * set and ignores the row height property. 55: * 56: * @specnote the methods, of this class, returning TreePath, actually returns 57: * the derived class GnuPath that provides additional information for optimized 58: * painting. See the GnuPath code for details. 59: * 60: * @author Audrius Meskauskas 61: */ 62: public class VariableHeightLayoutCache 63: extends AbstractLayoutCache 64: { 65: /** 66: * The cached node record. 67: */ 68: class NodeRecord 69: { 70: NodeRecord(int aRow, int aDepth, Object aNode, Object aParent) 71: { 72: row = aRow; 73: depth = aDepth; 74: parent = aParent; 75: node = aNode; 76: 77: isExpanded = expanded.contains(aNode); 78: } 79: 80: /** 81: * The row, where the tree node is displayed. 82: */ 83: final int row; 84: 85: /** 86: * The nesting depth 87: */ 88: final int depth; 89: 90: /** 91: * The parent of the given node, null for the root node. 92: */ 93: final Object parent; 94: 95: /** 96: * This node. 97: */ 98: final Object node; 99: 100: /** 101: * True for the expanded nodes. The value is calculated in constructor. 102: * Using this field saves one hashtable access operation. 103: */ 104: final boolean isExpanded; 105: 106: /** 107: * The cached bounds of the tree row. 108: */ 109: Rectangle bounds; 110: 111: /** 112: * The path from the tree top to the given node (computed under first 113: * demand) 114: */ 115: private TreePath path; 116: 117: /** 118: * Get the path for this node. The derived class is returned, making check 119: * for the last child of some parent easier. 120: */ 121: TreePath getPath() 122: { 123: if (path == null) 124: { 125: boolean lastChild = false; 126: if (parent != null) 127: { 128: int nc = treeModel.getChildCount(parent); 129: if (nc > 0) 130: { 131: int n = treeModel.getIndexOfChild(parent, node); 132: if (n == nc - 1) 133: lastChild = true; 134: } 135: } 136: 137: LinkedList lpath = new LinkedList(); 138: NodeRecord rp = this; 139: while (rp != null) 140: { 141: lpath.addFirst(rp.node); 142: if (rp.parent != null) 143: { 144: Object parent = rp.parent; 145: rp = (NodeRecord) nodes.get(parent); 146: // Add the root node, even if it is not visible. 147: if (rp == null) 148: lpath.addFirst(parent); 149: } 150: else 151: rp = null; 152: } 153: path = new GnuPath(lpath.toArray(), lastChild); 154: } 155: return path; 156: } 157: 158: /** 159: * Get the rectangle bounds (compute, if required). 160: */ 161: Rectangle getBounds() 162: { 163: // This method may be called in the context when the tree rectangle is 164: // not known. To work around this, it is assumed near infinitely large. 165: if (bounds==null) 166: bounds = getNodeDimensions(node, row, depth, isExpanded, 167: new Rectangle()); 168: return bounds; 169: } 170: } 171: 172: /** 173: * The set of all expanded tree nodes. 174: */ 175: Set expanded = new HashSet(); 176: 177: /** 178: * Maps nodes to the row numbers. 179: */ 180: Hashtable nodes = new Hashtable(); 181: 182: /** 183: * Maps row numbers to nodes. 184: */ 185: Hashtable row2node = new Hashtable(); 186: 187: /** 188: * If true, the row map must be recomputed before using. 189: */ 190: boolean dirty; 191: 192: /** 193: * The cumulative height of all rows. 194: */ 195: int totalHeight; 196: 197: /** 198: * The maximal width. 199: */ 200: int maximalWidth; 201: 202: /** 203: * Creates the unitialised instance. Before using the class, the row height 204: * must be set with the {@link #setRowHeight(int)} and the model must be set 205: * with {@link #setModel(TreeModel)}. The node dimensions may not be set. 206: */ 207: public VariableHeightLayoutCache() 208: { 209: // Nothing to do here. 210: } 211: 212: /** 213: * Get the total number of rows in the tree. Every displayed node occupies the 214: * single row. The root node row is included if the root node is set as 215: * visible (false by default). 216: * 217: * @return int the number of the displayed rows. 218: */ 219: public int getRowCount() 220: { 221: if (dirty) update(); 222: return row2node.size(); 223: } 224: 225: /** 226: * Refresh the row map. 227: */ 228: private final void update() 229: { 230: nodes.clear(); 231: row2node.clear(); 232: 233: totalHeight = maximalWidth = 0; 234: 235: Object root = treeModel.getRoot(); 236: 237: if (rootVisible) 238: { 239: countRows(root, null, 0); 240: } 241: else 242: { 243: int sc = treeModel.getChildCount(root); 244: for (int i = 0; i < sc; i++) 245: { 246: Object child = treeModel.getChild(root, i); 247: countRows(child, root, 0); 248: } 249: } 250: dirty = false; 251: } 252: 253: /** 254: * Recursively counts all rows in the tree. 255: */ 256: private final void countRows(Object node, Object parent, int depth) 257: { 258: Integer n = new Integer(row2node.size()); 259: row2node.put(n, node); 260: 261: NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent); 262: nodes.put(node, nr); 263: 264: // For expanded nodes 265: if (expanded.contains(node)) 266: { 267: int sc = treeModel.getChildCount(node); 268: int deeper = depth+1; 269: for (int i = 0; i < sc; i++) 270: { 271: Object child = treeModel.getChild(node, i); 272: countRows(child, node, deeper); 273: } 274: } 275: } 276: 277: /** 278: * Discard the bound information for the given path. 279: * 280: * @param path the path, for that the bound information must be recomputed. 281: */ 282: public void invalidatePathBounds(TreePath path) 283: { 284: NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent()); 285: if (r!=null) 286: r.bounds = null; 287: } 288: 289: /** 290: * Mark all cached information as invalid. 291: */ 292: public void invalidateSizes() 293: { 294: dirty = true; 295: } 296: 297: /** 298: * Set the expanded state of the given path. The expansion states must be 299: * always updated when expanding and colapsing the tree nodes. Otherwise 300: * other methods will not work correctly after the nodes are collapsed or 301: * expanded. 302: * 303: * @param path the tree path, for that the state is being set. 304: * @param isExpanded the expanded state of the given path. 305: */ 306: public void setExpandedState(TreePath path, boolean isExpanded) 307: { 308: if (isExpanded) 309: expanded.add(path.getLastPathComponent()); 310: else 311: expanded.remove(path.getLastPathComponent()); 312: 313: dirty = true; 314: } 315: 316: /** 317: * Get the expanded state for the given tree path. 318: * 319: * @return true if the given path is expanded, false otherwise. 320: */ 321: public boolean isExpanded(TreePath path) 322: { 323: return expanded.contains(path.getLastPathComponent()); 324: } 325: 326: /** 327: * Get bounds for the given tree path. 328: * 329: * @param path the tree path 330: * @param rect the rectangle that will be reused to return the result. 331: * @return Rectangle the bounds of the last line, defined by the given path. 332: */ 333: public Rectangle getBounds(TreePath path, Rectangle rect) 334: { 335: if (path == null) 336: return null; 337: if (dirty) 338: update(); 339: Object last = path.getLastPathComponent(); 340: NodeRecord r = (NodeRecord) nodes.get(last); 341: if (r == null) 342: // This node is not visible. 343: { 344: rect.x = rect.y = rect.width = rect.height = 0; 345: } 346: else 347: { 348: if (r.bounds == null) 349: { 350: Rectangle dim = getNodeDimensions(last, r.row, r.depth, 351: r.isExpanded, rect); 352: r.bounds = dim; 353: } 354: 355: rect.setRect(r.bounds); 356: } 357: return rect; 358: } 359: 360: /** 361: * Get the path, the last element of that is displayed in the given row. 362: * 363: * @param row the row 364: * @return TreePath the path 365: */ 366: public TreePath getPathForRow(int row) 367: { 368: if (dirty) 369: update(); 370: Object last = row2node.get(new Integer(row)); 371: if (last == null) 372: return null; 373: else 374: { 375: NodeRecord r = (NodeRecord) nodes.get(last); 376: return r.getPath(); 377: } 378: } 379: 380: /** 381: * Get the row, displaying the last node of the given path. 382: * 383: * @param path the path 384: * @return int the row number or -1 if the end of the path is not visible. 385: */ 386: public int getRowForPath(TreePath path) 387: { 388: if (path == null) 389: return -1; 390: if (dirty) update(); 391: 392: NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent()); 393: if (r == null) 394: return - 1; 395: else 396: return r.row; 397: } 398: 399: /** 400: * Get the path, closest to the given point. 401: * 402: * @param x the point x coordinate 403: * @param y the point y coordinate 404: * @return the tree path, closest to the the given point 405: */ 406: public TreePath getPathClosestTo(int x, int y) 407: { 408: if (dirty) 409: update(); 410: 411: // As the rows have arbitrary height, we need to iterate. 412: NodeRecord best = null; 413: NodeRecord r; 414: Enumeration en = nodes.elements(); 415: 416: int dist = Integer.MAX_VALUE; 417: 418: while (en.hasMoreElements() && dist > 0) 419: { 420: r = (NodeRecord) en.nextElement(); 421: if (best == null) 422: { 423: best = r; 424: dist = distance(r.getBounds(), x, y); 425: } 426: else 427: { 428: int rr = distance(r.getBounds(), x, y); 429: if (rr < dist) 430: { 431: best = r; 432: dist = rr; 433: } 434: } 435: } 436: 437: if (best == null) 438: return null; 439: else 440: return best.getPath(); 441: } 442: 443: /** 444: * Get the closest distance from this point till the given rectangle. Only 445: * vertical distance is taken into consideration. 446: */ 447: int distance(Rectangle r, int x, int y) 448: { 449: if (y < r.y) 450: return r.y - y; 451: else if (y > r.y + r.height) 452: return y - (r.y + r.height); 453: else 454: return 0; 455: } 456: 457: /** 458: * Get the number of the visible childs for the given tree path. If the node 459: * is not expanded, 0 is returned. Otherwise, the number of children is 460: * obtained from the model as the number of children for the last path 461: * component. 462: * 463: * @param path the tree path 464: * @return int the number of the visible childs (for row). 465: */ 466: public int getVisibleChildCount(TreePath path) 467: { 468: if (isExpanded(path)) 469: return 0; 470: else 471: return treeModel.getChildCount(path.getLastPathComponent()); 472: } 473: 474: /** 475: * Get the enumeration over all visible pathes that start from the given 476: * parent path. 477: * 478: * @param parentPath the parent path 479: * @return the enumeration over pathes 480: */ 481: public Enumeration getVisiblePathsFrom(TreePath parentPath) 482: { 483: if (dirty) 484: update(); 485: Vector p = new Vector(parentPath.getPathCount()); 486: Object node; 487: NodeRecord nr; 488: 489: for (int i = 0; i < parentPath.getPathCount(); i++) 490: { 491: node = parentPath.getPathComponent(i); 492: nr = (NodeRecord) nodes.get(node); 493: if (nr.row >= 0) 494: p.add(node); 495: } 496: return p.elements(); 497: } 498: 499: /** 500: * Return the expansion state of the given tree path. The expansion state 501: * must be previously set with the 502: * {@link #setExpandedState(TreePath, boolean)} 503: * 504: * @param path the path being checked 505: * @return true if the last node of the path is expanded, false otherwise. 506: */ 507: public boolean getExpandedState(TreePath path) 508: { 509: return expanded.contains(path.getLastPathComponent()); 510: } 511: 512: /** 513: * The listener method, called when the tree nodes are changed. 514: * 515: * @param event the change event 516: */ 517: public void treeNodesChanged(TreeModelEvent event) 518: { 519: dirty = true; 520: } 521: 522: /** 523: * The listener method, called when the tree nodes are inserted. 524: * 525: * @param event the change event 526: */ 527: public void treeNodesInserted(TreeModelEvent event) 528: { 529: dirty = true; 530: } 531: 532: /** 533: * The listener method, called when the tree nodes are removed. 534: * 535: * @param event the change event 536: */ 537: public void treeNodesRemoved(TreeModelEvent event) 538: { 539: dirty = true; 540: } 541: 542: /** 543: * Called when the tree structure has been changed. 544: * 545: * @param event the change event 546: */ 547: public void treeStructureChanged(TreeModelEvent event) 548: { 549: dirty = true; 550: } 551: 552: /** 553: * Set the tree model that will provide the data. 554: */ 555: public void setModel(TreeModel newModel) 556: { 557: treeModel = newModel; 558: // The root node is expanded by default. 559: expanded.add(treeModel.getRoot()); 560: dirty = true; 561: } 562: 563: /** 564: * Inform the instance if the tree root node is visible. If this method 565: * is not called, it is assumed that the tree root node is not visible. 566: * 567: * @param visible true if the tree root node is visible, false 568: * otherwise. 569: */ 570: public void setRootVisible(boolean visible) 571: { 572: rootVisible = visible; 573: dirty = true; 574: } 575: 576: /** 577: * Get the sum of heights for all rows. 578: */ 579: public int getPreferredHeight() 580: { 581: if (dirty) 582: update(); 583: totalHeight = 0; 584: Enumeration en = nodes.elements(); 585: while (en.hasMoreElements()) 586: { 587: NodeRecord nr = (NodeRecord) en.nextElement(); 588: Rectangle r = nr.getBounds(); 589: totalHeight += r.height; 590: } 591: return totalHeight; 592: } 593: 594: /** 595: * Get the maximal width. 596: */ 597: public int getPreferredWidth(Rectangle value) 598: { 599: if (dirty) 600: update(); 601: 602: maximalWidth = 0; 603: Enumeration en = nodes.elements(); 604: while (en.hasMoreElements()) 605: { 606: NodeRecord nr = (NodeRecord) en.nextElement(); 607: Rectangle r = nr.getBounds(); 608: if (r.x + r.width > maximalWidth) 609: maximalWidth = r.x + r.width; 610: } 611: return maximalWidth; 612: } 613: }
GNU Classpath (0.91) |