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 */
25package javax.swing.tree;
26
27import javax.swing.event.TreeModelEvent;
28import java.awt.Rectangle;
29import java.beans.BeanProperty;
30import 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
45public 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}
535