1 /*
   2  * Copyright (c) 1998, 2014, 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&trade;
  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 @SuppressWarnings("serial") // Same-version serialization only
  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 &lt;= 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      * @return the height of each row
 154      */
 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      */
 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      */
 178     public TreeSelectionModel getSelectionModel() {
 179         return treeSelectionModel;
 180     }
 181 
 182     /**
 183      * Returns the preferred height.
 184      *
 185      * @return the preferred height
 186      */
 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      */
 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   paths = getVisiblePathsFrom(firstPath);
 232 
 233             if(paths != null && paths.hasMoreElements()) {
 234                 Rectangle   pBounds = getBounds((TreePath)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((TreePath)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       */
 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      */
 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       */
 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       */
 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       */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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       */
 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     static public abstract class NodeDimensions {
 517         /**
 518          * Returns, by reference in bounds, the size and x origin to
 519          * place value at. The calling method is responsible for determining
 520          * the Y location. If bounds is <code>null</code>, a newly created
 521          * <code>Rectangle</code> should be returned,
 522          * otherwise the value should be placed in bounds and returned.
 523          *
 524          * @param value the <code>value</code> to be represented
 525          * @param row row being queried
 526          * @param depth the depth of the row
 527          * @param expanded true if row is expanded, false otherwise
 528          * @param bounds  a <code>Rectangle</code> containing the size needed
 529          *              to represent <code>value</code>
 530          * @return a <code>Rectangle</code> containing the node dimensions,
 531          *              or <code>null</code> if node has no dimension
 532          */
 533         public abstract Rectangle getNodeDimensions(Object value, int row,
 534                                                     int depth,
 535                                                     boolean expanded,
 536                                                     Rectangle bounds);
 537     }
 538 }