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