Source code: com/puppycrawl/tools/checkstyle/gui/JTreeTable.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2002 Oliver Burn
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19
20 /*
21 * @(#)JTreeTable.java 1.2 98/10/27
22 *
23 * Copyright 1997, 1998 by Sun Microsystems, Inc.,
24 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
25 * All rights reserved.
26 *
27 * This software is the confidential and proprietary information
28 * of Sun Microsystems, Inc. ("Confidential Information"). You
29 * shall not disclose such Confidential Information and shall use
30 * it only in accordance with the terms of the license agreement
31 * you entered into with Sun.
32 */
33
34 package com.puppycrawl.tools.checkstyle.gui;
35
36 import java.awt.Component;
37 import java.awt.Dimension;
38 import java.awt.Graphics;
39 import java.awt.event.ActionEvent;
40 import java.awt.event.MouseEvent;
41 import java.util.EventObject;
42 import javax.swing.Action;
43 import javax.swing.AbstractAction;
44 import javax.swing.JTable;
45 import javax.swing.JTree;
46 import javax.swing.KeyStroke;
47 import javax.swing.ListSelectionModel;
48 import javax.swing.LookAndFeel;
49 import javax.swing.UIManager;
50 import javax.swing.event.ListSelectionEvent;
51 import javax.swing.event.ListSelectionListener;
52 import javax.swing.table.TableCellEditor;
53 import javax.swing.table.TableCellRenderer;
54 import javax.swing.tree.DefaultTreeCellRenderer;
55 import javax.swing.tree.DefaultTreeSelectionModel;
56 import javax.swing.tree.TreeCellRenderer;
57 import javax.swing.tree.TreeModel;
58 import javax.swing.tree.TreePath;
59
60 /**
61 * This example shows how to create a simple JTreeTable component,
62 * by using a JTree as a renderer (and editor) for the cells in a
63 * particular column in the JTable.
64 *
65 * @version 1.2 10/27/98
66 *
67 * @author Philip Milne
68 * @author Scott Violet
69 * @author Lars Kühne
70 */
71 public class JTreeTable extends JTable
72 {
73 /** A subclass of JTree. */
74 protected TreeTableCellRenderer tree;
75
76 public JTreeTable(TreeTableModel treeTableModel)
77 {
78 super();
79
80 // Create the tree. It will be used as a renderer and editor.
81 tree = new TreeTableCellRenderer(treeTableModel);
82
83 // Install a tableModel representing the visible rows in the tree.
84 super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
85
86 // Force the JTable and JTree to share their row selection models.
87 ListToTreeSelectionModelWrapper selectionWrapper = new
88 ListToTreeSelectionModelWrapper();
89 tree.setSelectionModel(selectionWrapper);
90 setSelectionModel(selectionWrapper.getListSelectionModel());
91
92 // Install the tree editor renderer and editor.
93 setDefaultRenderer(TreeTableModel.class, tree);
94 setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
95
96 // No grid.
97 setShowGrid(false);
98
99 // No intercell spacing
100 setIntercellSpacing(new Dimension(0, 0));
101
102 // And update the height of the trees row to match that of
103 // the table.
104 if (tree.getRowHeight() < 1) {
105 // Metal looks better like this.
106 setRowHeight(getRowHeight());
107 }
108
109 Action expand = new AbstractAction() {
110 public void actionPerformed(ActionEvent e) {
111 TreePath selected = tree.getSelectionPath();
112 if (tree.isExpanded(selected)) {
113 tree.collapsePath(selected);
114 }
115 else {
116 tree.expandPath(selected);
117 }
118 tree.setSelectionPath(selected);
119 }
120 };
121 KeyStroke stroke = KeyStroke.getKeyStroke("ENTER");
122 String command = "expand/collapse";
123 getInputMap().put(stroke, command);
124 getActionMap().put(command, expand);
125 }
126
127 /**
128 * Overridden to message super and forward the method to the tree.
129 * Since the tree is not actually in the component hierarchy it will
130 * never receive this unless we forward it in this manner.
131 */
132 public void updateUI()
133 {
134 super.updateUI();
135 if (tree != null) {
136 tree.updateUI();
137 }
138 // Use the tree's default foreground and background colors in the
139 // table.
140 LookAndFeel.installColorsAndFont(this, "Tree.background",
141 "Tree.foreground", "Tree.font");
142 }
143
144 /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
145 * paint the editor. The UI currently uses different techniques to
146 * paint the renderers and editors and overriding setBounds() below
147 * is not the right thing to do for an editor. Returning -1 for the
148 * editing row in this case, ensures the editor is never painted.
149 */
150 public int getEditingRow()
151 {
152 final Class editingClass = getColumnClass(editingColumn);
153 return (editingClass == TreeTableModel.class) ? -1 : editingRow;
154 }
155
156 /**
157 * Overridden to pass the new rowHeight to the tree.
158 */
159 public void setRowHeight(int rowHeight)
160 {
161 super.setRowHeight(rowHeight);
162 if (tree != null && tree.getRowHeight() != rowHeight) {
163 tree.setRowHeight(getRowHeight());
164 }
165 }
166
167 /**
168 * @return the tree that is being shared between the model.
169 */
170 public JTree getTree()
171 {
172 return tree;
173 }
174
175 /**
176 * A TreeCellRenderer that displays a JTree.
177 */
178 class TreeTableCellRenderer extends JTree implements
179 TableCellRenderer
180 {
181 /** Last table/tree row asked to renderer. */
182 protected int visibleRow;
183
184 /** creates a new instance */
185 public TreeTableCellRenderer(TreeModel model)
186 {
187 super(model);
188 }
189
190 /**
191 * updateUI is overridden to set the colors of the Tree's renderer
192 * to match that of the table.
193 */
194 public void updateUI()
195 {
196 super.updateUI();
197 // Make the tree's cell renderer use the table's cell selection
198 // colors.
199 TreeCellRenderer tcr = getCellRenderer();
200 if (tcr instanceof DefaultTreeCellRenderer) {
201 DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
202 // For 1.1 uncomment this, 1.2 has a bug that will cause an
203 // exception to be thrown if the border selection color is
204 // null.
205 // dtcr.setBorderSelectionColor(null);
206 dtcr.setTextSelectionColor(UIManager.getColor
207 ("Table.selectionForeground"));
208 dtcr.setBackgroundSelectionColor(UIManager.getColor
209 ("Table.selectionBackground"));
210 }
211 }
212
213 /**
214 * Sets the row height of the tree, and forwards the row height to
215 * the table.
216 */
217 public void setRowHeight(int rowHeight)
218 {
219 if (rowHeight > 0) {
220 super.setRowHeight(rowHeight);
221 if (JTreeTable.this != null &&
222 JTreeTable.this.getRowHeight() != rowHeight) {
223 JTreeTable.this.setRowHeight(getRowHeight());
224 }
225 }
226 }
227
228 /**
229 * This is overridden to set the height to match that of the JTable.
230 */
231 public void setBounds(int x, int y, int w, int h)
232 {
233 super.setBounds(x, 0, w, JTreeTable.this.getHeight());
234 }
235
236 /**
237 * Sublcassed to translate the graphics such that the last visible
238 * row will be drawn at 0,0.
239 */
240 public void paint(Graphics g)
241 {
242 g.translate(0, -visibleRow * getRowHeight());
243 super.paint(g);
244 }
245
246 /**
247 * TreeCellRenderer method. Overridden to update the visible row.
248 * @see TableCellRenderer
249 */
250 public Component getTableCellRendererComponent(JTable table,
251 Object value,
252 boolean isSelected,
253 boolean hasFocus,
254 int row, int column)
255 {
256 if (isSelected) {
257 setBackground(table.getSelectionBackground());
258 } else {
259 setBackground(table.getBackground());
260 }
261
262 visibleRow = row;
263 return this;
264 }
265 }
266
267
268 /**
269 * TreeTableCellEditor implementation. Component returned is the
270 * JTree.
271 */
272 public class TreeTableCellEditor extends AbstractCellEditor implements
273 TableCellEditor
274 {
275 public Component getTableCellEditorComponent(JTable table,
276 Object value,
277 boolean isSelected,
278 int r, int c)
279 {
280 return tree;
281 }
282
283 /**
284 * Overridden to return false, and if the event is a mouse event
285 * it is forwarded to the tree.<p>
286 * The behavior for this is debatable, and should really be offered
287 * as a property. By returning false, all keyboard actions are
288 * implemented in terms of the table. By returning true, the
289 * tree would get a chance to do something with the keyboard
290 * events. For the most part this is ok. But for certain keys,
291 * such as left/right, the tree will expand/collapse where as
292 * the table focus should really move to a different column. Page
293 * up/down should also be implemented in terms of the table.
294 * By returning false this also has the added benefit that clicking
295 * outside of the bounds of the tree node, but still in the tree
296 * column will select the row, whereas if this returned true
297 * that wouldn't be the case.
298 * <p>By returning false we are also enforcing the policy that
299 * the tree will never be editable (at least by a key sequence).
300 *
301 * @see TableCellEditor
302 */
303 public boolean isCellEditable(EventObject e)
304 {
305 if (e instanceof MouseEvent) {
306 for (int counter = getColumnCount() - 1; counter >= 0;
307 counter--) {
308 if (getColumnClass(counter) == TreeTableModel.class) {
309 MouseEvent me = (MouseEvent) e;
310 MouseEvent newME = new MouseEvent(tree, me.getID(),
311 me.getWhen(), me.getModifiers(),
312 me.getX() - getCellRect(0, counter, true).x,
313 me.getY(), me.getClickCount(),
314 me.isPopupTrigger());
315 tree.dispatchEvent(newME);
316 break;
317 }
318 }
319 }
320
321 return false;
322 }
323 }
324
325
326 /**
327 * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
328 * to listen for changes in the ListSelectionModel it maintains. Once
329 * a change in the ListSelectionModel happens, the paths are updated
330 * in the DefaultTreeSelectionModel.
331 */
332 class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
333 {
334 /** Set to true when we are updating the ListSelectionModel. */
335 protected boolean updatingListSelectionModel;
336
337 public ListToTreeSelectionModelWrapper()
338 {
339 super();
340 getListSelectionModel().addListSelectionListener
341 (createListSelectionListener());
342 }
343
344 /**
345 * Returns the list selection model. ListToTreeSelectionModelWrapper
346 * listens for changes to this model and updates the selected paths
347 * accordingly.
348 *
349 * @return the list selection model
350 */
351 ListSelectionModel getListSelectionModel()
352 {
353 return listSelectionModel;
354 }
355
356 /**
357 * This is overridden to set <code>updatingListSelectionModel</code>
358 * and message super. This is the only place DefaultTreeSelectionModel
359 * alters the ListSelectionModel.
360 */
361 public void resetRowSelection()
362 {
363 if (!updatingListSelectionModel) {
364 updatingListSelectionModel = true;
365 try {
366 super.resetRowSelection();
367 } finally {
368 updatingListSelectionModel = false;
369 }
370 }
371 // Notice how we don't message super if
372 // updatingListSelectionModel is true. If
373 // updatingListSelectionModel is true, it implies the
374 // ListSelectionModel has already been updated and the
375 // paths are the only thing that needs to be updated.
376 }
377
378 /**
379 * Creates and returns an instance of ListSelectionHandler.
380 */
381 private ListSelectionListener createListSelectionListener()
382 {
383 return new ListSelectionHandler();
384 }
385
386 /**
387 * If <code>updatingListSelectionModel</code> is false, this will
388 * reset the selected paths from the selected rows in the list
389 * selection model.
390 */
391 protected void updateSelectedPathsFromSelectedRows()
392 {
393 if (!updatingListSelectionModel) {
394 updatingListSelectionModel = true;
395 try {
396 // This is way expensive, ListSelectionModel needs an
397 // enumerator for iterating.
398 int min = listSelectionModel.getMinSelectionIndex();
399 int max = listSelectionModel.getMaxSelectionIndex();
400
401 clearSelection();
402 if (min != -1 && max != -1) {
403 for (int counter = min; counter <= max; counter++) {
404 if (listSelectionModel.isSelectedIndex(counter)) {
405 TreePath selPath = tree.getPathForRow
406 (counter);
407
408 if (selPath != null) {
409 addSelectionPath(selPath);
410 }
411 }
412 }
413 }
414 } finally {
415 updatingListSelectionModel = false;
416 }
417 }
418 }
419
420 /**
421 * Class responsible for calling updateSelectedPathsFromSelectedRows
422 * when the selection of the list changse.
423 */
424 class ListSelectionHandler implements ListSelectionListener
425 {
426 public void valueChanged(ListSelectionEvent e)
427 {
428 updateSelectedPathsFromSelectedRows();
429 }
430 }
431 }
432 }