001 /*
002 * Copyright 1998-2004 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.swing.tree;
027
028 import javax.swing.event.TreeModelEvent;
029 import java.awt.Dimension;
030 import java.awt.Rectangle;
031 import java.util.Enumeration;
032
033 /**
034 * <strong>Warning:</strong>
035 * Serialized objects of this class will not be compatible with
036 * future Swing releases. The current serialization support is
037 * appropriate for short term storage or RMI between applications running
038 * the same version of Swing. As of 1.4, support for long term storage
039 * of all JavaBeans<sup><font size="-2">TM</font></sup>
040 * has been added to the <code>java.beans</code> package.
041 * Please see {@link java.beans.XMLEncoder}.
042 *
043 * @version 1.24 05/05/07
044 * @author Scott Violet
045 */
046
047 public abstract class AbstractLayoutCache implements RowMapper {
048 /** Object responsible for getting the size of a node. */
049 protected NodeDimensions nodeDimensions;
050
051 /** Model providing information. */
052 protected TreeModel treeModel;
053
054 /** Selection model. */
055 protected TreeSelectionModel treeSelectionModel;
056
057 /**
058 * True if the root node is displayed, false if its children are
059 * the highest visible nodes.
060 */
061 protected boolean rootVisible;
062
063 /**
064 * Height to use for each row. If this is <= 0 the renderer will be
065 * used to determine the height for each row.
066 */
067 protected int rowHeight;
068
069 /**
070 * Sets the renderer that is responsible for drawing nodes in the tree
071 * and which is threfore responsible for calculating the dimensions of
072 * individual nodes.
073 *
074 * @param nd a <code>NodeDimensions</code> object
075 */
076 public void setNodeDimensions(NodeDimensions nd) {
077 this .nodeDimensions = nd;
078 }
079
080 /**
081 * Returns the object that renders nodes in the tree, and which is
082 * responsible for calculating the dimensions of individual nodes.
083 *
084 * @return the <code>NodeDimensions</code> object
085 */
086 public NodeDimensions getNodeDimensions() {
087 return nodeDimensions;
088 }
089
090 /**
091 * Sets the <code>TreeModel</code> that will provide the data.
092 *
093 * @param newModel the <code>TreeModel</code> that is to
094 * provide the data
095 */
096 public void setModel(TreeModel newModel) {
097 treeModel = newModel;
098 }
099
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 } else {
224 firstPath = getPathClosestTo(bounds.x, bounds.y);
225 endY = bounds.height + bounds.y;
226 }
227
228 Enumeration paths = getVisiblePathsFrom(firstPath);
229
230 if (paths != null && paths.hasMoreElements()) {
231 Rectangle pBounds = getBounds((TreePath) paths
232 .nextElement(), null);
233 int width;
234
235 if (pBounds != null) {
236 width = pBounds.x + pBounds.width;
237 if (pBounds.y >= endY) {
238 return width;
239 }
240 } else
241 width = 0;
242 while (pBounds != null && paths.hasMoreElements()) {
243 pBounds = getBounds((TreePath) paths.nextElement(),
244 pBounds);
245 if (pBounds != null && pBounds.y < endY) {
246 width = Math.max(width, pBounds.x
247 + pBounds.width);
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 public abstract boolean isExpanded(TreePath path);
266
267 /**
268 * Returns a rectangle giving the bounds needed to draw path.
269 *
270 * @param path a <code>TreePath</code> specifying a node
271 * @param placeIn a <code>Rectangle</code> object giving the
272 * available space
273 * @return a <code>Rectangle</code> object specifying the space to be used
274 */
275 public abstract Rectangle getBounds(TreePath path, Rectangle placeIn);
276
277 /**
278 * Returns the path for passed in row. If row is not visible
279 * <code>null</code> is returned.
280 *
281 * @param row the row being queried
282 * @return the <code>TreePath</code> for the given row
283 */
284 public abstract TreePath getPathForRow(int row);
285
286 /**
287 * Returns the row that the last item identified in path is visible
288 * at. Will return -1 if any of the elements in path are not
289 * currently visible.
290 *
291 * @param path the <code>TreePath</code> being queried
292 * @return the row where the last item in path is visible or -1
293 * if any elements in path aren't currently visible
294 */
295 public abstract int getRowForPath(TreePath path);
296
297 /**
298 * Returns the path to the node that is closest to x,y. If
299 * there is nothing currently visible this will return <code>null</code>,
300 * otherwise it'll always return a valid path.
301 * If you need to test if the
302 * returned object is exactly at x, y you should get the bounds for
303 * the returned path and test x, y against that.
304 *
305 * @param x the horizontal component of the desired location
306 * @param y the vertical component of the desired location
307 * @return the <code>TreePath</code> closest to the specified point
308 */
309 public abstract TreePath getPathClosestTo(int x, int y);
310
311 /**
312 * Returns an <code>Enumerator</code> that increments over the visible
313 * paths starting at the passed in location. The ordering of the
314 * enumeration is based on how the paths are displayed.
315 * The first element of the returned enumeration will be path,
316 * unless it isn't visible,
317 * in which case <code>null</code> will be returned.
318 *
319 * @param path the starting location for the enumeration
320 * @return the <code>Enumerator</code> starting at the desired location
321 */
322 public abstract Enumeration<TreePath> getVisiblePathsFrom(
323 TreePath path);
324
325 /**
326 * Returns the number of visible children for row.
327 *
328 * @param path the path being queried
329 * @return the number of visible children for the specified path
330 */
331 public abstract int getVisibleChildCount(TreePath path);
332
333 /**
334 * Marks the path <code>path</code> expanded state to
335 * <code>isExpanded</code>.
336 *
337 * @param path the path being expanded or collapsed
338 * @param isExpanded true if the path should be expanded, false otherwise
339 */
340 public abstract void setExpandedState(TreePath path,
341 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,
487 int depth, boolean expanded, Rectangle placeIn) {
488 NodeDimensions nd = getNodeDimensions();
489
490 if (nd != null) {
491 return nd.getNodeDimensions(value, row, depth, expanded,
492 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 * Used by <code>AbstractLayoutCache</code> to determine the size
506 * and x origin of a particular node.
507 */
508 static public abstract class NodeDimensions {
509 /**
510 * Returns, by reference in bounds, the size and x origin to
511 * place value at. The calling method is responsible for determining
512 * the Y location. If bounds is <code>null</code>, a newly created
513 * <code>Rectangle</code> should be returned,
514 * otherwise the value should be placed in bounds and returned.
515 *
516 * @param value the <code>value</code> to be represented
517 * @param row row being queried
518 * @param depth the depth of the row
519 * @param expanded true if row is expanded, false otherwise
520 * @param bounds a <code>Rectangle</code> containing the size needed
521 * to represent <code>value</code>
522 * @return a <code>Rectangle</code> containing the node dimensions,
523 * or <code>null</code> if node has no dimension
524 */
525 public abstract Rectangle getNodeDimensions(Object value,
526 int row, int depth, boolean expanded, Rectangle bounds);
527 }
528 }
|