KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > swing > outline > Outline


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.swing.outline;
21
22 import java.awt.Font JavaDoc;
23 import java.awt.FontMetrics JavaDoc;
24 import java.awt.Graphics JavaDoc;
25 import java.awt.Insets JavaDoc;
26 import java.awt.Rectangle JavaDoc;
27 import java.awt.event.ActionEvent JavaDoc;
28 import java.awt.event.ActionListener JavaDoc;
29 import java.awt.event.ComponentAdapter JavaDoc;
30 import java.awt.event.ComponentEvent JavaDoc;
31 import java.awt.event.ComponentListener JavaDoc;
32 import java.awt.event.MouseEvent JavaDoc;
33 import java.util.ArrayList JavaDoc;
34 import java.util.EventObject JavaDoc;
35 import java.util.List JavaDoc;
36 import javax.swing.JScrollBar JavaDoc;
37 import javax.swing.JScrollPane JavaDoc;
38 import javax.swing.JTable JavaDoc;
39 import javax.swing.JTree JavaDoc;
40 import javax.swing.JViewport JavaDoc;
41 import javax.swing.ListSelectionModel JavaDoc;
42 import javax.swing.Timer JavaDoc;
43 import javax.swing.UIManager JavaDoc;
44 import javax.swing.event.TableModelEvent JavaDoc;
45 import javax.swing.event.TreeModelEvent JavaDoc;
46 import javax.swing.table.TableCellRenderer JavaDoc;
47 import javax.swing.table.TableModel JavaDoc;
48 import javax.swing.tree.AbstractLayoutCache JavaDoc;
49 import javax.swing.tree.TreePath JavaDoc;
50
51 /** An Outline, or tree-table component. Takes an instance of OutlineModel,
52  * an interface which merges TreeModel and TableModel.
53  * <p>
54  * Simplest usage:
55  * <ol>
56  * <li>Create a standard tree model for the tree node portion of the outline.</li>
57  * <li>Implement RowModel. RowModel is a subset of TableModel - it is passed
58  * the value in column 0 of the Outline and a column index, and returns the
59  * value in the column in question.</li>
60  * <li>Pass the TreeModel and the RowModel to <code>DefaultOutlineModel.createModel()</code>
61  * </ol>
62  * This will generate an instance of DefaultOutlineModel which will use the
63  * TreeModel for the rows/tree column content, and use the RowModel to provide
64  * the additional table columns.
65  * <p>
66  * It is also useful to provide an implementation of <code>RenderDataProvider</code>
67  * to supply icons and affect text display of cells - this covers most of the
68  * needs for which it is necessary to write a custom cell renderer in JTable/JTree.
69  * <p>
70  * <b>Example usage:</b><br>
71  * Assume FileTreeModel is a model which, given a root directory, will
72  * expose the files and folders underneath it. We will implement a
73  * RowModel to expose the file size and date, and a RenderDataProvider which
74  * will use a gray color for uneditable files and expose the full file path as
75  * a tooltip. Assume the class this is implemented in is a
76  * JPanel subclass or other Swing container.
77  * <br>
78  * XXX todo: clean up formatting & edit for style
79  * <pre>
80  * public void initComponents() {
81  * setLayout (new BorderLayout());
82  * TreeModel treeMdl = new FileTreeModel (someDirectory);
83  *
84  * OutlineModel mdl = DefaultOutlineModel.createOutlineModel(treeMdl,
85  * new FileRowModel(), true);
86  * outline = new Outline();
87  * outline.setRenderDataProvider(new FileDataProvider());
88  * outline.setRootVisible (true);
89  * outline.setModel (mdl);
90  * add (outline, BorderLayout.CENTER);
91  * }
92  * private class FileRowModel implements RowModel {
93  * public Class getColumnClass(int column) {
94  * switch (column) {
95  * case 0 : return Date.class;
96  * case 1 : return Long.class;
97  * default : assert false;
98  * }
99  * return null;
100  * }
101  *
102  * public int getColumnCount() {
103  * return 2;
104  * }
105  *
106  * public String getColumnName(int column) {
107  * return column == 0 ? "Date" : "Size";
108  * }
109  *
110  * public Object getValueFor(Object node, int column) {
111  * File f = (File) node;
112  * switch (column) {
113  * case 0 : return new Date (f.lastModified());
114  * case 1 : return new Long (f.length());
115  * default : assert false;
116  * }
117  * return null;
118  * }
119  *
120  * public boolean isCellEditable(Object node, int column) {
121  * return false;
122  * }
123  *
124  * public void setValueFor(Object node, int column, Object value) {
125  * //do nothing, nothing is editable
126  * }
127  * }
128  *
129  * private class FileDataProvider implements RenderDataProvider {
130  * public java.awt.Color getBackground(Object o) {
131  * return null;
132  * }
133  *
134  * public String getDisplayName(Object o) {
135  * return ((File) o).getName();
136  * }
137  *
138  * public java.awt.Color getForeground(Object o) {
139  * File f = (File) o;
140  * if (!f.isDirectory() && !f.canWrite()) {
141  * return UIManager.getColor ("controlShadow");
142  * }
143  * return null;
144  * }
145  *
146  * public javax.swing.Icon getIcon(Object o) {
147  * return null;
148  * }
149  *
150  * public String getTooltipText(Object o) {
151  * return ((File) o).getAbsolutePath();
152  * }
153  *
154  * public boolean isHtmlDisplayName(Object o) {
155  * return false;
156  * }
157  * }
158  * </pre>
159  *
160  * @author Tim Boudreau
161  */

162 public final class Outline extends JTable JavaDoc {
163     //XXX plenty of methods missing here - add/remove tree expansion listeners,
164
//better path info/queries, etc.
165

166     private boolean initialized = false;
167     private Boolean JavaDoc cachedRootVisible = null;
168     private RenderDataProvider renderDataProvider = null;
169     private ComponentListener JavaDoc componentListener = null;
170     /** Creates a new instance of Outline */
171     public Outline() {
172         init();
173     }
174     
175     public Outline(OutlineModel mdl) {
176         super (mdl);
177         init();
178     }
179     
180     private void init() {
181         initialized = true;
182         setDefaultRenderer(Object JavaDoc.class, new DefaultOutlineCellRenderer());
183     }
184     
185     /** Always returns the default renderer for Object.class for the tree column */
186     public TableCellRenderer JavaDoc getCellRenderer(int row, int column) {
187         TableCellRenderer JavaDoc result;
188         if (column == 0) {
189             result = getDefaultRenderer(Object JavaDoc.class);
190         } else {
191             result = super.getCellRenderer(row, column);
192         }
193         return result;
194     }
195     
196     /** Get the RenderDataProvider which is providing text, icons and tooltips
197      * for items in the tree column. The default property for this value is
198      * null, in which case standard JTable/JTree object -> icon/string
199      * conventions are used */

200     public RenderDataProvider getRenderDataProvider() {
201         return renderDataProvider;
202     }
203     
204     /** Set the RenderDataProvider which will provide text, icons and tooltips
205      * for items in the tree column. The default is null. If null,
206      * the data displayed will be generated in the standard JTable/JTree way -
207      * calling <code>toString()</code> on objects in the tree model and
208      * using the look and feel's default tree folder and tree leaf icons. */

209     public void setRenderDataProvider (RenderDataProvider provider) {
210         if (provider != renderDataProvider) {
211             RenderDataProvider old = renderDataProvider;
212             renderDataProvider = provider;
213             firePropertyChange ("renderDataProvider", old, provider); //NOI18N
214
}
215     }
216     
217     /** Get the TreePathSupport object which manages path expansion for this
218      * Outline. */

219     TreePathSupport getTreePathSupport () {
220         OutlineModel mdl = getOutlineModel();
221         if (mdl != null) {
222             return mdl.getTreePathSupport();
223         } else {
224             return null;
225         }
226     }
227     
228     /** Get the layout cache which manages layout data for the Outline.
229      * <strong>Under no circumstances directly call the methods on the
230      * layout cache which change the expanded state - such changes will not
231      * be propagated into the table model, and will leave the model and
232      * its layout in inconsistent states. Any calls that affect expanded
233      * state must go through <code>getTreePathSupport()</code>.</strong> */

234     AbstractLayoutCache JavaDoc getLayoutCache () {
235         OutlineModel mdl = getOutlineModel();
236         if (mdl != null) {
237             return mdl.getLayout();
238         } else {
239             return null;
240         }
241     }
242     
243     boolean isTreeColumnIndex (int column) {
244         //XXX fixme - this is not true if columns have been dragged
245
return column == 0;
246     }
247     
248     public boolean isVisible (TreePath JavaDoc path) {
249         if (getTreePathSupport() != null) {
250             return getTreePathSupport().isVisible(path);
251         }
252         return false;
253     }
254     
255     /** Overridden to pass the fixed row height to the tree layout cache */
256     public void setRowHeight(int val) {
257         super.setRowHeight(val);
258         if (getLayoutCache() != null) {
259             getLayoutCache().setRowHeight(val);
260         }
261     }
262     
263     /** Set whether or not the root is visible */
264     public void setRootVisible (boolean val) {
265         if (getOutlineModel() == null) {
266             cachedRootVisible = val ? Boolean.TRUE : Boolean.FALSE;
267         }
268         if (val != isRootVisible()) {
269             //TODO - need to force a property change on the model,
270
//the layout cache doesn't have direct listener support
271
getLayoutCache().setRootVisible(val);
272             firePropertyChange("rootVisible", !val, val); //NOI18N
273
}
274     }
275     
276     /** Is the tree root visible. Default value is true. */
277     public boolean isRootVisible() {
278         if (getLayoutCache() == null) {
279             return cachedRootVisible != null ?
280                 cachedRootVisible.booleanValue() : true;
281         } else {
282             return getLayoutCache().isRootVisible();
283         }
284     }
285     
286     /** Overridden to throw an exception if the passed model is not an instance
287      * of <code>OutlineModel</code> (with the exception of calls from the
288      * superclass constructor) */

289     public void setModel (TableModel JavaDoc mdl) {
290         if (initialized && (!(mdl instanceof OutlineModel))) {
291             throw new IllegalArgumentException JavaDoc (
292                 "Table model for an Outline must be an instance of " +
293                 "OutlineModel"); //NOI18N
294
}
295         if (mdl instanceof OutlineModel) {
296             AbstractLayoutCache JavaDoc layout = ((OutlineModel) mdl).getLayout();
297             if (cachedRootVisible != null) {
298                 
299                 layout.setRootVisible(
300                     cachedRootVisible.booleanValue());
301                 
302             }
303             
304             layout.setRowHeight(getRowHeight());
305             
306             if (((OutlineModel) mdl).isLargeModel()) {
307                 addComponentListener (getComponentListener());
308                 layout.setNodeDimensions(new ND());
309             } else {
310                 if (componentListener != null) {
311                     removeComponentListener (componentListener);
312                     componentListener = null;
313                 }
314             }
315         }
316         
317         super.setModel(mdl);
318     }
319     
320     /** Convenience getter for the <code>TableModel</code> as an instance of
321      * OutlineModel. If no OutlineModel has been set, returns null. */

322     public OutlineModel getOutlineModel() {
323         TableModel JavaDoc mdl = getModel();
324         if (mdl instanceof OutlineModel) {
325             return (OutlineModel) getModel();
326         } else {
327             return null;
328         }
329     }
330     
331     /** Expand a tree path */
332     public void expandPath (TreePath JavaDoc path) {
333         getTreePathSupport().expandPath (path);
334     }
335     
336     public void collapsePath (TreePath JavaDoc path) {
337         getTreePathSupport().collapsePath (path);
338     }
339     
340     public Rectangle JavaDoc getPathBounds(TreePath JavaDoc path) {
341         Insets JavaDoc i = getInsets();
342         Rectangle JavaDoc bounds = getLayoutCache().getBounds(path, null);
343
344         if(bounds != null && i != null) {
345             bounds.x += i.left;
346             bounds.y += i.top;
347         }
348         return bounds;
349     }
350     
351     public TreePath JavaDoc getClosestPathForLocation(int x, int y) {
352         Insets JavaDoc i = getInsets();
353         if (i != null) {
354             return getLayoutCache().getPathClosestTo(x - i.left, y - i.top);
355         } else {
356             return getLayoutCache().getPathClosestTo(x,y);
357         }
358     }
359     
360     public boolean editCellAt (int row, int column, EventObject JavaDoc e) {
361         //If it was on column 0, it may be a request to expand a tree
362
//node - check for that first.
363
if (isTreeColumnIndex (column) && e instanceof MouseEvent JavaDoc) {
364             MouseEvent JavaDoc me = (MouseEvent JavaDoc) e;
365             TreePath JavaDoc path = getLayoutCache().getPathClosestTo(me.getX(), me.getY());
366             if (!getOutlineModel().isLeaf(path.getLastPathComponent())) {
367                 int handleWidth = DefaultOutlineCellRenderer.getExpansionHandleWidth();
368                 Insets JavaDoc ins = getInsets();
369                 int handleStart = ins.left + ((path.getPathCount() - 1) * DefaultOutlineCellRenderer.getNestingWidth());
370                 int handleEnd = ins.left + handleStart + handleWidth;
371
372                 //TODO: Translate x/y to position of column if non-0
373

374                 if ((me.getX() > ins.left && me.getX() >= handleStart && me.getX() <= handleEnd) ||
375                      me.getClickCount() > 1) {
376
377                     boolean expanded = getLayoutCache().isExpanded(path);
378                     if (!expanded) {
379                         getTreePathSupport().expandPath(path);
380                     } else {
381                         getTreePathSupport().collapsePath(path);
382                     }
383                     return false;
384                 }
385             }
386         }
387             
388         return super.editCellAt(row, column, e);
389     }
390
391     private boolean needCalcRowHeight = true;
392     /** Calculate the height of rows based on the current font. This is
393      * done when the first paint occurs, to ensure that a valid Graphics
394      * object is available. */

395     private void calcRowHeight(Graphics JavaDoc g) {
396         //Users of themes can set an explicit row height, so check for it
397
Integer JavaDoc i = (Integer JavaDoc) UIManager.get("netbeans.outline.rowHeight"); //NOI18N
398

399         int rowHeight;
400         if (i != null) {
401             rowHeight = i.intValue();
402         } else {
403             //Derive a row height to accomodate the font and expando icon
404
Font JavaDoc f = getFont();
405             FontMetrics JavaDoc fm = g.getFontMetrics(f);
406             rowHeight = Math.max(fm.getHeight()+3,
407                 DefaultOutlineCellRenderer.getExpansionHandleHeight());
408         }
409         //Clear the flag
410
needCalcRowHeight = false;
411         //Set row height. If displayable, this will generate a new call
412
//to paint()
413
setRowHeight(rowHeight);
414     }
415     
416     public void tableChanged(TableModelEvent JavaDoc e) {
417 // System.err.println("Table got tableChanged " + e);
418
super.tableChanged(e);
419 // System.err.println("row count is " + getRowCount());
420
}
421     
422     public void paint(Graphics JavaDoc g) {
423         if (needCalcRowHeight) {
424             calcRowHeight(g);
425             //CalcRowHeight will trigger a repaint
426
return;
427         }
428         super.paint(g);
429     }
430     
431     /** Create a component listener to handle size changes if the table model
432      * is large-model */

433     private ComponentListener JavaDoc getComponentListener() {
434         if (componentListener == null) {
435             componentListener = new SizeManager();
436         }
437         return componentListener;
438     }
439     
440     private JScrollPane JavaDoc getScrollPane() {
441         JScrollPane JavaDoc result = null;
442         if (getParent() instanceof JViewport JavaDoc) {
443             if (((JViewport JavaDoc) getParent()).getParent() instanceof JScrollPane JavaDoc) {
444                 result = (JScrollPane JavaDoc) ((JViewport JavaDoc) getParent()).getParent();
445             }
446         }
447         return result;
448     }
449     
450     private void change() {
451         revalidate();
452         repaint();
453     }
454     
455     private class ND extends AbstractLayoutCache.NodeDimensions JavaDoc {
456         
457         public Rectangle JavaDoc getNodeDimensions(Object JavaDoc value, int row, int depth,
458             boolean expanded, Rectangle JavaDoc bounds) {
459                 
460                 int wid = Outline.this.getColumnModel().getColumn(0).getPreferredWidth();
461                 bounds.setBounds (0, row * getRowHeight(), wid, getRowHeight());
462                 return bounds;
463         }
464         
465     }
466     
467     
468     /** A component listener. If we're a large model table, we need
469      * to inform the FixedHeightLayoutCache when the size changes, so it
470      * can update its mapping of visible nodes */

471     private class SizeManager extends ComponentAdapter JavaDoc implements ActionListener JavaDoc {
472     protected Timer JavaDoc timer = null;
473     protected JScrollBar JavaDoc scrollBar = null;
474         
475         public void componentMoved(ComponentEvent JavaDoc e) {
476         if(timer == null) {
477         JScrollPane JavaDoc scrollPane = getScrollPane();
478
479         if(scrollPane == null) {
480             change();
481                 } else {
482             scrollBar = scrollPane.getVerticalScrollBar();
483             if(scrollBar == null ||
484             !scrollBar.getValueIsAdjusting()) {
485             // Try the horizontal scrollbar.
486
if((scrollBar = scrollPane.getHorizontalScrollBar())
487                 != null && scrollBar.getValueIsAdjusting()) {
488                                 
489                 startTimer();
490                         } else {
491                 change();
492                         }
493             } else {
494             startTimer();
495                     }
496         }
497         }
498         }
499         
500     protected void startTimer() {
501         if(timer == null) {
502         timer = new Timer JavaDoc(200, this);
503         timer.setRepeats(true);
504         }
505         timer.start();
506     }
507         
508     public void actionPerformed(ActionEvent JavaDoc ae) {
509         if(scrollBar == null || !scrollBar.getValueIsAdjusting()) {
510         if(timer != null)
511             timer.stop();
512         change();
513         timer = null;
514         scrollBar = null;
515         }
516     }
517         
518         public void componentHidden(ComponentEvent JavaDoc e) {
519         }
520         
521         public void componentResized(ComponentEvent JavaDoc e) {
522         }
523         
524         public void componentShown(ComponentEvent JavaDoc e) {
525         }
526     }
527 }
528
Popular Tags