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 * %W% %E%
22 *
23 * Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
24 *
25 * Redistribution and use in source and binary forms, with or
26 * without modification, are permitted provided that the following
27 * conditions are met:
28 *
29 * - Redistributions of source code must retain the above copyright
30 * notice, this list of conditions and the following disclaimer.
31 *
32 * - Redistribution in binary form must reproduce the above
33 * copyright notice, this list of conditions and the following
34 * disclaimer in the documentation and/or other materials
35 * provided with the distribution.
36 *
37 * Neither the name of Sun Microsystems, Inc. or the names of
38 * contributors may be used to endorse or promote products derived
39 * from this software without specific prior written permission.
40 *
41 * This software is provided "AS IS," without a warranty of any
42 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
43 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
44 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
45 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY
46 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
47 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR
48 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE
49 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT,
50 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
51 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF
52 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS
53 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
54 *
55 * You acknowledge that this software is not designed, licensed or
56 * intended for use in the design, construction, operation or
57 * maintenance of any nuclear facility.
58 */
59
60 package com.puppycrawl.tools.checkstyle.gui;
61
62 import java.awt.Component;
63 import java.awt.Dimension;
64 import java.awt.Graphics;
65 import java.awt.event.ActionEvent;
66 import java.awt.event.MouseEvent;
67 import java.util.EventObject;
68 import java.util.List;
69
70 import javax.swing.Action;
71 import javax.swing.AbstractAction;
72 import javax.swing.JTable;
73 import javax.swing.JTextArea;
74 import javax.swing.JTree;
75 import javax.swing.KeyStroke;
76 import javax.swing.ListSelectionModel;
77 import javax.swing.LookAndFeel;
78 import javax.swing.UIManager;
79 import javax.swing.event.ListSelectionEvent;
80 import javax.swing.event.ListSelectionListener;
81 import javax.swing.table.TableCellEditor;
82 import javax.swing.table.TableCellRenderer;
83 import javax.swing.tree.DefaultTreeCellRenderer;
84 import javax.swing.tree.DefaultTreeSelectionModel;
85 import javax.swing.tree.TreeCellRenderer;
86 import javax.swing.tree.TreeModel;
87 import javax.swing.tree.TreePath;
88
89 import com.puppycrawl.tools.checkstyle.api.DetailAST;
90
91 /**
92 * This example shows how to create a simple JTreeTable component,
93 * by using a JTree as a renderer (and editor) for the cells in a
94 * particular column in the JTable.
95 *
96 * <a href="http://java.sun.com/products/jfc/tsc/articles/treetable1/index.html">Original Source Location</a>
97 *
98 * @author Philip Milne
99 * @author Scott Violet
100 * @author Lars Kühne
101 */
102 public class JTreeTable extends JTable
103 {
104 /** For Serialisation that will never happen. */
105 private static final long serialVersionUID = -8493693409423365387L;
106 /** A subclass of JTree. */
107 protected TreeTableCellRenderer tree;
108 private JTextArea editor;
109 private List<Integer> lines2position;
110
111 public JTreeTable(TreeTableModel treeTableModel)
112 {
113 super();
114
115 // Create the tree. It will be used as a renderer and editor.
116 tree = new TreeTableCellRenderer(treeTableModel);
117
118 // Install a tableModel representing the visible rows in the tree.
119 super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
120
121 // Force the JTable and JTree to share their row selection models.
122 final ListToTreeSelectionModelWrapper selectionWrapper = new
123 ListToTreeSelectionModelWrapper();
124 tree.setSelectionModel(selectionWrapper);
125 setSelectionModel(selectionWrapper.getListSelectionModel());
126
127 // Install the tree editor renderer and editor.
128 setDefaultRenderer(TreeTableModel.class, tree);
129 setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
130
131 // No grid.
132 setShowGrid(false);
133
134 // No intercell spacing
135 setIntercellSpacing(new Dimension(0, 0));
136
137 // And update the height of the trees row to match that of
138 // the table.
139 if (tree.getRowHeight() < 1) {
140 // Metal looks better like this.
141 setRowHeight(getRowHeight());
142 }
143
144 final Action expand = new AbstractAction() {
145 /**
146 *
147 */
148 private static final long serialVersionUID = -5859674518660156121L;
149
150 @Override
151 public void actionPerformed(ActionEvent e) {
152 final TreePath selected = tree.getSelectionPath();
153
154 DetailAST ast = (DetailAST) selected.getLastPathComponent();
155 new CodeSelector(ast, editor, lines2position).select();
156
157 if (tree.isExpanded(selected)) {
158 tree.collapsePath(selected);
159 }
160 else {
161 tree.expandPath(selected);
162 }
163 tree.setSelectionPath(selected);
164 }
165 };
166 final KeyStroke stroke = KeyStroke.getKeyStroke("ENTER");
167 final String command = "expand/collapse";
168 getInputMap().put(stroke, command);
169 getActionMap().put(command, expand);
170 }
171
172 /**
173 * Overridden to message super and forward the method to the tree.
174 * Since the tree is not actually in the component hierarchy it will
175 * never receive this unless we forward it in this manner.
176 */
177 @Override
178 public void updateUI()
179 {
180 super.updateUI();
181 if (tree != null) {
182 tree.updateUI();
183 }
184 // Use the tree's default foreground and background colors in the
185 // table.
186 LookAndFeel.installColorsAndFont(this, "Tree.background",
187 "Tree.foreground", "Tree.font");
188 }
189
190 /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
191 * paint the editor. The UI currently uses different techniques to
192 * paint the renderers and editors and overriding setBounds() below
193 * is not the right thing to do for an editor. Returning -1 for the
194 * editing row in this case, ensures the editor is never painted.
195 */
196 @Override
197 public int getEditingRow()
198 {
199 final Class<?> editingClass = getColumnClass(editingColumn);
200 return editingClass == TreeTableModel.class ? -1 : editingRow;
201 }
202
203 /**
204 * Overridden to pass the new rowHeight to the tree.
205 */
206 @Override
207 public void setRowHeight(int newRowHeight)
208 {
209 super.setRowHeight(newRowHeight);
210 if (tree != null && tree.getRowHeight() != newRowHeight) {
211 tree.setRowHeight(getRowHeight());
212 }
213 }
214
215 /**
216 * @return the tree that is being shared between the model.
217 */
218 public JTree getTree()
219 {
220 return tree;
221 }
222
223 /**
224 * A TreeCellRenderer that displays a JTree.
225 */
226 class TreeTableCellRenderer extends JTree implements
227 TableCellRenderer
228 {
229 /**
230 *
231 */
232 private static final long serialVersionUID = 4324031590789321581L;
233 /** Last table/tree row asked to renderer. */
234 protected int visibleRow;
235
236 /** creates a new instance */
237 public TreeTableCellRenderer(TreeModel model)
238 {
239 super(model);
240 }
241
242 /**
243 * updateUI is overridden to set the colors of the Tree's renderer
244 * to match that of the table.
245 */
246 @Override
247 public void updateUI()
248 {
249 super.updateUI();
250 // Make the tree's cell renderer use the table's cell selection
251 // colors.
252 final TreeCellRenderer tcr = getCellRenderer();
253 if (tcr instanceof DefaultTreeCellRenderer) {
254 final DefaultTreeCellRenderer dtcr = (DefaultTreeCellRenderer) tcr;
255 // For 1.1 uncomment this, 1.2 has a bug that will cause an
256 // exception to be thrown if the border selection color is
257 // null.
258 // dtcr.setBorderSelectionColor(null);
259 dtcr.setTextSelectionColor(UIManager.getColor
260 ("Table.selectionForeground"));
261 dtcr.setBackgroundSelectionColor(UIManager.getColor
262 ("Table.selectionBackground"));
263 }
264 }
265
266 /**
267 * Sets the row height of the tree, and forwards the row height to
268 * the table.
269 */
270 @Override
271 public void setRowHeight(int newRowHeight)
272 {
273 if (newRowHeight > 0) {
274 super.setRowHeight(newRowHeight);
275 if (JTreeTable.this != null &&
276 JTreeTable.this.getRowHeight() != newRowHeight)
277 {
278 JTreeTable.this.setRowHeight(getRowHeight());
279 }
280 }
281 }
282
283 /**
284 * This is overridden to set the height to match that of the JTable.
285 */
286 @Override
287 public void setBounds(int x, int y, int w, int h)
288 {
289 super.setBounds(x, 0, w, JTreeTable.this.getHeight());
290 }
291
292 /**
293 * Sublcassed to translate the graphics such that the last visible
294 * row will be drawn at 0,0.
295 */
296 @Override
297 public void paint(Graphics g)
298 {
299 g.translate(0, -visibleRow * getRowHeight());
300 super.paint(g);
301 }
302
303 /**
304 * TreeCellRenderer method. Overridden to update the visible row.
305 * @see TableCellRenderer
306 */
307 @Override
308 public Component getTableCellRendererComponent(JTable table,
309 Object value,
310 boolean isSelected,
311 boolean hasFocus,
312 int row, int column)
313 {
314 if (isSelected) {
315 setBackground(table.getSelectionBackground());
316 } else {
317 setBackground(table.getBackground());
318 }
319
320 visibleRow = row;
321 return this;
322 }
323 }
324
325
326 /**
327 * TreeTableCellEditor implementation. Component returned is the
328 * JTree.
329 */
330 public class TreeTableCellEditor extends AbstractCellEditor implements
331 TableCellEditor
332 {
333 @Override
334 public Component getTableCellEditorComponent(JTable table,
335 Object value,
336 boolean isSelected,
337 int r, int c)
338 {
339 return tree;
340 }
341
342 /**
343 * Overridden to return false, and if the event is a mouse event
344 * it is forwarded to the tree.<p>
345 * The behavior for this is debatable, and should really be offered
346 * as a property. By returning false, all keyboard actions are
347 * implemented in terms of the table. By returning true, the
348 * tree would get a chance to do something with the keyboard
349 * events. For the most part this is ok. But for certain keys,
350 * such as left/right, the tree will expand/collapse where as
351 * the table focus should really move to a different column. Page
352 * up/down should also be implemented in terms of the table.
353 * By returning false this also has the added benefit that clicking
354 * outside of the bounds of the tree node, but still in the tree
355 * column will select the row, whereas if this returned true
356 * that wouldn't be the case.
357 * <p>By returning false we are also enforcing the policy that
358 * the tree will never be editable (at least by a key sequence).
359 *
360 * @see TableCellEditor
361 */
362 @Override
363 public boolean isCellEditable(EventObject e)
364 {
365 if (e instanceof MouseEvent) {
366 for (int counter = getColumnCount() - 1; counter >= 0;
367 counter--) {
368 if (getColumnClass(counter) == TreeTableModel.class) {
369 final MouseEvent me = (MouseEvent) e;
370 final MouseEvent newME = new MouseEvent(tree, me.getID(),
371 me.getWhen(), me.getModifiers(),
372 me.getX() - getCellRect(0, counter, true).x,
373 me.getY(), me.getClickCount(),
374 me.isPopupTrigger());
375 tree.dispatchEvent(newME);
376 break;
377 }
378 }
379 }
380
381 return false;
382 }
383 }
384
385
386 /**
387 * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
388 * to listen for changes in the ListSelectionModel it maintains. Once
389 * a change in the ListSelectionModel happens, the paths are updated
390 * in the DefaultTreeSelectionModel.
391 */
392 class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
393 {
394 /**
395 *
396 */
397 private static final long serialVersionUID = 2267930983939339510L;
398 /** Set to true when we are updating the ListSelectionModel. */
399 protected boolean updatingListSelectionModel;
400
401 public ListToTreeSelectionModelWrapper()
402 {
403 super();
404 getListSelectionModel().addListSelectionListener
405 (createListSelectionListener());
406 }
407
408 /**
409 * Returns the list selection model. ListToTreeSelectionModelWrapper
410 * listens for changes to this model and updates the selected paths
411 * accordingly.
412 *
413 * @return the list selection model
414 */
415 ListSelectionModel getListSelectionModel()
416 {
417 return listSelectionModel;
418 }
419
420 /**
421 * This is overridden to set <code>updatingListSelectionModel</code>
422 * and message super. This is the only place DefaultTreeSelectionModel
423 * alters the ListSelectionModel.
424 */
425 @Override
426 public void resetRowSelection()
427 {
428 if (!updatingListSelectionModel) {
429 updatingListSelectionModel = true;
430 try {
431 super.resetRowSelection();
432 } finally {
433 updatingListSelectionModel = false;
434 }
435 }
436 // Notice how we don't message super if
437 // updatingListSelectionModel is true. If
438 // updatingListSelectionModel is true, it implies the
439 // ListSelectionModel has already been updated and the
440 // paths are the only thing that needs to be updated.
441 }
442
443 /**
444 * Creates and returns an instance of ListSelectionHandler.
445 */
446 private ListSelectionListener createListSelectionListener()
447 {
448 return new ListSelectionHandler();
449 }
450
451 /**
452 * If <code>updatingListSelectionModel</code> is false, this will
453 * reset the selected paths from the selected rows in the list
454 * selection model.
455 */
456 protected void updateSelectedPathsFromSelectedRows()
457 {
458 if (!updatingListSelectionModel) {
459 updatingListSelectionModel = true;
460 try {
461 // This is way expensive, ListSelectionModel needs an
462 // enumerator for iterating.
463 final int min = listSelectionModel.getMinSelectionIndex();
464 final int max = listSelectionModel.getMaxSelectionIndex();
465
466 clearSelection();
467 if (min != -1 && max != -1) {
468 for (int counter = min; counter <= max; counter++) {
469 if (listSelectionModel.isSelectedIndex(counter)) {
470 final TreePath selPath = tree.getPathForRow
471 (counter);
472
473 if (selPath != null) {
474 addSelectionPath(selPath);
475 }
476 }
477 }
478 }
479 } finally {
480 updatingListSelectionModel = false;
481 }
482 }
483 }
484
485 /**
486 * Class responsible for calling updateSelectedPathsFromSelectedRows
487 * when the selection of the list changse.
488 */
489 class ListSelectionHandler implements ListSelectionListener
490 {
491 @Override
492 public void valueChanged(ListSelectionEvent e)
493 {
494 updateSelectedPathsFromSelectedRows();
495 }
496 }
497 }
498
499 public void setEditor(JTextArea mJTextArea)
500 {
501 this.editor = mJTextArea;
502 }
503
504 public void setLinePositionMap(List<Integer> lines2position)
505 {
506 this.lines2position = lines2position;
507 }
508 }