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