KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mc4j > console > swing > treetable > JTreeTable


1 package org.mc4j.console.swing.treetable;
2
3 /*
4  * @(#)JTreeTable.java 1.2 98/10/27
5  *
6  * Copyright 1997, 1998 by Sun Microsystems, Inc.,
7  * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
8  * All rights reserved.
9  *
10  * This software is the confidential and proprietary information
11  * of Sun Microsystems, Inc. ("Confidential Information"). You
12  * shall not disclose such Confidential Information and shall use
13  * it only in accordance with the terms of the license agreement
14  * you entered into with Sun.
15  */

16
17 import java.awt.Color JavaDoc;
18 import java.awt.Component JavaDoc;
19 import java.awt.Dimension JavaDoc;
20 import java.awt.Graphics JavaDoc;
21 import java.awt.Rectangle JavaDoc;
22 import java.awt.event.InputEvent JavaDoc;
23 import java.awt.event.MouseEvent JavaDoc;
24 import java.util.EventObject JavaDoc;
25
26 import javax.swing.DefaultCellEditor JavaDoc;
27 import javax.swing.Icon JavaDoc;
28 import javax.swing.JTable JavaDoc;
29 import javax.swing.JTextField JavaDoc;
30 import javax.swing.JTree JavaDoc;
31 import javax.swing.ListSelectionModel JavaDoc;
32 import javax.swing.LookAndFeel JavaDoc;
33 import javax.swing.UIManager JavaDoc;
34 import javax.swing.border.Border JavaDoc;
35 import javax.swing.event.ListSelectionEvent JavaDoc;
36 import javax.swing.event.ListSelectionListener JavaDoc;
37 import javax.swing.table.TableCellRenderer JavaDoc;
38 import javax.swing.tree.DefaultTreeCellRenderer JavaDoc;
39 import javax.swing.tree.DefaultTreeSelectionModel JavaDoc;
40 import javax.swing.tree.TreeCellRenderer JavaDoc;
41 import javax.swing.tree.TreeModel JavaDoc;
42 import javax.swing.tree.TreePath JavaDoc;
43
44 /**
45  * This example shows how to create a simple JTreeTable component,
46  * by using a JTree as a renderer (and editor) for the cells in a
47  * particular column in the JTable.
48  *
49  * @author Philip Milne
50  * @author Scott Violet
51  * @version 1.2 10/27/98
52  */

53 public class JTreeTable extends JTable JavaDoc {
54     /**
55      * A subclass of JTree.
56      */

57     protected TreeTableCellRenderer tree;
58
59
60     public JTreeTable(TreeTableModel treeTableModel) {
61         super();
62
63         // Creates the tree. It will be used as a renderer and editor.
64
tree = new TreeTableCellRenderer(this, treeTableModel);
65
66         // Installs a tableModel representing the visible rows in the tree.
67
super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
68
69         // Forces the JTable and JTree to share their row selection models.
70
ListToTreeSelectionModelWrapper selectionWrapper = new
71             ListToTreeSelectionModelWrapper();
72         tree.setSelectionModel(selectionWrapper);
73         setSelectionModel(selectionWrapper.getListSelectionModel());
74
75         // Installs the tree editor renderer and editor.
76
setDefaultRenderer(TreeTableModel.class, tree);
77         setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
78
79         // No grid.
80
setShowGrid(false);
81
82         // No intercell spacing
83
setIntercellSpacing(new Dimension JavaDoc(0, 0));
84
85         // And update the height of the trees row to match that of
86
// the table.
87
if (tree.getRowHeight() < 1) {
88             // Metal looks better like this.
89
setRowHeight(20);
90         }
91     }
92
93     /**
94      * Overridden to message super and forward the method to the tree.
95      * Since the tree is not actually in the component hierarchy it will
96      * never receive this unless we forward it in this manner.
97      */

98     public void updateUI() {
99         super.updateUI();
100         if (tree != null) {
101             tree.updateUI();
102             // Do this so that the editor is referencing the current renderer
103
// from the tree. The renderer can potentially change each time
104
// laf changes.
105
setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
106         }
107         // Use the tree's default foreground and background colors in the
108
// table.
109
LookAndFeel.installColorsAndFont(this, "Tree.background",
110             "Tree.foreground", "Tree.font");
111     }
112
113     /**
114      * Workaround for BasicTableUI anomaly. Make sure the UI never tries to
115      * resize the editor. The UI currently uses different techniques to
116      * paint the renderers and editors; overriding setBounds() below
117      * is not the right thing to do for an editor. Returning -1 for the
118      * editing row in this case, ensures the editor is never painted.
119      */

120     public int getEditingRow() {
121         return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 :
122             editingRow;
123     }
124
125     /**
126      * Returns the actual row that is editing as <code>getEditingRow</code>
127      * will always return -1.
128      */

129     private int realEditingRow() {
130         return editingRow;
131     }
132
133     /**
134      * This is overridden to invoke super's implementation, and then,
135      * if the receiver is editing a Tree column, the editor's bounds is
136      * reset. The reason we have to do this is because JTable doesn't
137      * think the table is being edited, as <code>getEditingRow</code> returns
138      * -1, and therefore doesn't automatically resize the editor for us.
139      */

140     public void sizeColumnsToFit(int resizingColumn) {
141         super.sizeColumnsToFit(resizingColumn);
142         if (getEditingColumn() != -1 && getColumnClass(editingColumn) ==
143             TreeTableModel.class) {
144             Rectangle JavaDoc cellRect = getCellRect(realEditingRow(),
145                 getEditingColumn(), false);
146             Component JavaDoc component = getEditorComponent();
147             component.setBounds(cellRect);
148             component.validate();
149         }
150     }
151
152     /**
153      * Overridden to pass the new rowHeight to the tree.
154      */

155     public void setRowHeight(int rowHeight) {
156         super.setRowHeight(rowHeight);
157         if (tree != null && tree.getRowHeight() != rowHeight) {
158             tree.setRowHeight(getRowHeight());
159         }
160     }
161
162     /**
163      * Returns the tree that is being shared between the model.
164      */

165     public JTree JavaDoc getTree() {
166         return tree;
167     }
168
169     /**
170      * Overridden to invoke repaint for the particular location if
171      * the column contains the tree. This is done as the tree editor does
172      * not fill the bounds of the cell, we need the renderer to paint
173      * the tree in the background, and then draw the editor over it.
174      */

175     public boolean editCellAt(int row, int column, EventObject JavaDoc e) {
176         boolean retValue = super.editCellAt(row, column, e);
177         if (retValue && getColumnClass(column) == TreeTableModel.class) {
178             repaint(getCellRect(row, column, false));
179         }
180         return retValue;
181     }
182
183     /**
184      * A TreeCellRenderer that displays a JTree.
185      */

186     public static class TreeTableCellRenderer extends JTree JavaDoc implements
187         TableCellRenderer JavaDoc {
188         /**
189          * Last table/tree row asked to renderer.
190          */

191         protected int visibleRow;
192         /**
193          * Border to draw around the tree, if this is non-null, it will
194          * be painted.
195          */

196         protected Border JavaDoc highlightBorder;
197
198         protected JTreeTable treeTable;
199
200         public TreeTableCellRenderer(JTreeTable treeTable, TreeModel JavaDoc model) {
201             super(model);
202             this.treeTable = treeTable;
203         }
204
205         /**
206          * updateUI is overridden to set the colors of the Tree's renderer
207          * to match that of the table.
208          */

209         public void updateUI() {
210             super.updateUI();
211             // Make the tree's cell renderer use the table's cell selection
212
// colors.
213
TreeCellRenderer JavaDoc tcr = getCellRenderer();
214             if (tcr instanceof DefaultTreeCellRenderer JavaDoc) {
215                 DefaultTreeCellRenderer JavaDoc dtcr = ((DefaultTreeCellRenderer JavaDoc) tcr);
216                 // For 1.1 uncomment this, 1.2 has a bug that will cause an
217
// exception to be thrown if the border selection color is
218
// null.
219
// dtcr.setBorderSelectionColor(null);
220
dtcr.setTextSelectionColor(UIManager.getColor
221                     ("Table.selectionForeground"));
222                 dtcr.setBackgroundSelectionColor(UIManager.getColor
223                     ("Table.selectionBackground"));
224             }
225         }
226
227         /**
228          * Sets the row height of the tree, and forwards the row height to
229          * the table.
230          */

231         public void setRowHeight(int rowHeight) {
232             if (rowHeight > 0) {
233                 super.setRowHeight(rowHeight);
234                 if (this.treeTable != null &&
235                     this.treeTable.getRowHeight() != rowHeight) {
236                     this.treeTable.setRowHeight(getRowHeight());
237                 }
238             }
239         }
240
241         /**
242          * This is overridden to set the height to match that of the JTable.
243          */

244         public void setBounds(int x, int y, int w, int h) {
245             super.setBounds(x, 0, w, this.treeTable.getHeight());
246         }
247
248         /**
249          * Sublcassed to translate the graphics such that the last visible
250          * row will be drawn at 0,0.
251          */

252         public void paint(Graphics JavaDoc g) {
253             g.translate(0, -visibleRow * getRowHeight());
254             super.paint(g);
255             // Draw the Table border if we have focus.
256
if (highlightBorder != null) {
257                 highlightBorder.paintBorder(this, g, 0, visibleRow *
258                     getRowHeight(), getWidth(),
259                     getRowHeight());
260             }
261         }
262
263         /**
264          * TreeCellRenderer method. Overridden to update the visible row.
265          */

266         public Component JavaDoc getTableCellRendererComponent(JTable JavaDoc table,
267                                                        Object JavaDoc value,
268                                                        boolean isSelected,
269                                                        boolean hasFocus,
270                                                        int row, int column) {
271             Color JavaDoc background;
272             Color JavaDoc foreground;
273
274             if (isSelected) {
275                 background = table.getSelectionBackground();
276                 foreground = table.getSelectionForeground();
277             } else {
278                 background = table.getBackground();
279                 foreground = table.getForeground();
280             }
281             highlightBorder = null;
282             if (this.treeTable.realEditingRow() == row && this.treeTable.getEditingColumn() == column) {
283                 background = UIManager.getColor("Table.focusCellBackground");
284                 foreground = UIManager.getColor("Table.focusCellForeground");
285             } else if (hasFocus) {
286                 highlightBorder = UIManager.getBorder
287                     ("Table.focusCellHighlightBorder");
288                 if (this.treeTable.isCellEditable(row, column)) {
289                     background = UIManager.getColor
290                         ("Table.focusCellBackground");
291                     foreground = UIManager.getColor
292                         ("Table.focusCellForeground");
293                 }
294             }
295
296             visibleRow = row;
297             setBackground(background);
298
299             TreeCellRenderer JavaDoc tcr = getCellRenderer();
300             if (tcr instanceof DefaultTreeCellRenderer JavaDoc) {
301                 DefaultTreeCellRenderer JavaDoc dtcr = ((DefaultTreeCellRenderer JavaDoc) tcr);
302                 if (isSelected) {
303                     dtcr.setTextSelectionColor(foreground);
304                     dtcr.setBackgroundSelectionColor(background);
305                 } else {
306                     dtcr.setTextNonSelectionColor(foreground);
307                     dtcr.setBackgroundNonSelectionColor(background);
308                 }
309             }
310             return this;
311         }
312     }
313
314
315     /**
316      * An editor that can be used to edit the tree column. This extends
317      * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField)
318      * to perform the actual editing.
319      * <p>To support editing of the tree column we can not make the tree
320      * editable. The reason this doesn't work is that you can not use
321      * the same component for editing and renderering. The table may have
322      * the need to paint cells, while a cell is being edited. If the same
323      * component were used for the rendering and editing the component would
324      * be moved around, and the contents would change. When editing, this
325      * is undesirable, the contents of the text field must stay the same,
326      * including the caret blinking, and selections persisting. For this
327      * reason the editing is done via a TableCellEditor.
328      * <p>Another interesting thing to be aware of is how tree positions
329      * its render and editor. The render/editor is responsible for drawing the
330      * icon indicating the type of node (leaf, branch...). The tree is
331      * responsible for drawing any other indicators, perhaps an additional
332      * +/- sign, or lines connecting the various nodes. So, the renderer
333      * is positioned based on depth. On the other hand, table always makes
334      * its editor fill the contents of the cell. To get the allusion
335      * that the table cell editor is part of the tree, we don't want the
336      * table cell editor to fill the cell bounds. We want it to be placed
337      * in the same manner as tree places it editor, and have table message
338      * the tree to paint any decorations the tree wants. Then, we would
339      * only have to worry about the editing part. The approach taken
340      * here is to determine where tree would place the editor, and to override
341      * the <code>reshape</code> method in the JTextField component to
342      * nudge the textfield to the location tree would place it. Since
343      * JTreeTable will paint the tree behind the editor everything should
344      * just work. So, that is what we are doing here. Determining of
345      * the icon position will only work if the TreeCellRenderer is
346      * an instance of DefaultTreeCellRenderer. If you need custom
347      * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer,
348      * and you want to support editing in JTreeTable, you will have
349      * to do something similiar.
350      */

351     public class TreeTableCellEditor extends DefaultCellEditor JavaDoc {
352         public TreeTableCellEditor() {
353             super(new TreeTableTextField());
354         }
355
356         /**
357          * Overridden to determine an offset that tree would place the
358          * editor at. The offset is determined from the
359          * <code>getRowBounds</code> JTree method, and additionally
360          * from the icon DefaultTreeCellRenderer will use.
361          * <p>The offset is then set on the TreeTableTextField component
362          * created in the constructor, and returned.
363          */

364         public Component JavaDoc getTableCellEditorComponent(JTable JavaDoc table,
365                                                      Object JavaDoc value,
366                                                      boolean isSelected,
367                                                      int r, int c) {
368             Component JavaDoc component = super.getTableCellEditorComponent
369                 (table, value, isSelected, r, c);
370             JTree JavaDoc t = getTree();
371             boolean rv = t.isRootVisible();
372             int offsetRow = rv ? r : r - 1;
373             Rectangle JavaDoc bounds = t.getRowBounds(offsetRow);
374             int offset = bounds.x;
375             TreeCellRenderer JavaDoc tcr = t.getCellRenderer();
376             if (tcr instanceof DefaultTreeCellRenderer JavaDoc) {
377                 Object JavaDoc node = t.getPathForRow(offsetRow).
378                     getLastPathComponent();
379                 Icon JavaDoc icon;
380                 if (t.getModel().isLeaf(node))
381                     icon = ((DefaultTreeCellRenderer JavaDoc) tcr).getLeafIcon();
382                 else if (tree.isExpanded(offsetRow))
383                     icon = ((DefaultTreeCellRenderer JavaDoc) tcr).getOpenIcon();
384                 else
385                     icon = ((DefaultTreeCellRenderer JavaDoc) tcr).getClosedIcon();
386                 if (icon != null) {
387                     offset += ((DefaultTreeCellRenderer JavaDoc) tcr).getIconTextGap() +
388                         icon.getIconWidth();
389                 }
390             }
391             ((TreeTableTextField) getComponent()).offset = offset;
392             return component;
393         }
394
395         /**
396          * This is overridden to forward the event to the tree. This will
397          * return true if the click count >= 3, or the event is null.
398          */

399         public boolean isCellEditable(EventObject JavaDoc e) {
400             if (e instanceof MouseEvent JavaDoc) {
401                 MouseEvent JavaDoc me = (MouseEvent JavaDoc) e;
402                 // If the modifiers are not 0 (or the left mouse button),
403
// tree may try and toggle the selection, and table
404
// will then try and toggle, resulting in the
405
// selection remaining the same. To avoid this, we
406
// only dispatch when the modifiers are 0 (or the left mouse
407
// button).
408
if (me.getModifiers() == 0 ||
409                     me.getModifiers() == InputEvent.BUTTON1_MASK) {
410                     for (int counter = getColumnCount() - 1; counter >= 0;
411                          counter--) {
412                         if (getColumnClass(counter) == TreeTableModel.class) {
413                             MouseEvent JavaDoc newME = new MouseEvent JavaDoc
414                                 (JTreeTable.this.tree, me.getID(),
415                                     me.getWhen(), me.getModifiers(),
416                                     me.getX() - getCellRect(0, counter, true).x,
417                                     me.getY(), me.getClickCount(),
418                                     me.isPopupTrigger());
419                             JTreeTable.this.tree.dispatchEvent(newME);
420                             break;
421                         }
422                     }
423                 }
424                 if (me.getClickCount() >= 3) {
425                     return true;
426                 }
427                 return false;
428             }
429             if (e == null) {
430                 return true;
431             }
432             return false;
433         }
434     }
435
436
437     /**
438      * Component used by TreeTableCellEditor. The only thing this does
439      * is to override the <code>reshape</code> method, and to ALWAYS
440      * make the x location be <code>offset</code>.
441      */

442     static class TreeTableTextField extends JTextField JavaDoc {
443         public int offset;
444
445         public void reshape(int x, int y, int w, int h) {
446             int newX = Math.max(x, offset);
447             super.reshape(newX, y, w - (newX - x), h);
448         }
449     }
450
451
452     /**
453      * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
454      * to listen for changes in the ListSelectionModel it maintains. Once
455      * a change in the ListSelectionModel happens, the paths are updated
456      * in the DefaultTreeSelectionModel.
457      */

458     class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel JavaDoc {
459         /**
460          * Set to true when we are updating the ListSelectionModel.
461          */

462         protected boolean updatingListSelectionModel;
463
464         public ListToTreeSelectionModelWrapper() {
465             super();
466             getListSelectionModel().addListSelectionListener
467                 (createListSelectionListener());
468         }
469
470         /**
471          * Returns the list selection model. ListToTreeSelectionModelWrapper
472          * listens for changes to this model and updates the selected paths
473          * accordingly.
474          */

475         ListSelectionModel JavaDoc getListSelectionModel() {
476             return listSelectionModel;
477         }
478
479         /**
480          * This is overridden to set <code>updatingListSelectionModel</code>
481          * and message super. This is the only place DefaultTreeSelectionModel
482          * alters the ListSelectionModel.
483          */

484         public void resetRowSelection() {
485             if (!updatingListSelectionModel) {
486                 updatingListSelectionModel = true;
487                 try {
488                     super.resetRowSelection();
489                 } finally {
490                     updatingListSelectionModel = false;
491                 }
492             }
493             // Notice how we don't message super if
494
// updatingListSelectionModel is true. If
495
// updatingListSelectionModel is true, it implies the
496
// ListSelectionModel has already been updated and the
497
// paths are the only thing that needs to be updated.
498
}
499
500         /**
501          * Creates and returns an instance of ListSelectionHandler.
502          */

503         protected ListSelectionListener JavaDoc createListSelectionListener() {
504             return new ListSelectionHandler();
505         }
506
507         /**
508          * If <code>updatingListSelectionModel</code> is false, this will
509          * reset the selected paths from the selected rows in the list
510          * selection model.
511          */

512         protected void updateSelectedPathsFromSelectedRows() {
513             if (!updatingListSelectionModel) {
514                 updatingListSelectionModel = true;
515                 try {
516                     // This is way expensive, ListSelectionModel needs an
517
// enumerator for iterating.
518
int min = listSelectionModel.getMinSelectionIndex();
519                     int max = listSelectionModel.getMaxSelectionIndex();
520
521                     clearSelection();
522                     if (min != -1 && max != -1) {
523                         for (int counter = min; counter <= max; counter++) {
524                             if (listSelectionModel.isSelectedIndex(counter)) {
525                                 TreePath JavaDoc selPath = tree.getPathForRow
526                                     (counter);
527
528                                 if (selPath != null) {
529                                     addSelectionPath(selPath);
530                                 }
531                             }
532                         }
533                     }
534                 } finally {
535                     updatingListSelectionModel = false;
536                 }
537             }
538         }
539
540         /**
541          * Class responsible for calling updateSelectedPathsFromSelectedRows
542          * when the selection of the list changse.
543          */

544         class ListSelectionHandler implements ListSelectionListener JavaDoc {
545             public void valueChanged(ListSelectionEvent JavaDoc e) {
546                 updateSelectedPathsFromSelectedRows();
547             }
548         }
549     }
550 }
551
552
Popular Tags