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