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&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 
  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     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 }