001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2016 the original author or authors. 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 020package com.puppycrawl.tools.checkstyle.gui; 021 022import java.awt.Component; 023import java.awt.Dimension; 024import java.awt.FontMetrics; 025import java.awt.event.ActionEvent; 026import java.awt.event.MouseAdapter; 027import java.awt.event.MouseEvent; 028import java.util.EventObject; 029import java.util.List; 030 031import javax.swing.AbstractAction; 032import javax.swing.Action; 033import javax.swing.JTable; 034import javax.swing.JTextArea; 035import javax.swing.JTree; 036import javax.swing.KeyStroke; 037import javax.swing.LookAndFeel; 038import javax.swing.table.TableCellEditor; 039import javax.swing.tree.TreePath; 040 041import com.google.common.collect.ImmutableList; 042import com.google.common.primitives.Ints; 043import com.puppycrawl.tools.checkstyle.api.DetailAST; 044 045/** 046 * This example shows how to create a simple JTreeTable component, 047 * by using a JTree as a renderer (and editor) for the cells in a 048 * particular column in the JTable. 049 * 050 * <a href= 051 * "https://docs.oracle.com/cd/E48246_01/apirefs.1111/e13403/oracle/ide/controls/TreeTableModel.html"> 052 * Original Source Location</a> 053 * 054 * @author Philip Milne 055 * @author Scott Violet 056 * @author Lars Kühne 057 */ 058public class JTreeTable extends JTable { 059 private static final long serialVersionUID = -8493693409423365387L; 060 /** A subclass of JTree. */ 061 private final TreeTableCellRenderer tree; 062 /** JTextArea editor. */ 063 private JTextArea editor; 064 /** Line position map. */ 065 private List<Integer> linePositionMap; 066 067 /** 068 * Creates JTreeTable base on TreeTableModel. 069 * @param treeTableModel Tree table model 070 */ 071 public JTreeTable(ParseTreeTableModel treeTableModel) { 072 073 // Create the tree. It will be used as a renderer and editor. 074 tree = new TreeTableCellRenderer(this, treeTableModel); 075 076 // Install a tableModel representing the visible rows in the tree. 077 setModel(new TreeTableModelAdapter(treeTableModel, tree)); 078 079 // Force the JTable and JTree to share their row selection models. 080 final ListToTreeSelectionModelWrapper selectionWrapper = new 081 ListToTreeSelectionModelWrapper(this); 082 tree.setSelectionModel(selectionWrapper); 083 setSelectionModel(selectionWrapper.getListSelectionModel()); 084 085 // Install the tree editor renderer and editor. 086 setDefaultRenderer(ParseTreeTableModel.class, tree); 087 setDefaultEditor(ParseTreeTableModel.class, new TreeTableCellEditor()); 088 089 // No grid. 090 setShowGrid(false); 091 092 // No intercell spacing 093 setIntercellSpacing(new Dimension(0, 0)); 094 095 // And update the height of the trees row to match that of 096 // the table. 097 if (tree.getRowHeight() < 1) { 098 // Metal looks better like this. 099 setRowHeight(getRowHeight()); 100 } 101 102 setColumnsInitialWidth(); 103 104 final Action expand = new AbstractAction() { 105 private static final long serialVersionUID = -5859674518660156121L; 106 107 @Override 108 public void actionPerformed(ActionEvent event) { 109 expandSelectedNode(); 110 } 111 }; 112 final KeyStroke stroke = KeyStroke.getKeyStroke("ENTER"); 113 final String command = "expand/collapse"; 114 getInputMap().put(stroke, command); 115 getActionMap().put(command, expand); 116 117 addMouseListener(new MouseAdapter() { 118 @Override 119 public void mouseClicked(MouseEvent event) { 120 if (event.getClickCount() == 2) { 121 expandSelectedNode(); 122 } 123 } 124 }); 125 } 126 127 /** 128 * Do expansion of a tree node. 129 */ 130 private void expandSelectedNode() { 131 final TreePath selected = makeCodeSelection(); 132 133 if (tree.isExpanded(selected)) { 134 tree.collapsePath(selected); 135 } 136 else { 137 tree.expandPath(selected); 138 } 139 tree.setSelectionPath(selected); 140 } 141 142 /** 143 * Make selection of code in a text area. 144 * @return selected TreePath. 145 */ 146 private TreePath makeCodeSelection() { 147 final TreePath selected = tree.getSelectionPath(); 148 final DetailAST ast = (DetailAST) selected.getLastPathComponent(); 149 new CodeSelector(ast, editor, linePositionMap).select(); 150 return selected; 151 } 152 153 /** 154 * Set initial value of width for columns in table. 155 */ 156 private void setColumnsInitialWidth() { 157 final FontMetrics fontMetrics = getFontMetrics(getFont()); 158 // Six character string to contain "Column" column. 159 final int widthOfSixCharacterString = fontMetrics.stringWidth("XXXXXX"); 160 // Padding must be added to width for columns to make them fully 161 // visible in table header. 162 final int padding = 10; 163 final int widthOfColumnContainingSixCharacterString = 164 widthOfSixCharacterString + padding; 165 getColumn("Line").setMaxWidth(widthOfColumnContainingSixCharacterString); 166 getColumn("Column").setMaxWidth(widthOfColumnContainingSixCharacterString); 167 final int preferredTreeColumnWidth = 168 Ints.checkedCast(Math.round(getPreferredSize().getWidth() * 0.6)); 169 getColumn("Tree").setPreferredWidth(preferredTreeColumnWidth); 170 // Twenty eight character string to contain "Type" column 171 final int widthOfTwentyEightCharacterString = 172 fontMetrics.stringWidth("XXXXXXXXXXXXXXXXXXXXXXXXXXXX"); 173 final int preferredTypeColumnWidth = widthOfTwentyEightCharacterString + padding; 174 getColumn("Type").setPreferredWidth(preferredTypeColumnWidth); 175 } 176 177 /** 178 * Overridden to message super and forward the method to the tree. 179 * Since the tree is not actually in the component hierarchy it will 180 * never receive this unless we forward it in this manner. 181 */ 182 @Override 183 public void updateUI() { 184 super.updateUI(); 185 if (tree != null) { 186 tree.updateUI(); 187 } 188 // Use the tree's default foreground and background colors in the 189 // table. 190 LookAndFeel.installColorsAndFont(this, "Tree.background", 191 "Tree.foreground", "Tree.font"); 192 } 193 194 /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to 195 * paint the editor. The UI currently uses different techniques to 196 * paint the renderers and editors and overriding setBounds() below 197 * is not the right thing to do for an editor. Returning -1 for the 198 * editing row in this case, ensures the editor is never painted. 199 */ 200 @Override 201 public int getEditingRow() { 202 final Class<?> editingClass = getColumnClass(editingColumn); 203 204 if (editingClass == ParseTreeTableModel.class) { 205 return -1; 206 } 207 else { 208 return editingRow; 209 } 210 } 211 212 /** 213 * Overridden to pass the new rowHeight to the tree. 214 */ 215 @Override 216 public final void setRowHeight(int newRowHeight) { 217 super.setRowHeight(newRowHeight); 218 if (tree != null && tree.getRowHeight() != newRowHeight) { 219 tree.setRowHeight(getRowHeight()); 220 } 221 } 222 223 /** 224 * @return the tree that is being shared between the model. 225 */ 226 public JTree getTree() { 227 return tree; 228 } 229 230 /** 231 * Sets text area editor. 232 * @param textArea JTextArea component. 233 */ 234 public void setEditor(JTextArea textArea) { 235 editor = textArea; 236 } 237 238 /** 239 * Sets line position map. 240 * @param linePositionMap Line position map. 241 */ 242 public void setLinePositionMap(List<Integer> linePositionMap) { 243 this.linePositionMap = ImmutableList.copyOf(linePositionMap); 244 } 245 246 /** 247 * TreeTableCellEditor implementation. Component returned is the 248 * JTree. 249 */ 250 private class TreeTableCellEditor extends BaseCellEditor implements 251 TableCellEditor { 252 @Override 253 public Component getTableCellEditorComponent(JTable table, 254 Object value, 255 boolean isSelected, 256 int row, int column) { 257 return tree; 258 } 259 260 /** 261 * Overridden to return false, and if the event is a mouse event 262 * it is forwarded to the tree. 263 * 264 * <p>The behavior for this is debatable, and should really be offered 265 * as a property. By returning false, all keyboard actions are 266 * implemented in terms of the table. By returning true, the 267 * tree would get a chance to do something with the keyboard 268 * events. For the most part this is ok. But for certain keys, 269 * such as left/right, the tree will expand/collapse where as 270 * the table focus should really move to a different column. Page 271 * up/down should also be implemented in terms of the table. 272 * By returning false this also has the added benefit that clicking 273 * outside of the bounds of the tree node, but still in the tree 274 * column will select the row, whereas if this returned true 275 * that wouldn't be the case. 276 * 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 @Override 283 public boolean isCellEditable(EventObject event) { 284 if (event instanceof MouseEvent) { 285 for (int counter = getColumnCount() - 1; counter >= 0; 286 counter--) { 287 if (getColumnClass(counter) == ParseTreeTableModel.class) { 288 final MouseEvent mouseEvent = (MouseEvent) event; 289 final MouseEvent newMouseEvent = new MouseEvent(tree, mouseEvent.getID(), 290 mouseEvent.getWhen(), mouseEvent.getModifiers(), 291 mouseEvent.getX() - getCellRect(0, counter, true).x, 292 mouseEvent.getY(), mouseEvent.getClickCount(), 293 mouseEvent.isPopupTrigger()); 294 tree.dispatchEvent(newMouseEvent); 295 break; 296 } 297 } 298 } 299 300 return false; 301 } 302 } 303}