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 * %W% %E% 022 * 023 * Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved. 024 * 025 * Redistribution and use in source and binary forms, with or 026 * without modification, are permitted provided that the following 027 * conditions are met: 028 * 029 * - Redistributions of source code must retain the above copyright 030 * notice, this list of conditions and the following disclaimer. 031 * 032 * - Redistribution in binary form must reproduce the above 033 * copyright notice, this list of conditions and the following 034 * disclaimer in the documentation and/or other materials 035 * provided with the distribution. 036 * 037 * Neither the name of Sun Microsystems, Inc. or the names of 038 * contributors may be used to endorse or promote products derived 039 * from this software without specific prior written permission. 040 * 041 * This software is provided "AS IS," without a warranty of any 042 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 043 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 044 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 045 * EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY 046 * DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR 047 * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE OR 048 * ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE 049 * FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, 050 * SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER 051 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF 052 * THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS 053 * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 054 * 055 * You acknowledge that this software is not designed, licensed or 056 * intended for use in the design, construction, operation or 057 * maintenance of any nuclear facility. 058 */ 059 060package com.puppycrawl.tools.checkstyle.gui; 061 062import java.awt.Component; 063import java.awt.Dimension; 064import java.awt.Graphics; 065import java.awt.event.ActionEvent; 066import java.awt.event.MouseEvent; 067import java.util.EventObject; 068import java.util.List; 069 070import javax.swing.Action; 071import javax.swing.AbstractAction; 072import javax.swing.JTable; 073import javax.swing.JTextArea; 074import javax.swing.JTree; 075import javax.swing.KeyStroke; 076import javax.swing.ListSelectionModel; 077import javax.swing.LookAndFeel; 078import javax.swing.UIManager; 079import javax.swing.event.ListSelectionEvent; 080import javax.swing.event.ListSelectionListener; 081import javax.swing.table.TableCellEditor; 082import javax.swing.table.TableCellRenderer; 083import javax.swing.tree.DefaultTreeCellRenderer; 084import javax.swing.tree.DefaultTreeSelectionModel; 085import javax.swing.tree.TreeCellRenderer; 086import javax.swing.tree.TreeModel; 087import javax.swing.tree.TreePath; 088 089import com.puppycrawl.tools.checkstyle.api.DetailAST; 090 091/** 092 * This example shows how to create a simple JTreeTable component, 093 * by using a JTree as a renderer (and editor) for the cells in a 094 * particular column in the JTable. 095 * 096 * <a href="http://java.sun.com/products/jfc/tsc/articles/treetable1/index.html">Original Source Location</a> 097 * 098 * @author Philip Milne 099 * @author Scott Violet 100 * @author Lars Kühne 101 */ 102public 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}