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