1. /*
  2. * @(#)AbstractLayoutCache.java 1.7 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.tree;
  8. import javax.swing.event.TreeModelEvent;
  9. import java.awt.Dimension;
  10. import java.awt.Rectangle;
  11. import java.util.Enumeration;
  12. /**
  13. * <strong>Warning:</strong>
  14. * Serialized objects of this class will not be compatible with
  15. * future Swing releases. The current serialization support is appropriate
  16. * for short term storage or RMI between applications running the same
  17. * version of Swing. A future release of Swing will provide support for
  18. * long term persistence.
  19. *
  20. * @version 1.7 11/29/01
  21. * @author Scott Violet
  22. */
  23. public abstract class AbstractLayoutCache implements RowMapper {
  24. /** Object responsible for getting the size of a node. */
  25. protected NodeDimensions nodeDimensions;
  26. /** Model providing information. */
  27. protected TreeModel treeModel;
  28. /** Selection model. */
  29. protected TreeSelectionModel treeSelectionModel;
  30. /**
  31. * True if the root node is displayed, false if its children are
  32. * the highest visible nodes.
  33. */
  34. protected boolean rootVisible;
  35. /**
  36. * Height to use for each row. If this is <= 0 the renderer will be
  37. * used to determine the height for each row.
  38. */
  39. protected int rowHeight;
  40. /**
  41. * Sets the renderer that is responsible for drawing nodes in the tree
  42. * and which is threfore responsible foc calculating the dimensions of
  43. * individual nodes.
  44. *
  45. * @param nd a NodeDimensions object
  46. */
  47. public void setNodeDimensions(NodeDimensions nd) {
  48. this.nodeDimensions = nd;
  49. }
  50. /**
  51. * Returns the object that renders nodes in the tree, and which is
  52. * responsible for calculating the dimensions of individual nodes.
  53. *
  54. * @return the NodeDimensions object
  55. */
  56. public NodeDimensions getNodeDimensions() {
  57. return nodeDimensions;
  58. }
  59. /**
  60. * Sets the TreeModel that will provide the data.
  61. *
  62. * @param newModel the TreeModel that is to provide the data
  63. */
  64. public void setModel(TreeModel newModel) {
  65. treeModel = newModel;
  66. }
  67. /**
  68. * Returns the TreeModel that is providing the data.
  69. *
  70. * @return the TreeModel that is providing the data
  71. */
  72. public TreeModel getModel() {
  73. return treeModel;
  74. }
  75. /**
  76. * Determines whether or not the root node from
  77. * the TreeModel is visible.
  78. *
  79. * @param rootVisible true if the root node of the tree is to be displayed
  80. * @see #rootVisible
  81. * @beaninfo
  82. * bound: true
  83. * description: Whether or not the root node
  84. * from the TreeModel is visible.
  85. */
  86. public void setRootVisible(boolean rootVisible) {
  87. this.rootVisible = rootVisible;
  88. }
  89. /**
  90. * Returns true if the root node of the tree is displayed.
  91. *
  92. * @return true if the root node of the tree is displayed
  93. * @see #rootVisible
  94. */
  95. public boolean isRootVisible() {
  96. return rootVisible;
  97. }
  98. /**
  99. * Sets the height of each cell. If the specified value
  100. * is less than or equal to zero the current cell renderer is
  101. * queried for each row's height.
  102. *
  103. * @param rowHeight the height of each cell, in pixels
  104. * @beaninfo
  105. * bound: true
  106. * description: The height of each cell.
  107. */
  108. public void setRowHeight(int rowHeight) {
  109. this.rowHeight = rowHeight;
  110. }
  111. /**
  112. * Returns the height of each row. If the returned value is less than
  113. * or equal to 0 the height for each row is determined by the
  114. * renderer.
  115. *
  116. * @param the height of each cell, in pixels. Zero or negative if the
  117. * height of each row is determined by the tree cell renderer
  118. */
  119. public int getRowHeight() {
  120. return rowHeight;
  121. }
  122. /**
  123. * Sets the TreeSelectionModel used to manage the selection to
  124. * new LSM.
  125. */
  126. public void setSelectionModel(TreeSelectionModel newLSM) {
  127. if(treeSelectionModel != null)
  128. treeSelectionModel.setRowMapper(null);
  129. treeSelectionModel = newLSM;
  130. if(treeSelectionModel != null)
  131. treeSelectionModel.setRowMapper(this);
  132. }
  133. /**
  134. * Returns the model used to maintain the selection.
  135. */
  136. public TreeSelectionModel getSelectionModel() {
  137. return treeSelectionModel;
  138. }
  139. /**
  140. * Returns the preferred height.
  141. */
  142. public int getPreferredHeight() {
  143. // Get the height
  144. int rowCount = getRowCount();
  145. if(rowCount > 0) {
  146. Rectangle bounds = getBounds(getPathForRow(rowCount - 1),
  147. null);
  148. if(bounds != null)
  149. return bounds.y + bounds.height;
  150. }
  151. return 0;
  152. }
  153. /**
  154. * Returns the preferred width for the passed in region. If
  155. * <code>bounds</code> is null, the preferred width for all the nodes
  156. * will be returned (and this may be VERY expensive).
  157. */
  158. public int getPreferredWidth(Rectangle bounds) {
  159. int rowCount = getRowCount();
  160. if(rowCount > 0) {
  161. // Get the width
  162. TreePath firstPath;
  163. int endY;
  164. if(bounds == null) {
  165. firstPath = getPathForRow(0);
  166. endY = Integer.MAX_VALUE;
  167. }
  168. else {
  169. firstPath = getPathClosestTo(bounds.x, bounds.y);
  170. endY = bounds.height + bounds.y;
  171. }
  172. Enumeration paths = getVisiblePathsFrom(firstPath);
  173. if(paths != null && paths.hasMoreElements()) {
  174. Rectangle pBounds = getBounds((TreePath)paths.nextElement(),
  175. null);
  176. int width;
  177. if(pBounds != null)
  178. width = pBounds.x + pBounds.width;
  179. else
  180. width = 0;
  181. while(pBounds != null && paths.hasMoreElements() &&
  182. pBounds.y < endY) {
  183. width = Math.max(width, pBounds.x + pBounds.width);
  184. pBounds = getBounds((TreePath)paths.nextElement(),
  185. pBounds);
  186. }
  187. return width;
  188. }
  189. }
  190. return 0;
  191. }
  192. //
  193. // Abstract methods that must be implemented to be concrete.
  194. //
  195. /**
  196. * Returns true if the value identified by row is currently expanded.
  197. */
  198. public abstract boolean isExpanded(TreePath path);
  199. /**
  200. * Returns a rectangle giving the bounds needed to draw path.
  201. *
  202. * @param path a TreePath specifying a node
  203. * @param placeIn a Rectangle object giving the available space
  204. * @return a Rectangle object specifying the space to be used
  205. */
  206. public abstract Rectangle getBounds(TreePath path, Rectangle placeIn);
  207. /**
  208. * Returns the path for passed in row. If row is not visible
  209. * null is returned.
  210. */
  211. public abstract TreePath getPathForRow(int row);
  212. /**
  213. * Returns the row that the last item identified in path is visible
  214. * at. Will return -1 if any of the elements in path are not
  215. * currently visible.
  216. */
  217. public abstract int getRowForPath(TreePath path);
  218. /**
  219. * Returns the path to the node that is closest to x,y. If
  220. * there is nothing currently visible this will return null, otherwise
  221. * it'll always return a valid path. If you need to test if the
  222. * returned object is exactly at x, y you should get the bounds for
  223. * the returned path and test x, y against that.
  224. */
  225. public abstract TreePath getPathClosestTo(int x, int y);
  226. /**
  227. * Returns an Enumerator that increments over the visible paths
  228. * starting at the passed in location. The ordering of the enumeration
  229. * is based on how the paths are displayed. The first element of the
  230. * returned enumeration will be path, unless it isn't visible, in
  231. * which case null will be returned.
  232. */
  233. public abstract Enumeration getVisiblePathsFrom(TreePath path);
  234. /**
  235. * Returns the number of visible children for row.
  236. */
  237. public abstract int getVisibleChildCount(TreePath path);
  238. /**
  239. * Marks the path <code>path</code> expanded state to
  240. * <code>isExpanded</code>.
  241. */
  242. public abstract void setExpandedState(TreePath path, boolean isExpanded);
  243. /**
  244. * Returns true if the path is expanded, and visible.
  245. */
  246. public abstract boolean getExpandedState(TreePath path);
  247. /**
  248. * Number of rows being displayed.
  249. */
  250. public abstract int getRowCount();
  251. /**
  252. * Informs the TreeState that it needs to recalculate all the sizes
  253. * it is referencing.
  254. */
  255. public abstract void invalidateSizes();
  256. /**
  257. * Instructs the LayoutCache that the bounds for <code>path</code>
  258. * are invalid, and need to be updated.
  259. */
  260. public abstract void invalidatePathBounds(TreePath path);
  261. //
  262. // TreeModelListener methods
  263. // AbstractTreeState does not directly become a TreeModelListener on
  264. // the model, it is up to some other object to forward these methods.
  265. //
  266. /**
  267. * <p>Invoked after a node (or a set of siblings) has changed in some
  268. * way. The node(s) have not changed locations in the tree or
  269. * altered their children arrays, but other attributes have
  270. * changed and may affect presentation. Example: the name of a
  271. * file has changed, but it is in the same location in the file
  272. * system.</p>
  273. *
  274. * <p>e.path() returns the path the parent of the changed node(s).</p>
  275. *
  276. * <p>e.childIndices() returns the index(es) of the changed node(s).</p>
  277. */
  278. public abstract void treeNodesChanged(TreeModelEvent e);
  279. /**
  280. * <p>Invoked after nodes have been inserted into the tree.</p>
  281. *
  282. * <p>e.path() returns the parent of the new nodes
  283. * <p>e.childIndices() returns the indices of the new nodes in
  284. * ascending order.
  285. */
  286. public abstract void treeNodesInserted(TreeModelEvent e);
  287. /**
  288. * <p>Invoked after nodes have been removed from the tree. Note that
  289. * if a subtree is removed from the tree, this method may only be
  290. * invoked once for the root of the removed subtree, not once for
  291. * each individual set of siblings removed.</p>
  292. *
  293. * <p>e.path() returns the former parent of the deleted nodes.</p>
  294. *
  295. * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p>
  296. */
  297. public abstract void treeNodesRemoved(TreeModelEvent e);
  298. /**
  299. * <p>Invoked after the tree has drastically changed structure from a
  300. * given node down. If the path returned by e.getPath() is of length
  301. * one and the first element does not identify the current root node
  302. * the first element should become the new root of the tree.<p>
  303. *
  304. * <p>e.path() holds the path to the node.</p>
  305. * <p>e.childIndices() returns null.</p>
  306. */
  307. public abstract void treeStructureChanged(TreeModelEvent e);
  308. //
  309. // RowMapper
  310. //
  311. /**
  312. * Returns the rows that the TreePath instances in <code>path</code>
  313. * are being displayed at. The receiver should return an array of
  314. * the same length as that passed in, and if one of the TreePaths
  315. * in <code>path</code> is not valid its entry in the array should
  316. * be set to -1.
  317. */
  318. public int[] getRowsForPaths(TreePath[] paths) {
  319. if(paths == null)
  320. return null;
  321. int numPaths = paths.length;
  322. int[] rows = new int[numPaths];
  323. for(int counter = 0; counter < numPaths; counter++)
  324. rows[counter] = getRowForPath(paths[counter]);
  325. return rows;
  326. }
  327. //
  328. // Local methods that subclassers may wish to use that are primarly
  329. // convenience methods.
  330. //
  331. /**
  332. * Returns, by reference in size, the size needed to reprensent
  333. * value. If size is null, a newly created Dimension should be returned,
  334. * otherwise the value should be placed in size and returned. This will
  335. * return null if there is no renderer.
  336. */
  337. protected Rectangle getNodeDimensions(Object value, int row, int depth,
  338. boolean expanded,
  339. Rectangle placeIn) {
  340. NodeDimensions nd = getNodeDimensions();
  341. if(nd != null) {
  342. return nd.getNodeDimensions(value, row, depth, expanded, placeIn);
  343. }
  344. return null;
  345. }
  346. /**
  347. * Returns true if the height of each row is a fixed size.
  348. */
  349. protected boolean isFixedRowHeight() {
  350. return (rowHeight > 0);
  351. }
  352. /**
  353. * Used by AbstractLayoutCache to determing the size and x origin
  354. * of a particular node.
  355. */
  356. static public abstract class NodeDimensions {
  357. /**
  358. * Returns, by reference in bounds, the size and x origin to
  359. * place value at. The receiver is responsible for determing
  360. * the Y location. If bounds is null, a newly created
  361. * Rectangle should be returned, otherwise the value should be
  362. * placed in bounds and returned.
  363. */
  364. public abstract Rectangle getNodeDimensions(Object value, int row,
  365. int depth,
  366. boolean expanded,
  367. Rectangle bounds);
  368. }
  369. }