001: /*
002: * @(#)JTreeTable.java 1.2 98/10/27
003: *
004: * Copyright 1997, 1998 by Sun Microsystems, Inc.,
005: * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
006: * All rights reserved.
007: *
008: * This software is the confidential and proprietary information
009: * of Sun Microsystems, Inc. ("Confidential Information"). You
010: * shall not disclose such Confidential Information and shall use
011: * it only in accordance with the terms of the license agreement
012: * you entered into with Sun.
013: */
014:
015: package gruntspud.ui.treetable;
016:
017: import javax.swing.*;
018: import javax.swing.event.*;
019: import javax.swing.tree.*;
020: import javax.swing.table.*;
021:
022: import java.awt.Dimension;
023: import java.awt.Component;
024: import java.awt.Graphics;
025: import java.awt.Rectangle;
026:
027: import java.awt.event.MouseEvent;
028:
029: import java.util.EventObject;
030:
031: /**
032: * This example shows how to create a simple JTreeTable component,
033: * by using a JTree as a renderer (and editor) for the cells in a
034: * particular column in the JTable.
035: *
036: * @version 1.2 10/27/98
037: *
038: * @author Philip Milne
039: * @author Scott Violet
040: */
041: public class JTreeTable extends JTable {
042: /** A subclass of JTree. */
043: protected TreeTableCellRenderer tree;
044:
045: public JTreeTable(TreeTableModel treeTableModel) {
046: super ();
047:
048: // Create the tree. It will be used as a renderer and editor.
049: tree = new TreeTableCellRenderer(treeTableModel);
050:
051: // Install a tableModel representing the visible rows in the tree.
052: super .setModel(new TreeTableModelAdapter(treeTableModel, tree));
053:
054: // Force the JTable and JTree to share their row selection models.
055: ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper();
056: tree.setSelectionModel(selectionWrapper);
057: setSelectionModel(selectionWrapper.getListSelectionModel());
058:
059: // Install the tree editor renderer and editor.
060: setDefaultRenderer(TreeTableModel.class, tree);
061: setDefaultEditor(TreeTableModel.class,
062: new TreeTableCellEditor());
063:
064: // No grid.
065: setShowGrid(false);
066:
067: // No intercell spacing
068: setIntercellSpacing(new Dimension(0, 0));
069:
070: // And update the height of the trees row to match that of
071: // the table.
072: if (tree.getRowHeight() < 1) {
073: // Metal looks better like this.
074: setRowHeight(18);
075: }
076: }
077:
078: /**
079: * Overridden to message super and forward the method to the tree.
080: * Since the tree is not actually in the component hieachy it will
081: * never receive this unless we forward it in this manner.
082: */
083: public void updateUI() {
084: super .updateUI();
085: if (tree != null) {
086: tree.updateUI();
087: }
088: // Use the tree's default foreground and background colors in the
089: // table.
090: LookAndFeel.installColorsAndFont(this , "Tree.background",
091: "Tree.foreground", "Tree.font");
092: }
093:
094: /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
095: * paint the editor. The UI currently uses different techniques to
096: * paint the renderers and editors and overriding setBounds() below
097: * is not the right thing to do for an editor. Returning -1 for the
098: * editing row in this case, ensures the editor is never painted.
099: */
100: public int getEditingRow() {
101: return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1
102: : editingRow;
103: }
104:
105: /**
106: * Overridden to pass the new rowHeight to the tree.
107: */
108: public void setRowHeight(int rowHeight) {
109: super .setRowHeight(rowHeight);
110: if (tree != null && tree.getRowHeight() != rowHeight) {
111: tree.setRowHeight(getRowHeight());
112: }
113: }
114:
115: /**
116: * Returns the tree that is being shared between the model.
117: */
118: public JTree getTree() {
119: return tree;
120: }
121:
122: /**
123: * A TreeCellRenderer that displays a JTree.
124: */
125: public class TreeTableCellRenderer extends JTree implements
126: TableCellRenderer {
127: /** Last table/tree row asked to renderer. */
128: protected int visibleRow;
129:
130: public TreeTableCellRenderer(TreeModel model) {
131: super (model);
132: }
133:
134: /**
135: * updateUI is overridden to set the colors of the Tree's renderer
136: * to match that of the table.
137: */
138: public void updateUI() {
139: super .updateUI();
140: // Make the tree's cell renderer use the table's cell selection
141: // colors.
142: TreeCellRenderer tcr = getCellRenderer();
143: if (tcr instanceof DefaultTreeCellRenderer) {
144: DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
145: // For 1.1 uncomment this, 1.2 has a bug that will cause an
146: // exception to be thrown if the border selection color is
147: // null.
148: // dtcr.setBorderSelectionColor(null);
149: dtcr.setTextSelectionColor(UIManager
150: .getColor("Table.selectionForeground"));
151: dtcr.setBackgroundSelectionColor(UIManager
152: .getColor("Table.selectionBackground"));
153: }
154: }
155:
156: /**
157: * Sets the row height of the tree, and forwards the row height to
158: * the table.
159: */
160: public void setRowHeight(int rowHeight) {
161: if (rowHeight > 0) {
162: super .setRowHeight(rowHeight);
163: if (JTreeTable.this != null
164: && JTreeTable.this .getRowHeight() != rowHeight) {
165: JTreeTable.this .setRowHeight(getRowHeight());
166: }
167: }
168: }
169:
170: /**
171: * This is overridden to set the height to match that of the JTable.
172: */
173: public void setBounds(int x, int y, int w, int h) {
174: super .setBounds(x, 0, w, JTreeTable.this .getHeight());
175: }
176:
177: /**
178: * Sublcassed to translate the graphics such that the last visible
179: * row will be drawn at 0,0.
180: */
181: public void paint(Graphics g) {
182: g.translate(0, -visibleRow * getRowHeight());
183: super .paint(g);
184: }
185:
186: /**
187: * TreeCellRenderer method. Overridden to update the visible row.
188: */
189: public Component getTableCellRendererComponent(JTable table,
190: Object value, boolean isSelected, boolean hasFocus,
191: int row, int column) {
192: if (isSelected)
193: setBackground(table.getSelectionBackground());
194: else
195: setBackground(table.getBackground());
196:
197: visibleRow = row;
198: return this ;
199: }
200: }
201:
202: /**
203: * TreeTableCellEditor implementation. Component returned is the
204: * JTree.
205: */
206: public class TreeTableCellEditor extends AbstractCellEditor
207: implements TableCellEditor {
208: public Component getTableCellEditorComponent(JTable table,
209: Object value, boolean isSelected, int r, int c) {
210: return tree;
211: }
212:
213: /**
214: * Overridden to return false, and if the event is a mouse event
215: * it is forwarded to the tree.<p>
216: * The behavior for this is debatable, and should really be offered
217: * as a property. By returning false, all keyboard actions are
218: * implemented in terms of the table. By returning true, the
219: * tree would get a chance to do something with the keyboard
220: * events. For the most part this is ok. But for certain keys,
221: * such as left/right, the tree will expand/collapse where as
222: * the table focus should really move to a different column. Page
223: * up/down should also be implemented in terms of the table.
224: * By returning false this also has the added benefit that clicking
225: * outside of the bounds of the tree node, but still in the tree
226: * column will select the row, whereas if this returned true
227: * that wouldn't be the case.
228: * <p>By returning false we are also enforcing the policy that
229: * the tree will never be editable (at least by a key sequence).
230: */
231: public boolean isCellEditable(EventObject e) {
232: if (e instanceof MouseEvent) {
233: for (int counter = getColumnCount() - 1; counter >= 0; counter--) {
234: if (getColumnClass(counter) == TreeTableModel.class) {
235: MouseEvent me = (MouseEvent) e;
236: MouseEvent newME = new MouseEvent(tree, me
237: .getID(), me.getWhen(), me
238: .getModifiers(), me.getX()
239: - getCellRect(0, counter, true).x, me
240: .getY(), me.getClickCount(), me
241: .isPopupTrigger());
242: tree.dispatchEvent(newME);
243: break;
244: }
245: }
246: }
247: return false;
248: }
249: }
250:
251: /**
252: * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
253: * to listen for changes in the ListSelectionModel it maintains. Once
254: * a change in the ListSelectionModel happens, the paths are updated
255: * in the DefaultTreeSelectionModel.
256: */
257: class ListToTreeSelectionModelWrapper extends
258: DefaultTreeSelectionModel {
259: /** Set to true when we are updating the ListSelectionModel. */
260: protected boolean updatingListSelectionModel;
261:
262: public ListToTreeSelectionModelWrapper() {
263: super ();
264: getListSelectionModel().addListSelectionListener(
265: createListSelectionListener());
266: }
267:
268: /**
269: * Returns the list selection model. ListToTreeSelectionModelWrapper
270: * listens for changes to this model and updates the selected paths
271: * accordingly.
272: */
273: ListSelectionModel getListSelectionModel() {
274: return listSelectionModel;
275: }
276:
277: /**
278: * This is overridden to set <code>updatingListSelectionModel</code>
279: * and message super. This is the only place DefaultTreeSelectionModel
280: * alters the ListSelectionModel.
281: */
282: public void resetRowSelection() {
283: if (!updatingListSelectionModel) {
284: updatingListSelectionModel = true;
285: try {
286: super .resetRowSelection();
287: } finally {
288: updatingListSelectionModel = false;
289: }
290: }
291: // Notice how we don't message super if
292: // updatingListSelectionModel is true. If
293: // updatingListSelectionModel is true, it implies the
294: // ListSelectionModel has already been updated and the
295: // paths are the only thing that needs to be updated.
296: }
297:
298: /**
299: * Creates and returns an instance of ListSelectionHandler.
300: */
301: protected ListSelectionListener createListSelectionListener() {
302: return new ListSelectionHandler();
303: }
304:
305: /**
306: * If <code>updatingListSelectionModel</code> is false, this will
307: * reset the selected paths from the selected rows in the list
308: * selection model.
309: */
310: protected void updateSelectedPathsFromSelectedRows() {
311: if (!updatingListSelectionModel) {
312: updatingListSelectionModel = true;
313: try {
314: // This is way expensive, ListSelectionModel needs an
315: // enumerator for iterating.
316: int min = listSelectionModel.getMinSelectionIndex();
317: int max = listSelectionModel.getMaxSelectionIndex();
318:
319: clearSelection();
320: if (min != -1 && max != -1) {
321: for (int counter = min; counter <= max; counter++) {
322: if (listSelectionModel
323: .isSelectedIndex(counter)) {
324: TreePath selPath = tree
325: .getPathForRow(counter);
326:
327: if (selPath != null) {
328: addSelectionPath(selPath);
329: }
330: }
331: }
332: }
333: } finally {
334: updatingListSelectionModel = false;
335: }
336: }
337: }
338:
339: /**
340: * Class responsible for calling updateSelectedPathsFromSelectedRows
341: * when the selection of the list changse.
342: */
343: class ListSelectionHandler implements ListSelectionListener {
344: public void valueChanged(ListSelectionEvent e) {
345: updateSelectedPathsFromSelectedRows();
346: }
347: }
348: }
349: }
|