1 /* 2 * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.tree; 27 28 import javax.swing.event.TreeModelEvent; 29 import java.awt.Dimension; 30 import java.awt.Rectangle; 31 import java.util.Enumeration; 32 33 /** 34 * <strong>Warning:</strong> 35 * Serialized objects of this class will not be compatible with 36 * future Swing releases. The current serialization support is 37 * appropriate for short term storage or RMI between applications running 38 * the same version of Swing. As of 1.4, support for long term storage 39 * of all JavaBeans™ 40 * has been added to the <code>java.beans</code> package. 41 * Please see {@link java.beans.XMLEncoder}. 42 * 43 * @author Scott Violet 44 */ 45 46 public abstract class AbstractLayoutCache implements RowMapper { 47 /** Object responsible for getting the size of a node. */ 48 protected NodeDimensions nodeDimensions; 49 50 /** Model providing information. */ 51 protected TreeModel treeModel; 52 53 /** Selection model. */ 54 protected TreeSelectionModel treeSelectionModel; 55 56 /** 57 * True if the root node is displayed, false if its children are 58 * the highest visible nodes. 59 */ 60 protected boolean rootVisible; 61 62 /** 63 * Height to use for each row. If this is <= 0 the renderer will be 64 * used to determine the height for each row. 65 */ 66 protected int rowHeight; 67 68 69 /** 70 * Sets the renderer that is responsible for drawing nodes in the tree 71 * and which is therefore responsible for calculating the dimensions of 72 * individual nodes. 73 * 74 * @param nd a <code>NodeDimensions</code> object 75 */ 76 public void setNodeDimensions(NodeDimensions nd) { 77 this.nodeDimensions = nd; 78 } 79 80 /** 81 * Returns the object that renders nodes in the tree, and which is 82 * responsible for calculating the dimensions of individual nodes. 83 * 84 * @return the <code>NodeDimensions</code> object 85 */ 86 public NodeDimensions getNodeDimensions() { 87 return nodeDimensions; 88 } 89 90 /** 91 * Sets the <code>TreeModel</code> that will provide the data. 92 * 93 * @param newModel the <code>TreeModel</code> that is to 94 * provide the data 95 */ 96 public void setModel(TreeModel newModel) { 97 treeModel = newModel; 98 } 99 100 /** 101 * Returns the <code>TreeModel</code> that is providing the data. 102 * 103 * @return the <code>TreeModel</code> that is providing the data 104 */ 105 public TreeModel getModel() { 106 return treeModel; 107 } 108 109 /** 110 * Determines whether or not the root node from 111 * the <code>TreeModel</code> is visible. 112 * 113 * @param rootVisible true if the root node of the tree is to be displayed 114 * @see #rootVisible 115 * @beaninfo 116 * bound: true 117 * description: Whether or not the root node 118 * from the TreeModel is visible. 119 */ 120 public void setRootVisible(boolean rootVisible) { 121 this.rootVisible = rootVisible; 122 } 123 124 /** 125 * Returns true if the root node of the tree is displayed. 126 * 127 * @return true if the root node of the tree is displayed 128 * @see #rootVisible 129 */ 130 public boolean isRootVisible() { 131 return rootVisible; 132 } 133 134 /** 135 * Sets the height of each cell. If the specified value 136 * is less than or equal to zero the current cell renderer is 137 * queried for each row's height. 138 * 139 * @param rowHeight the height of each cell, in pixels 140 * @beaninfo 141 * bound: true 142 * description: The height of each cell. 143 */ 144 public void setRowHeight(int rowHeight) { 145 this.rowHeight = rowHeight; 146 } 147 148 /** 149 * Returns the height of each row. If the returned value is less than 150 * or equal to 0 the height for each row is determined by the 151 * renderer. 152 */ 153 public int getRowHeight() { 154 return rowHeight; 155 } 156 157 /** 158 * Sets the <code>TreeSelectionModel</code> used to manage the 159 * selection to new LSM. 160 * 161 * @param newLSM the new <code>TreeSelectionModel</code> 162 */ 163 public void setSelectionModel(TreeSelectionModel newLSM) { 164 if(treeSelectionModel != null) 165 treeSelectionModel.setRowMapper(null); 166 treeSelectionModel = newLSM; 167 if(treeSelectionModel != null) 168 treeSelectionModel.setRowMapper(this); 169 } 170 171 /** 172 * Returns the model used to maintain the selection. 173 * 174 * @return the <code>treeSelectionModel</code> 175 */ 176 public TreeSelectionModel getSelectionModel() { 177 return treeSelectionModel; 178 } 179 180 /** 181 * Returns the preferred height. 182 * 183 * @return the preferred height 184 */ 185 public int getPreferredHeight() { 186 // Get the height 187 int rowCount = getRowCount(); 188 189 if(rowCount > 0) { 190 Rectangle bounds = getBounds(getPathForRow(rowCount - 1), 191 null); 192 193 if(bounds != null) 194 return bounds.y + bounds.height; 195 } 196 return 0; 197 } 198 199 /** 200 * Returns the preferred width for the passed in region. 201 * The region is defined by the path closest to 202 * <code>(bounds.x, bounds.y)</code> and 203 * ends at <code>bounds.height + bounds.y</code>. 204 * If <code>bounds</code> is <code>null</code>, 205 * the preferred width for all the nodes 206 * will be returned (and this may be a VERY expensive 207 * computation). 208 * 209 * @param bounds the region being queried 210 * @return the preferred width for the passed in region 211 */ 212 public int getPreferredWidth(Rectangle bounds) { 213 int rowCount = getRowCount(); 214 215 if(rowCount > 0) { 216 // Get the width 217 TreePath firstPath; 218 int endY; 219 220 if(bounds == null) { 221 firstPath = getPathForRow(0); 222 endY = Integer.MAX_VALUE; 223 } 224 else { 225 firstPath = getPathClosestTo(bounds.x, bounds.y); 226 endY = bounds.height + bounds.y; 227 } 228 229 Enumeration paths = getVisiblePathsFrom(firstPath); 230 231 if(paths != null && paths.hasMoreElements()) { 232 Rectangle pBounds = getBounds((TreePath)paths.nextElement(), 233 null); 234 int width; 235 236 if(pBounds != null) { 237 width = pBounds.x + pBounds.width; 238 if (pBounds.y >= endY) { 239 return width; 240 } 241 } 242 else 243 width = 0; 244 while (pBounds != null && paths.hasMoreElements()) { 245 pBounds = getBounds((TreePath)paths.nextElement(), 246 pBounds); 247 if (pBounds != null && pBounds.y < endY) { 248 width = Math.max(width, pBounds.x + pBounds.width); 249 } 250 else { 251 pBounds = null; 252 } 253 } 254 return width; 255 } 256 } 257 return 0; 258 } 259 260 // 261 // Abstract methods that must be implemented to be concrete. 262 // 263 264 /** 265 * Returns true if the value identified by row is currently expanded. 266 */ 267 public abstract boolean isExpanded(TreePath path); 268 269 /** 270 * Returns a rectangle giving the bounds needed to draw path. 271 * 272 * @param path a <code>TreePath</code> specifying a node 273 * @param placeIn a <code>Rectangle</code> object giving the 274 * available space 275 * @return a <code>Rectangle</code> object specifying the space to be used 276 */ 277 public abstract Rectangle getBounds(TreePath path, Rectangle placeIn); 278 279 /** 280 * Returns the path for passed in row. If row is not visible 281 * <code>null</code> is returned. 282 * 283 * @param row the row being queried 284 * @return the <code>TreePath</code> for the given row 285 */ 286 public abstract TreePath getPathForRow(int row); 287 288 /** 289 * Returns the row that the last item identified in path is visible 290 * at. Will return -1 if any of the elements in path are not 291 * currently visible. 292 * 293 * @param path the <code>TreePath</code> being queried 294 * @return the row where the last item in path is visible or -1 295 * if any elements in path aren't currently visible 296 */ 297 public abstract int getRowForPath(TreePath path); 298 299 /** 300 * Returns the path to the node that is closest to x,y. If 301 * there is nothing currently visible this will return <code>null</code>, 302 * otherwise it'll always return a valid path. 303 * If you need to test if the 304 * returned object is exactly at x, y you should get the bounds for 305 * the returned path and test x, y against that. 306 * 307 * @param x the horizontal component of the desired location 308 * @param y the vertical component of the desired location 309 * @return the <code>TreePath</code> closest to the specified point 310 */ 311 public abstract TreePath getPathClosestTo(int x, int y); 312 313 /** 314 * Returns an <code>Enumerator</code> that increments over the visible 315 * paths starting at the passed in location. The ordering of the 316 * enumeration is based on how the paths are displayed. 317 * The first element of the returned enumeration will be path, 318 * unless it isn't visible, 319 * in which case <code>null</code> will be returned. 320 * 321 * @param path the starting location for the enumeration 322 * @return the <code>Enumerator</code> starting at the desired location 323 */ 324 public abstract Enumeration<TreePath> getVisiblePathsFrom(TreePath path); 325 326 /** 327 * Returns the number of visible children for row. 328 * 329 * @param path the path being queried 330 * @return the number of visible children for the specified path 331 */ 332 public abstract int getVisibleChildCount(TreePath path); 333 334 /** 335 * Marks the path <code>path</code> expanded state to 336 * <code>isExpanded</code>. 337 * 338 * @param path the path being expanded or collapsed 339 * @param isExpanded true if the path should be expanded, false otherwise 340 */ 341 public abstract void setExpandedState(TreePath path, boolean isExpanded); 342 343 /** 344 * Returns true if the path is expanded, and visible. 345 * 346 * @param path the path being queried 347 * @return true if the path is expanded and visible, false otherwise 348 */ 349 public abstract boolean getExpandedState(TreePath path); 350 351 /** 352 * Number of rows being displayed. 353 * 354 * @return the number of rows being displayed 355 */ 356 public abstract int getRowCount(); 357 358 /** 359 * Informs the <code>TreeState</code> that it needs to recalculate 360 * all the sizes it is referencing. 361 */ 362 public abstract void invalidateSizes(); 363 364 /** 365 * Instructs the <code>LayoutCache</code> that the bounds for 366 * <code>path</code> are invalid, and need to be updated. 367 * 368 * @param path the path being updated 369 */ 370 public abstract void invalidatePathBounds(TreePath path); 371 372 // 373 // TreeModelListener methods 374 // AbstractTreeState does not directly become a TreeModelListener on 375 // the model, it is up to some other object to forward these methods. 376 // 377 378 /** 379 * <p> 380 * Invoked after a node (or a set of siblings) has changed in some 381 * way. The node(s) have not changed locations in the tree or 382 * altered their children arrays, but other attributes have 383 * changed and may affect presentation. Example: the name of a 384 * file has changed, but it is in the same location in the file 385 * system.</p> 386 * 387 * <p>e.path() returns the path the parent of the changed node(s).</p> 388 * 389 * <p>e.childIndices() returns the index(es) of the changed node(s).</p> 390 * 391 * @param e the <code>TreeModelEvent</code> 392 */ 393 public abstract void treeNodesChanged(TreeModelEvent e); 394 395 /** 396 * <p>Invoked after nodes have been inserted into the tree.</p> 397 * 398 * <p>e.path() returns the parent of the new nodes</p> 399 * <p>e.childIndices() returns the indices of the new nodes in 400 * ascending order.</p> 401 * 402 * @param e the <code>TreeModelEvent</code> 403 */ 404 public abstract void treeNodesInserted(TreeModelEvent e); 405 406 /** 407 * <p>Invoked after nodes have been removed from the tree. Note that 408 * if a subtree is removed from the tree, this method may only be 409 * invoked once for the root of the removed subtree, not once for 410 * each individual set of siblings removed.</p> 411 * 412 * <p>e.path() returns the former parent of the deleted nodes.</p> 413 * 414 * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p> 415 * 416 * @param e the <code>TreeModelEvent</code> 417 */ 418 public abstract void treeNodesRemoved(TreeModelEvent e); 419 420 /** 421 * <p>Invoked after the tree has drastically changed structure from a 422 * given node down. If the path returned by <code>e.getPath()</code> 423 * is of length one and the first element does not identify the 424 * current root node the first element should become the new root 425 * of the tree.</p> 426 * 427 * <p>e.path() holds the path to the node.</p> 428 * <p>e.childIndices() returns null.</p> 429 * 430 * @param e the <code>TreeModelEvent</code> 431 */ 432 public abstract void treeStructureChanged(TreeModelEvent e); 433 434 // 435 // RowMapper 436 // 437 438 /** 439 * Returns the rows that the <code>TreePath</code> instances in 440 * <code>path</code> are being displayed at. 441 * This method should return an array of the same length as that passed 442 * in, and if one of the <code>TreePaths</code> 443 * in <code>path</code> is not valid its entry in the array should 444 * be set to -1. 445 * 446 * @param paths the array of <code>TreePath</code>s being queried 447 * @return an array of the same length that is passed in containing 448 * the rows that each corresponding where each 449 * <code>TreePath</code> is displayed; if <code>paths</code> 450 * is <code>null</code>, <code>null</code> is returned 451 */ 452 public int[] getRowsForPaths(TreePath[] paths) { 453 if(paths == null) 454 return null; 455 456 int numPaths = paths.length; 457 int[] rows = new int[numPaths]; 458 459 for(int counter = 0; counter < numPaths; counter++) 460 rows[counter] = getRowForPath(paths[counter]); 461 return rows; 462 } 463 464 // 465 // Local methods that subclassers may wish to use that are primarly 466 // convenience methods. 467 // 468 469 /** 470 * Returns, by reference in <code>placeIn</code>, 471 * the size needed to represent <code>value</code>. 472 * If <code>inPlace</code> is <code>null</code>, a newly created 473 * <code>Rectangle</code> should be returned, otherwise the value 474 * should be placed in <code>inPlace</code> and returned. This will 475 * return <code>null</code> if there is no renderer. 476 * 477 * @param value the <code>value</code> to be represented 478 * @param row row being queried 479 * @param depth the depth of the row 480 * @param expanded true if row is expanded, false otherwise 481 * @param placeIn a <code>Rectangle</code> containing the size needed 482 * to represent <code>value</code> 483 * @return a <code>Rectangle</code> containing the node dimensions, 484 * or <code>null</code> if node has no dimension 485 */ 486 protected Rectangle getNodeDimensions(Object value, int row, int depth, 487 boolean expanded, 488 Rectangle placeIn) { 489 NodeDimensions nd = getNodeDimensions(); 490 491 if(nd != null) { 492 return nd.getNodeDimensions(value, row, depth, expanded, placeIn); 493 } 494 return null; 495 } 496 497 /** 498 * Returns true if the height of each row is a fixed size. 499 */ 500 protected boolean isFixedRowHeight() { 501 return (rowHeight > 0); 502 } 503 504 505 /** 506 * Used by <code>AbstractLayoutCache</code> to determine the size 507 * and x origin of a particular node. 508 */ 509 static public abstract class NodeDimensions { 510 /** 511 * Returns, by reference in bounds, the size and x origin to 512 * place value at. The calling method is responsible for determining 513 * the Y location. If bounds is <code>null</code>, a newly created 514 * <code>Rectangle</code> should be returned, 515 * otherwise the value should be placed in bounds and returned. 516 * 517 * @param value the <code>value</code> to be represented 518 * @param row row being queried 519 * @param depth the depth of the row 520 * @param expanded true if row is expanded, false otherwise 521 * @param bounds a <code>Rectangle</code> containing the size needed 522 * to represent <code>value</code> 523 * @return a <code>Rectangle</code> containing the node dimensions, 524 * or <code>null</code> if node has no dimension 525 */ 526 public abstract Rectangle getNodeDimensions(Object value, int row, 527 int depth, 528 boolean expanded, 529 Rectangle bounds); 530 } 531 }