OSDN Git Service

2006-06-09 Thomas Fitzsimmons <fitzsim@redhat.com>
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / tree / VariableHeightLayoutCache.java
1 /* VariableHeightLayoutCache.java --
2    Copyright (C) 2002, 2004, 2006,  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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)
9 any later version.
10
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.
15
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
19 02110-1301 USA.
20
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
24 combination.
25
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. */
37
38 package javax.swing.tree;
39
40 import gnu.javax.swing.tree.GnuPath;
41
42 import java.awt.Rectangle;
43 import java.util.Enumeration;
44 import java.util.HashSet;
45 import java.util.Hashtable;
46 import java.util.LinkedList;
47 import java.util.Set;
48 import java.util.Vector;
49
50 import javax.swing.event.TreeModelEvent;
51
52 /**
53  * The fixed height tree layout. This class requires the NodeDimensions to be
54  * set and ignores the row height property.
55  * 
56  * @specnote the methods, of this class, returning TreePath, actually returns
57  * the derived class GnuPath that provides additional information for optimized
58  * painting. See the GnuPath code for details.
59  * 
60  * @author Audrius Meskauskas
61  */
62 public class VariableHeightLayoutCache
63                 extends AbstractLayoutCache
64 {
65   /**
66    * The cached node record.
67    */
68   class NodeRecord
69   {
70     NodeRecord(int aRow, int aDepth, Object aNode, Object aParent)
71     {
72       row = aRow;
73       depth = aDepth;
74       parent = aParent;
75       node = aNode;
76       
77       isExpanded = expanded.contains(aNode); 
78     }
79     
80     /**
81      * The row, where the tree node is displayed.
82      */
83     final int row;    
84     
85     /**
86      * The nesting depth
87      */
88     final int depth;
89     
90     /**
91      * The parent of the given node, null for the root node.
92      */
93     final Object parent;
94     
95     /**
96      * This node.
97      */
98     final Object node;
99     
100     /**
101      * True for the expanded nodes. The value is calculated in constructor.
102      * Using this field saves one hashtable access operation.
103      */
104     final boolean isExpanded;
105     
106     /**
107      * The cached bounds of the tree row.
108      */
109     Rectangle bounds;
110     
111     /**
112      * The path from the tree top to the given node (computed under first
113      * demand)
114      */
115     private TreePath path;
116     
117     /**
118      * Get the path for this node. The derived class is returned, making check
119      * for the last child of some parent easier.
120      */
121     TreePath getPath()
122     {
123       if (path == null)
124         {
125           boolean lastChild = false;
126           if (parent != null)
127             {
128               int nc = treeModel.getChildCount(parent);
129               if (nc > 0)
130                 {
131                   int n = treeModel.getIndexOfChild(parent, node);
132                   if (n == nc - 1)
133                     lastChild = true;
134                 }
135             }
136
137           LinkedList lpath = new LinkedList();
138           NodeRecord rp = this;
139           while (rp != null)
140             {
141               lpath.addFirst(rp.node);
142               if (rp.parent != null)
143                 {
144                   Object parent = rp.parent;
145                   rp = (NodeRecord) nodes.get(parent);
146                   // Add the root node, even if it is not visible.
147                   if (rp == null)
148                     lpath.addFirst(parent);
149                 }
150               else
151                 rp = null;
152             }
153           path = new GnuPath(lpath.toArray(), lastChild);
154         }
155       return path;
156     }
157     
158     /**
159      * Get the rectangle bounds (compute, if required).
160      */
161     Rectangle getBounds()
162     {
163       // This method may be called in the context when the tree rectangle is
164       // not known. To work around this, it is assumed near infinitely large.
165       if (bounds == null)
166         bounds = getNodeDimensions(node, row, depth, isExpanded, 
167                                    new Rectangle());
168       return bounds;      
169     }
170   }
171   
172   /**
173    * The set of all expanded tree nodes.
174    */
175   Set expanded = new HashSet();
176   
177   /**
178    * Maps nodes to the row numbers.
179    */
180   Hashtable nodes = new Hashtable();
181   
182   /**
183    * Maps row numbers to nodes.
184    */
185   Hashtable row2node = new Hashtable();
186   
187   /**
188    * If true, the row map must be recomputed before using.
189    */
190   boolean dirty;
191   
192   /**
193    * The cumulative height of all rows.
194    */
195   int totalHeight;
196   
197   /**
198    * The maximal width.
199    */
200   int maximalWidth;
201
202   /**
203    * Creates the unitialised instance. Before using the class, the row height
204    * must be set with the {@link #setRowHeight(int)} and the model must be set
205    * with {@link #setModel(TreeModel)}. The node dimensions may not be set.
206    */
207   public VariableHeightLayoutCache()
208   {
209     // Nothing to do here.
210   } 
211
212   /**
213    * Get the total number of rows in the tree. Every displayed node occupies the
214    * single row. The root node row is included if the root node is set as
215    * visible (false by default).
216    * 
217    * @return int the number of the displayed rows.
218    */
219   public int getRowCount()
220   {
221     if (dirty) update();
222     return row2node.size();
223   } 
224   
225   /**
226    * Refresh the row map.
227    */
228   private final void update()
229   {
230     nodes.clear();
231     row2node.clear();
232     
233     totalHeight = maximalWidth = 0;
234
235     if (treeModel == null)
236       return;
237
238     Object root = treeModel.getRoot();
239
240     if (rootVisible)
241       {
242         countRows(root, null, 0);
243       }
244     else
245       {
246         int sc = treeModel.getChildCount(root);
247         for (int i = 0; i < sc; i++)
248           {
249             Object child = treeModel.getChild(root, i);
250             countRows(child, root, 0);
251           }
252       }
253     dirty = false;
254   }
255   
256   /**
257    * Recursively counts all rows in the tree.
258    */
259   private final void countRows(Object node, Object parent, int depth)
260   {
261     Integer n = new Integer(row2node.size());
262     row2node.put(n, node);
263     
264     NodeRecord nr = new NodeRecord(n.intValue(), depth, node, parent);
265     nodes.put(node, nr);
266      
267     // For expanded nodes
268     if (expanded.contains(node))
269       {
270         int sc = treeModel.getChildCount(node);
271         int deeper = depth + 1;
272         for (int i = 0; i < sc; i++)
273           {
274             Object child = treeModel.getChild(node, i);
275             countRows(child, node, deeper);
276           }
277       }
278   }
279
280   /**
281    * Discard the bound information for the given path.
282    * 
283    * @param path the path, for that the bound information must be recomputed.
284    */
285   public void invalidatePathBounds(TreePath path)
286   {
287     NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent());
288     if (r != null)
289       r.bounds = null;
290   } 
291
292   /**
293    * Mark all cached information as invalid.
294    */
295   public void invalidateSizes()
296   {
297     dirty = true;
298   } 
299
300   /**
301    * Set the expanded state of the given path. The expansion states must be
302    * always updated when expanding and colapsing the tree nodes. Otherwise 
303    * other methods will not work correctly after the nodes are collapsed or
304    * expanded.
305    *
306    * @param path the tree path, for that the state is being set.
307    * @param isExpanded the expanded state of the given path.
308    */
309   public void setExpandedState(TreePath path, boolean isExpanded)
310   {
311     if (isExpanded)
312       expanded.add(path.getLastPathComponent());
313     else
314       expanded.remove(path.getLastPathComponent());
315     
316     dirty = true;
317   }
318   
319   /**
320    * Get the expanded state for the given tree path.
321    * 
322    * @return true if the given path is expanded, false otherwise.
323    */
324   public boolean isExpanded(TreePath path)
325   {
326     return expanded.contains(path.getLastPathComponent());
327   } 
328
329   /**
330    * Get bounds for the given tree path.
331    * 
332    * @param path the tree path
333    * @param rect the rectangle that will be reused to return the result.
334    * @return Rectangle the bounds of the last line, defined by the given path.
335    */
336   public Rectangle getBounds(TreePath path, Rectangle rect)
337   {
338     if (path == null)
339       return null;
340     if (dirty)
341       update();
342     Object last = path.getLastPathComponent();
343     NodeRecord r = (NodeRecord) nodes.get(last);
344     if (r == null)
345     // This node is not visible.
346       {
347         rect.x = rect.y = rect.width = rect.height = 0;
348       }
349     else
350       {
351         if (r.bounds == null)
352           {
353             Rectangle dim = getNodeDimensions(last, r.row, r.depth,
354                                               r.isExpanded, rect);
355             r.bounds = dim;
356           }
357
358         rect.setRect(r.bounds);
359       }
360     return rect;
361   } 
362
363   /**
364    * Get the path, the last element of that is displayed in the given row.
365    * 
366    * @param row the row
367    * @return TreePath the path
368    */
369   public TreePath getPathForRow(int row)
370   {
371     if (dirty)
372       update();
373     Object last = row2node.get(new Integer(row));
374     if (last == null)
375       return null;
376     else
377       {
378         NodeRecord r = (NodeRecord) nodes.get(last);
379         return r.getPath();
380       }
381   } 
382
383   /**
384    * Get the row, displaying the last node of the given path.
385    * 
386    * @param path the path
387    * @return int the row number or -1 if the end of the path is not visible.
388    */
389   public int getRowForPath(TreePath path)
390   {
391     if (path == null)
392       return -1;
393     if (dirty) update();
394
395     NodeRecord r = (NodeRecord) nodes.get(path.getLastPathComponent());
396     if (r == null)
397       return - 1;
398     else
399       return r.row;
400   } 
401
402   /**
403    * Get the path, closest to the given point.
404    * 
405    * @param x the point x coordinate
406    * @param y the point y coordinate
407    * @return the tree path, closest to the the given point
408    */
409   public TreePath getPathClosestTo(int x, int y)
410   {
411     if (dirty)
412       update();
413
414     // As the rows have arbitrary height, we need to iterate.
415     NodeRecord best = null;
416     NodeRecord r;
417     Enumeration en = nodes.elements();
418     
419     int dist = Integer.MAX_VALUE;
420
421     while (en.hasMoreElements() && dist > 0)
422       {
423         r = (NodeRecord) en.nextElement();
424         if (best == null)
425           {
426             best = r;
427             dist = distance(r.getBounds(), x, y);
428           }
429         else
430           {
431             int rr = distance(r.getBounds(), x, y);
432             if (rr < dist)
433               {
434                 best = r;
435                 dist = rr;
436               }
437           }
438       }
439
440     if (best == null)
441       return null;
442     else
443       return best.getPath();
444   } 
445   
446   /**
447    * Get the closest distance from this point till the given rectangle. Only
448    * vertical distance is taken into consideration.
449    */
450   int distance(Rectangle r, int x, int y)
451   {
452     if (y < r.y)
453       return r.y - y;
454     else if (y > r.y + r.height)
455       return y - (r.y + r.height);
456     else
457       return 0;
458   }
459
460   /**
461    * Get the number of the visible childs for the given tree path. If the node
462    * is not expanded, 0 is returned. Otherwise, the number of children is
463    * obtained from the model as the number of children for the last path
464    * component.
465    * 
466    * @param path the tree path
467    * @return int the number of the visible childs (for row).
468    */
469   public int getVisibleChildCount(TreePath path)  
470   {
471     if (isExpanded(path))
472       return 0; 
473     else
474       return treeModel.getChildCount(path.getLastPathComponent());
475   } 
476
477   /**
478    * Get the enumeration over all visible pathes that start from the given
479    * parent path.
480    * 
481    * @param parentPath the parent path
482    * @return the enumeration over pathes
483    */
484   public Enumeration getVisiblePathsFrom(TreePath parentPath)
485   {
486     if (dirty)
487       update();
488     Vector p = new Vector(parentPath.getPathCount());
489     Object node;
490     NodeRecord nr;
491
492     for (int i = 0; i < parentPath.getPathCount(); i++)
493       {
494         node = parentPath.getPathComponent(i);
495         nr = (NodeRecord) nodes.get(node);
496         if (nr.row >= 0)
497           p.add(node);
498       }
499     return p.elements();
500   }
501
502   /**
503    * Return the expansion state of the given tree path. The expansion state
504    * must be previously set with the 
505    * {@link #setExpandedState(TreePath, boolean)}
506    * 
507    * @param path the path being checked
508    * @return true if the last node of the path is expanded, false otherwise.
509    */
510   public boolean getExpandedState(TreePath path)
511   {
512     return expanded.contains(path.getLastPathComponent());
513   }
514
515   /**
516    * The listener method, called when the tree nodes are changed.
517    * 
518    * @param event the change event
519    */
520   public void treeNodesChanged(TreeModelEvent event)
521   {
522     dirty = true;
523   } 
524
525   /**
526    * The listener method, called when the tree nodes are inserted.
527    * 
528    * @param event the change event
529    */
530   public void treeNodesInserted(TreeModelEvent event)
531   {
532     dirty = true;
533   } 
534
535   /**
536    * The listener method, called when the tree nodes are removed.
537    * 
538    * @param event the change event
539    */
540   public void treeNodesRemoved(TreeModelEvent event)
541   {
542     dirty = true;
543   } 
544
545   /**
546    * Called when the tree structure has been changed. 
547    * 
548    * @param event the change event
549    */
550   public void treeStructureChanged(TreeModelEvent event)
551   {
552     dirty = true;
553   } 
554   
555   /**
556    * Set the tree model that will provide the data.
557    */
558   public void setModel(TreeModel newModel)
559   {
560     treeModel = newModel;
561     // We need to clear the table and update the layout,
562     // so that we don't end up with wrong data in the tables.
563     expanded.clear();
564     update();
565     if (treeModel != null)
566       {
567         // The root node is expanded by default.
568         expanded.add(treeModel.getRoot());
569         dirty = true;
570       }
571   }
572   
573   /**
574    * Inform the instance if the tree root node is visible. If this method
575    * is not called, it is assumed that the tree root node is not visible.
576    * 
577    * @param visible true if the tree root node is visible, false
578    * otherwise.
579    */
580   public void setRootVisible(boolean visible)
581   {
582     rootVisible = visible;
583     dirty = true;
584   }
585
586   /**
587    * Get the sum of heights for all rows.
588    */
589   public int getPreferredHeight()
590   {
591     if (dirty)
592       update();
593     totalHeight = 0;
594     Enumeration en = nodes.elements();
595     while (en.hasMoreElements())
596       {
597         NodeRecord nr = (NodeRecord) en.nextElement();
598         Rectangle r = nr.getBounds();
599         totalHeight += r.height;
600       }
601     return totalHeight;
602   }
603
604   /**
605    * Get the maximal width.
606    */
607   public int getPreferredWidth(Rectangle value)
608   {
609     if (dirty)
610       update();
611     
612     maximalWidth = 0;
613     Enumeration en = nodes.elements();
614     while (en.hasMoreElements())
615       {
616         NodeRecord nr = (NodeRecord) en.nextElement();
617         Rectangle r = nr.getBounds();
618         if (r.x + r.width > maximalWidth)
619           maximalWidth = r.x + r.width;
620       }
621     return maximalWidth;
622   }
623 }