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