1 /* VariableHeightLayoutCache.java --
2 Copyright (C) 2002, 2004, 2006, Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package javax
.swing
.tree
;
40 import gnu
.javax
.swing
.tree
.GnuPath
;
42 import java
.awt
.Rectangle
;
43 import java
.util
.ArrayList
;
44 import java
.util
.Enumeration
;
45 import java
.util
.HashSet
;
46 import java
.util
.Hashtable
;
47 import java
.util
.LinkedList
;
49 import java
.util
.Vector
;
51 import javax
.swing
.event
.TreeModelEvent
;
54 * The fixed height tree layout. This class requires the NodeDimensions to be
55 * set and ignores the row height property.
57 * @specnote the methods, of this class, returning TreePath, actually returns
58 * the derived class GnuPath that provides additional information for optimized
59 * painting. See the GnuPath code for details.
61 * @author Audrius Meskauskas
63 public class VariableHeightLayoutCache
64 extends AbstractLayoutCache
67 private static final Rectangle RECT_CACHE
= new Rectangle();
70 * The cached node record.
74 NodeRecord(int aRow
, int aDepth
, Object aNode
, Object aParent
)
80 isExpanded
= expanded
.contains(aNode
);
81 bounds
= new Rectangle(0, -1, 0, 0);
85 * The row, where the tree node is displayed.
95 * The parent of the given node, null for the root node.
105 * True for the expanded nodes. The value is calculated in constructor.
106 * Using this field saves one hashtable access operation.
108 final boolean isExpanded
;
111 * The cached bounds of the tree row.
116 * The path from the tree top to the given node (computed under first
119 private TreePath path
;
122 * Get the path for this node. The derived class is returned, making check
123 * for the last child of some parent easier.
129 boolean lastChild
= false;
132 int nc
= treeModel
.getChildCount(parent
);
135 int n
= treeModel
.getIndexOfChild(parent
, node
);
141 LinkedList
<Object
> lpath
= new LinkedList
<Object
>();
142 NodeRecord rp
= this;
145 lpath
.addFirst(rp
.node
);
146 if (rp
.parent
!= null)
148 Object parent
= rp
.parent
;
149 rp
= nodes
.get(parent
);
150 // Add the root node, even if it is not visible.
152 lpath
.addFirst(parent
);
157 path
= new GnuPath(lpath
.toArray(), lastChild
);
163 * Get the rectangle bounds (compute, if required).
165 Rectangle
getBounds()
172 * The set of all expanded tree nodes.
174 Set
<Object
> expanded
= new HashSet
<Object
>();
177 * Maps nodes to the row numbers.
179 Hashtable
<Object
,NodeRecord
> nodes
= new Hashtable
<Object
,NodeRecord
>();
182 * Maps row numbers to nodes.
184 ArrayList
<Object
> row2node
= new ArrayList
<Object
>();
187 * If true, the row map must be recomputed before using.
192 * The cumulative height of all rows.
202 * Creates the unitialised instance. Before using the class, the row height
203 * must be set with the {@link #setRowHeight(int)} and the model must be set
204 * with {@link #setModel(TreeModel)}. The node dimensions may not be set.
206 public VariableHeightLayoutCache()
208 // Nothing to do here.
212 * Get the total number of rows in the tree. Every displayed node occupies the
213 * single row. The root node row is included if the root node is set as
214 * visible (false by default).
216 * @return int the number of the displayed rows.
218 public int getRowCount()
221 return row2node
.size();
225 * Refresh the row map.
227 private final void update()
232 totalHeight
= maximalWidth
= 0;
234 if (treeModel
== null)
237 Object root
= treeModel
.getRoot();
238 countRows(root
, null, 0, 0);
243 * Recursively counts all rows in the tree.
245 private final int countRows(Object node
, Object parent
, int depth
, int y
)
247 boolean visible
= node
!= treeModel
.getRoot() || rootVisible
;
248 int row
= row2node
.size();
253 NodeRecord nr
= new NodeRecord(row
, depth
, node
, parent
);
254 NodeDimensions d
= getNodeDimensions();
255 Rectangle r
= RECT_CACHE
;
257 r
= d
.getNodeDimensions(node
, row
, depth
, nr
.isExpanded
, r
);
259 r
.setBounds(0, 0, 0, 0);
264 r
.y
= Math
.max(0, y
);
266 if (isFixedRowHeight())
267 r
.height
= getRowHeight();
269 nr
.bounds
.setBounds(r
);
275 int sc
= treeModel
.getChildCount(node
);
276 int deeper
= depth
+ 1;
277 if (expanded
.contains(node
))
279 for (int i
= 0; i
< sc
; i
++)
281 Object child
= treeModel
.getChild(node
, i
);
282 y
= countRows(child
, node
, deeper
, y
);
289 * Discard the bound information for the given path.
291 * @param path the path, for that the bound information must be recomputed.
293 public void invalidatePathBounds(TreePath path
)
295 NodeRecord r
= nodes
.get(path
.getLastPathComponent());
301 * Mark all cached information as invalid.
303 public void invalidateSizes()
309 * Set the expanded state of the given path. The expansion states must be
310 * always updated when expanding and colapsing the tree nodes. Otherwise
311 * other methods will not work correctly after the nodes are collapsed or
314 * @param path the tree path, for that the state is being set.
315 * @param isExpanded the expanded state of the given path.
317 public void setExpandedState(TreePath path
, boolean isExpanded
)
321 int length
= path
.getPathCount();
322 for (int i
= 0; i
< length
; i
++)
323 expanded
.add(path
.getPathComponent(i
));
326 expanded
.remove(path
.getLastPathComponent());
332 * Get the expanded state for the given tree path.
334 * @return true if the given path is expanded, false otherwise.
336 public boolean isExpanded(TreePath path
)
338 return expanded
.contains(path
.getLastPathComponent());
342 * Get bounds for the given tree path.
344 * @param path the tree path
345 * @param rect the rectangle that will be reused to return the result.
346 * @return Rectangle the bounds of the last line, defined by the given path.
348 public Rectangle
getBounds(TreePath path
, Rectangle rect
)
355 Object last
= path
.getLastPathComponent();
356 Rectangle result
= null;
357 NodeRecord r
= nodes
.get(last
);
360 // The RI allows null arguments for rect, in which case a new Rectangle
364 result
= new Rectangle(r
.bounds
);
366 result
.setBounds(r
.bounds
);
372 * Get the path, the last element of that is displayed in the given row.
375 * @return TreePath the path
377 public TreePath
getPathForRow(int row
)
382 TreePath path
= null;
383 // Search row in the nodes map. TODO: This is inefficient, optimize this.
384 Enumeration nodesEnum
= nodes
.elements();
385 while (nodesEnum
.hasMoreElements() && path
== null)
387 NodeRecord record
= (NodeRecord
) nodesEnum
.nextElement();
388 if (record
.row
== row
)
389 path
= record
.getPath();
395 * Get the row, displaying the last node of the given path.
397 * @param path the path
398 * @return int the row number or -1 if the end of the path is not visible.
400 public int getRowForPath(TreePath path
)
408 NodeRecord r
= nodes
.get(path
.getLastPathComponent());
416 * Get the path, closest to the given point.
418 * @param x the point x coordinate
419 * @param y the point y coordinate
420 * @return the tree path, closest to the the given point
422 public TreePath
getPathClosestTo(int x
, int y
)
427 // As the rows have arbitrary height, we need to iterate.
428 NodeRecord best
= null;
430 Enumeration
<NodeRecord
> en
= nodes
.elements();
432 int dist
= Integer
.MAX_VALUE
;
434 while (en
.hasMoreElements() && dist
> 0)
436 r
= en
.nextElement();
440 dist
= distance(r
.getBounds(), x
, y
);
444 int rr
= distance(r
.getBounds(), x
, y
);
456 return best
.getPath();
460 * Get the closest distance from this point till the given rectangle. Only
461 * vertical distance is taken into consideration.
463 int distance(Rectangle r
, int x
, int y
)
467 else if (y
> r
.y
+ r
.height
- 1)
468 return y
- (r
.y
+ r
.height
- 1);
474 * Get the number of the visible childs for the given tree path. If the node
475 * is not expanded, 0 is returned. Otherwise, the number of children is
476 * obtained from the model as the number of children for the last path
479 * @param path the tree path
480 * @return int the number of the visible childs (for row).
482 public int getVisibleChildCount(TreePath path
)
484 if (! isExpanded(path
) || treeModel
== null)
487 return treeModel
.getChildCount(path
.getLastPathComponent());
491 * Get the enumeration over all visible paths that start from the given
494 * @param parentPath the parent path
495 * @return the enumeration over pathes
497 public Enumeration
<TreePath
> getVisiblePathsFrom(TreePath parentPath
)
501 Vector p
= new Vector(parentPath
.getPathCount());
505 for (int i
= 0; i
< parentPath
.getPathCount(); i
++)
507 node
= parentPath
.getPathComponent(i
);
508 nr
= nodes
.get(node
);
509 if (nr
!= null && nr
.row
>= 0)
516 * Return the expansion state of the given tree path. The expansion state
517 * must be previously set with the
518 * {@link #setExpandedState(TreePath, boolean)}
520 * @param path the path being checked
521 * @return true if the last node of the path is expanded, false otherwise.
523 public boolean getExpandedState(TreePath path
)
525 return expanded
.contains(path
.getLastPathComponent());
529 * The listener method, called when the tree nodes are changed.
531 * @param event the change event
533 public void treeNodesChanged(TreeModelEvent event
)
539 * The listener method, called when the tree nodes are inserted.
541 * @param event the change event
543 public void treeNodesInserted(TreeModelEvent event
)
549 * The listener method, called when the tree nodes are removed.
551 * @param event the change event
553 public void treeNodesRemoved(TreeModelEvent event
)
559 * Called when the tree structure has been changed.
561 * @param event the change event
563 public void treeStructureChanged(TreeModelEvent event
)
569 * Set the tree model that will provide the data.
571 public void setModel(TreeModel newModel
)
573 treeModel
= newModel
;
575 if (treeModel
!= null)
577 // The root node is expanded by default.
578 expanded
.add(treeModel
.getRoot());
583 * Inform the instance if the tree root node is visible. If this method
584 * is not called, it is assumed that the tree root node is not visible.
586 * @param visible true if the tree root node is visible, false
589 public void setRootVisible(boolean visible
)
591 rootVisible
= visible
;
596 * Get the sum of heights for all rows.
598 public int getPreferredHeight()
603 int rowCount
= getRowCount();
606 NodeRecord last
= nodes
.get(row2node
.get(rowCount
- 1));
607 height
= last
.bounds
.y
+ last
.bounds
.height
;
613 * Get the maximal width.
615 public int getPreferredWidth(Rectangle value
)
621 Enumeration
<NodeRecord
> en
= nodes
.elements();
622 while (en
.hasMoreElements())
624 NodeRecord nr
= en
.nextElement();
627 Rectangle r
= nr
.getBounds();
628 int width
= r
.x
+ r
.width
;
629 if (width
> maximalWidth
)
630 maximalWidth
= width
;
637 * Sets the node dimensions and invalidates the cached layout.
639 * @param dim the dimensions to set
641 public void setNodeDimensions(NodeDimensions dim
)
643 super.setNodeDimensions(dim
);
648 * Sets the row height and marks the layout as invalid.
650 * @param height the row height to set
652 public void setRowHeight(int height
)
654 super.setRowHeight(height
);