001: /*
002: * The contents of this file are subject to the Mozilla Public License
003: * Version 1.1 (the "License"); you may not use this file except in
004: * compliance with the License. You may obtain a copy of the License at
005: * http://www.mozilla.org/MPL/
006: *
007: * Software distributed under the License is distributed on an "AS IS"
008: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
009: * License for the specific language governing rights and limitations
010: * under the License.
011: *
012: * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
013: *
014: * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
015: * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
016: *
017: * Contributor(s):
018: * Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
019: *
020: * If you didn't download this code from the following link, you should check
021: * if you aren't using an obsolete version: http://www.isqlviewer.com
022: */
023: package org.isqlviewer.swing.outline;
024:
025: import java.awt.Component;
026: import java.awt.Dimension;
027: import java.awt.Graphics;
028: import java.awt.event.MouseEvent;
029: import java.util.Date;
030: import java.util.EventObject;
031: import java.util.Hashtable;
032:
033: import javax.swing.JTable;
034: import javax.swing.JTree;
035: import javax.swing.ListSelectionModel;
036: import javax.swing.LookAndFeel;
037: import javax.swing.UIDefaults;
038: import javax.swing.UIManager;
039: import javax.swing.UIDefaults.ProxyLazyValue;
040: import javax.swing.event.ListSelectionEvent;
041: import javax.swing.event.ListSelectionListener;
042: import javax.swing.table.TableCellEditor;
043: import javax.swing.table.TableCellRenderer;
044: import javax.swing.tree.DefaultTreeCellRenderer;
045: import javax.swing.tree.DefaultTreeSelectionModel;
046: import javax.swing.tree.TreeCellRenderer;
047: import javax.swing.tree.TreeModel;
048: import javax.swing.tree.TreePath;
049:
050: import org.isqlviewer.ui.laf.EnhancedTreeUI;
051:
052: /**
053: * This example shows how to create a simple JTreeTable component, by using a JTree as a renderer (and editor) for the
054: * cells in a particular column in the JTable.
055: *
056: * @version 1.2 10/27/98
057: * @author Philip Milne
058: * @author Scott Violet
059: */
060: public class JOutline extends JTable {
061:
062: private static final long serialVersionUID = -6011537580748076035L;
063: /** A subclass of JTree. */
064: protected TreeTableCellRenderer tree;
065:
066: public JOutline(OutlineModel treeTableModel) {
067:
068: super ();
069:
070: // Create the tree. It will be used as a renderer and editor.
071: tree = new TreeTableCellRenderer(treeTableModel);
072:
073: // Install a tableModel representing the visible rows in the tree.
074: super .setModel(new TreeTableModelAdapter(treeTableModel, tree));
075:
076: // Force the JTable and JTree to share their row selection models.
077: ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper();
078: tree.setSelectionModel(selectionWrapper);
079: setSelectionModel(selectionWrapper.getListSelectionModel());
080:
081: // Install the tree editor renderer and editor.
082: setDefaultRenderer(OutlineModel.class, tree);
083: setDefaultEditor(OutlineModel.class, new TreeTableCellEditor());
084:
085: // No grid.
086: setShowGrid(false);
087:
088: // No intercell spacing
089: setIntercellSpacing(new Dimension(0, 0));
090:
091: // And update the height of the trees row to match that of
092: // the table.
093: if (tree.getRowHeight() < 1) {
094: // Metal looks better like this.
095: setRowHeight(18);
096: }
097:
098: }
099:
100: /**
101: * Overridden to message super and forward the method to the tree. Since the tree is not actually in the component
102: * hieachy it will never receive this unless we forward it in this manner.
103: */
104: @Override
105: public void updateUI() {
106:
107: super .updateUI();
108: if (tree != null) {
109: tree.updateUI();
110: }
111: // Use the tree's default foreground and background colors in the
112: // table.
113: LookAndFeel.installColorsAndFont(this , "Tree.background",
114: "Tree.foreground", "Tree.font");
115: }
116:
117: /*
118: * Workaround for BasicTableUI anomaly. Make sure the UI never tries to paint the editor. The UI currently uses
119: * different techniques to paint the renderers and editors and overriding setBounds() below is not the right thing
120: * to do for an editor. Returning -1 for the editing row in this case, ensures the editor is never painted.
121: */
122: @Override
123: public int getEditingRow() {
124:
125: return (getColumnClass(editingColumn) == OutlineModel.class) ? -1
126: : editingRow;
127: }
128:
129: /**
130: * Overridden to pass the new rowHeight to the tree.
131: */
132: @Override
133: public void setRowHeight(int rowHeight) {
134:
135: super .setRowHeight(rowHeight);
136: if (tree != null && tree.getRowHeight() != rowHeight) {
137: tree.setRowHeight(getRowHeight());
138: }
139: }
140:
141: /**
142: * Returns the tree that is being shared between the model.
143: */
144: public JTree getTree() {
145:
146: return tree;
147: }
148:
149: @Override
150: protected void createDefaultRenderers() {
151:
152: defaultRenderersByColumnClass = new UIDefaults();
153:
154: // Numbers
155: setLazyRenderer(Number.class,
156: "org.isqlviewer.ui.laf.EnhancedNumberCellRenderer");
157: // Doubles and Floats
158: setLazyRenderer(Float.class,
159: "org.isqlviewer.ui.laf.EnhancedDecimalCellRenderer");
160: setLazyRenderer(Double.class,
161: "org.isqlviewer.ui.laf.EnhancedDecimalCellRendererr");
162: // Dates
163: setLazyRenderer(Date.class,
164: "org.isqlviewer.ui.laf.EnhancedDateCellRenderer");
165: // Booleans
166: setLazyRenderer(Boolean.class,
167: "org.isqlviewer.ui.laf.EnhancedBooleanCellRenderer");
168: // Everything else
169: setLazyRenderer(Object.class,
170: "org.isqlviewer.ui.laf.EnhancedTableCellRenderer");
171: }
172:
173: private void setLazyValue(Hashtable<Class, ProxyLazyValue> h,
174: Class c, String s) {
175:
176: h.put(c, new UIDefaults.ProxyLazyValue(s));
177: }
178:
179: private void setLazyRenderer(Class c, String s) {
180:
181: setLazyValue(defaultRenderersByColumnClass, c, s);
182: }
183:
184: /**
185: * A TreeCellRenderer that displays a JTree.
186: */
187: public class TreeTableCellRenderer extends JTree implements
188: TableCellRenderer {
189:
190: private static final long serialVersionUID = -1452280482504589192L;
191: /** Last table/tree row asked to renderer. */
192: protected int visibleRow;
193:
194: public TreeTableCellRenderer(TreeModel model) {
195:
196: super (model);
197: setUI(new EnhancedTreeUI(JOutline.this ));
198: }
199:
200: /**
201: * updateUI is overridden to set the colors of the Tree's renderer to match that of the table.
202: */
203: @Override
204: public void updateUI() {
205:
206: super .updateUI();
207: // Make the tree's cell renderer use the table's cell selection
208: // colors.
209: TreeCellRenderer tcr = getCellRenderer();
210: if (tcr instanceof DefaultTreeCellRenderer) {
211: DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
212: // For 1.1 uncomment this, 1.2 has a bug that will cause an
213: // exception to be thrown if the border selection color is
214: // null.
215: // dtcr.setBorderSelectionColor(null);
216: dtcr.setTextSelectionColor(UIManager
217: .getColor("Table.selectionForeground"));
218: dtcr.setBackgroundSelectionColor(UIManager
219: .getColor("Table.selectionBackground"));
220: }
221: }
222:
223: /**
224: * Sets the row height of the tree, and forwards the row height to the table.
225: */
226: @Override
227: public void setRowHeight(int rowHeight) {
228:
229: if (rowHeight > 0) {
230: super .setRowHeight(rowHeight);
231: if (JOutline.this != null
232: && JOutline.this .getRowHeight() != rowHeight) {
233: JOutline.this .setRowHeight(getRowHeight());
234: }
235: }
236: }
237:
238: /**
239: * This is overridden to set the height to match that of the JTable.
240: */
241: @Override
242: public void setBounds(int x, int y, int w, int h) {
243:
244: super .setBounds(x, 0, w, JOutline.this .getHeight());
245: }
246:
247: /**
248: * Sublcassed to translate the graphics such that the last visible row will be drawn at 0,0.
249: */
250: @Override
251: public void paint(Graphics g) {
252:
253: g.translate(0, -visibleRow * getRowHeight());
254: super .paint(g);
255: }
256:
257: /**
258: * TreeCellRenderer method. Overridden to update the visible row.
259: */
260: public Component getTableCellRendererComponent(JTable table,
261: Object value, boolean isSelected, boolean hasFocus,
262: int row, int column) {
263:
264: // if (isSelected)
265: // setBackground(table.getSelectionBackground());
266: // else
267: // setBackground(table.getBackground());
268:
269: visibleRow = row;
270: return this ;
271: }
272: }
273:
274: /**
275: * TreeTableCellEditor implementation. Component returned is the JTree.
276: */
277: public class TreeTableCellEditor extends AbstractCellEditor
278: implements TableCellEditor {
279:
280: public Component getTableCellEditorComponent(JTable table,
281: Object value, boolean isSelected, int r, int c) {
282:
283: return tree;
284: }
285:
286: /**
287: * Overridden to return false, and if the event is a mouse event it is forwarded to the tree.
288: * <p>
289: * The behavior for this is debatable, and should really be offered as a property. By returning false, all
290: * keyboard actions are implemented in terms of the table. By returning true, the tree would get a chance to do
291: * something with the keyboard events. For the most part this is ok. But for certain keys, such as left/right,
292: * the tree will expand/collapse where as the table focus should really move to a different column. Page up/down
293: * should also be implemented in terms of the table. By returning false this also has the added benefit that
294: * clicking outside of the bounds of the tree node, but still in the tree column will select the row, whereas if
295: * this returned true that wouldn't be the case.
296: * <p>
297: * By returning false we are also enforcing the policy that the tree will never be editable (at least by a key
298: * sequence).
299: */
300: @Override
301: public boolean isCellEditable(EventObject e) {
302:
303: if (e instanceof MouseEvent) {
304: for (int counter = getColumnCount() - 1; counter >= 0; counter--) {
305: if (getColumnClass(counter) == OutlineModel.class) {
306: MouseEvent me = (MouseEvent) e;
307: MouseEvent newME = new MouseEvent(tree, me
308: .getID(), me.getWhen(), me
309: .getModifiers(), me.getX()
310: - getCellRect(0, counter, true).x, me
311: .getY(), me.getClickCount(), me
312: .isPopupTrigger());
313: tree.dispatchEvent(newME);
314: break;
315: }
316: }
317: }
318: return false;
319: }
320: }
321:
322: /**
323: * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel to listen for changes in the ListSelectionModel
324: * it maintains. Once a change in the ListSelectionModel happens, the paths are updated in the
325: * DefaultTreeSelectionModel.
326: */
327: class ListToTreeSelectionModelWrapper extends
328: DefaultTreeSelectionModel {
329:
330: private static final long serialVersionUID = -2542037807820930938L;
331: /** Set to true when we are updating the ListSelectionModel. */
332: protected boolean updatingListSelectionModel;
333:
334: public ListToTreeSelectionModelWrapper() {
335:
336: super ();
337: getListSelectionModel().addListSelectionListener(
338: createListSelectionListener());
339: }
340:
341: /**
342: * Returns the list selection model. ListToTreeSelectionModelWrapper listens for changes to this model and
343: * updates the selected paths accordingly.
344: */
345: ListSelectionModel getListSelectionModel() {
346:
347: return listSelectionModel;
348: }
349:
350: /**
351: * This is overridden to set <code>updatingListSelectionModel</code> and message super. This is the only place
352: * DefaultTreeSelectionModel alters the ListSelectionModel.
353: */
354: @Override
355: public void resetRowSelection() {
356:
357: if (!updatingListSelectionModel) {
358: updatingListSelectionModel = true;
359: try {
360: super .resetRowSelection();
361: } finally {
362: updatingListSelectionModel = false;
363: }
364: }
365: // Notice how we don't message super if
366: // updatingListSelectionModel is true. If
367: // updatingListSelectionModel is true, it implies the
368: // ListSelectionModel has already been updated and the
369: // paths are the only thing that needs to be updated.
370: }
371:
372: /**
373: * Creates and returns an instance of ListSelectionHandler.
374: */
375: protected ListSelectionListener createListSelectionListener() {
376:
377: return new ListSelectionHandler();
378: }
379:
380: /**
381: * If <code>updatingListSelectionModel</code> is false, this will reset the selected paths from the selected
382: * rows in the list selection model.
383: */
384: protected void updateSelectedPathsFromSelectedRows() {
385:
386: if (!updatingListSelectionModel) {
387: updatingListSelectionModel = true;
388: try {
389: // This is way expensive, ListSelectionModel needs an
390: // enumerator for iterating.
391: int min = listSelectionModel.getMinSelectionIndex();
392: int max = listSelectionModel.getMaxSelectionIndex();
393:
394: clearSelection();
395: if (min != -1 && max != -1) {
396: for (int counter = min; counter <= max; counter++) {
397: if (listSelectionModel
398: .isSelectedIndex(counter)) {
399: TreePath selPath = tree
400: .getPathForRow(counter);
401:
402: if (selPath != null) {
403: addSelectionPath(selPath);
404: }
405: }
406: }
407: }
408: } finally {
409: updatingListSelectionModel = false;
410: }
411: }
412: }
413:
414: /**
415: * Class responsible for calling updateSelectedPathsFromSelectedRows when the selection of the list changse.
416: */
417: class ListSelectionHandler implements ListSelectionListener {
418:
419: public void valueChanged(ListSelectionEvent e) {
420:
421: updateSelectedPathsFromSelectedRows();
422: }
423: }
424: }
425:
426: /**
427: * @param b
428: */
429: public void setRootVisible(boolean b) {
430:
431: tree.setRootVisible(b);
432: }
433:
434: public void cancelEditing() {
435:
436: tree.cancelEditing();
437: }
438:
439: public TreePath getPathForLocation(int x, int y) {
440:
441: return tree.getPathForLocation(x, y);
442: }
443:
444: public int getRowForLocation(int x, int y) {
445:
446: return tree.getRowForLocation(x, y);
447: }
448:
449: public void setTreeCellRenderer(TreeCellRenderer renderer) {
450:
451: tree.setCellRenderer(renderer);
452: }
453:
454: public TreeCellRenderer getTreeCellRenderer() {
455:
456: return tree.getCellRenderer();
457: }
458:
459: public Component getTreeCellRendererComponent(Object value,
460: boolean selected, boolean expanded, boolean leaf, int row,
461: boolean hasFocus) {
462:
463: TreeCellRenderer tcr = getTreeCellRenderer();
464: return tcr.getTreeCellRendererComponent(tree, value, selected,
465: expanded, leaf, row, hasFocus);
466: }
467: }
|