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&trade;
  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 &lt;= 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 }